SpringSecurity的使用与步骤

news2024/12/25 14:23:33

1、SpringSecurity流程图

2、导入坐标

<!-- spring-boot-starter-security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--        jjwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

3、SpringSecurity的配置类

package com.springsecuritylearning.config;

import com.springsecuritylearning.filter.DoFilterInternal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DoFilterInternal filterInternal;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //关闭csrf
            .csrf().disable()
            //不通过的Session获取SecurityContext
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            //对于该接口放行
            .antMatchers("/auth/user/**").permitAll()
            //其余接口拦截
            .anyRequest().authenticated();
        http
            .addFilterBefore(filterInternal, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

4、根据流程图可以看出用户登录接口,将用户名和密码进行提交,所以先写登录接口(流程图第1步)

/**
 * 登录验证
 */
@RestController
@RequestMapping("/auth/user")
@CrossOrigin
@Slf4j
public class UserController {

    @Autowired
    private UserServiceImpl userService;

    /**
     * 用户登录
     * @param user
     * @return
     */
    @PostMapping("/login")
    public R login(User user){
        System.out.println(user);
        String token = userService.login(user);
        HashMap<String,String> map=new HashMap<>();
        map.put("token",token);
        return R.ok(map).setCode(200);
    }
}

5、创建login接口的实现类(流程图第2、3步,第4步自动完成,最后获取到Authentication为第9步)

package com.xuechengplusauth.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuechengplusauth.domain.User;
import com.xuechengplusauth.mapper.UserMapper;
import com.xuechengplusauth.security.LoginUser;
import com.xuechengplusauth.service.UserService;
import com.xuechengpluscommon.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

/**
* @author 卿十三
* @description 针对表【xc_user】的数据库操作Service实现
* @createDate 2023-02-08 14:48:36
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public String login(User user) {
        //将请求的信息封装到Authentication,实现类为UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        //判断Authentication是否通过认证,如果为空则没有通过PasswordEncoder的认证,说明账号密码错误
        if(ObjectUtils.isEmpty(authenticate)){
            throw new RuntimeException("未通过认证");
        }
        //从Authentication获取信息
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        //从loginUser类获取User信息
        User loginUserUser = loginUser.getUser();
        String id = loginUserUser.getId();
        //将用户Id保存到redis
        redisTemplate.opsForValue().set(id,loginUser);
        //将用户Id转成JWT然后返回
        String token = JwtUtils.createToken(id);
        return token;
    }
}

6、经过自动认证之后来到UserDetailsService进行手动填充信息,首先创建LoginUser类实现UserDetails接口,用来作为loadUserByUsername类的返回值(流程图5、6、7、8步)

package com.xuechengplusauth.security;

import com.alibaba.fastjson.annotation.JSONField;
import com.xuechengplusauth.domain.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {

    //存储用户信息
    private User user;

    //存储权限信息
    private List<String> authList;

    //优化getAuthorities类,避免多次重复调用
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LoginUser(User user,List<String> authList){
        this.user=user;
        this.authList=authList;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }else{
            authorities=new ArrayList<>();
            for (String s : authList) {
                SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority(s);
                authorities.add(simpleGrantedAuthority);
            }
            return authorities;
        }
    }
    //用户密码,通过user.getPassword()获取的密码来通过PassWordEncoder加密后和数据库的密码进行校验
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    //用户账号
    @Override
    public String getUsername() {
        return user.getUsername();
    }
    //返回true意味着账号未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //返回true意味着账号未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //返回true意味着凭证未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //返回true意味着可以使用
    @Override
    public boolean isEnabled() {
        return true;
    }
}

7、创建UserDetailsImpl类实现UserDetailsService接口,通过重写loadUserByUsername方法实现自定义校验(流程图5、6、7、8步)

package com.xuechengplusauth.security.Impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuechengplusauth.domain.Menu;
import com.xuechengplusauth.domain.Permission;
import com.xuechengplusauth.domain.User;
import com.xuechengplusauth.domain.UserRole;
import com.xuechengplusauth.mapper.MenuMapper;
import com.xuechengplusauth.mapper.PermissionMapper;
import com.xuechengplusauth.mapper.UserMapper;
import com.xuechengplusauth.mapper.UserRoleMapper;
import com.xuechengplusauth.security.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private MenuMapper menuMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据输入的账号username从数据库查询到用户
        LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,username);
        //获取查到的用户
        User user = userMapper.selectOne(queryWrapper);
        //如果为空用户不存在
        if(ObjectUtils.isEmpty(user)){
            throw new RuntimeException("用户不存在");
        }

        //从数据库查询该用户所拥有的权限信息
            //创建权限集合,用来存储权限
        List<String> authList=new ArrayList<>();
        String id = user.getId();
        LambdaQueryWrapper<UserRole> queryWrapper1=new LambdaQueryWrapper<>();
        queryWrapper1.eq(UserRole::getUserId,id);
        UserRole userRole = userRoleMapper.selectOne(queryWrapper1);
        String roleId = userRole.getRoleId();
        LambdaQueryWrapper<Permission> queryWrapper2=new LambdaQueryWrapper<>();
        queryWrapper2.eq(Permission::getRoleId,roleId);
        List<Permission> permissions = permissionMapper.selectList(queryWrapper2);
        for (Permission permission : permissions) {
            String menuId = permission.getMenuId();
            Menu menu = menuMapper.selectById(menuId);
            String code = menu.getCode();
            authList.add(code);
        }
        //将用户信息和权限信息封装到loginUser中
        LoginUser loginUser=new LoginUser(user,authList);
        
        
        //返回loginUser
        return loginUser;
    }
}

8、编写拦截器将Authentication保存到上下文中,通过继承OncePerRequestFilter类实现doFilterInternal接口实现拦截器的功能(流程图第10步)

package com.xuechengplusauth.filter;

import com.xuechengplusauth.security.LoginUser;
import com.xuechengpluscommon.utils.JwtUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;

@Component
public class DoFilterInternal extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头的token信息
        String token = request.getHeader("token");
        //如果为空,则放行
        if(StringUtils.isEmpty(token)){
            filterChain.doFilter(request,response);
            return;
        }
        //JWT进行解密获取Token信息
        String tokenInfo = JwtUtils.getTokenInfo(token);
        //从数据库查询token信息中的用户
        LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(tokenInfo);
        //如果用户存在说明已经过认证,如果不存在,说明未经过认证
        if(ObjectUtils.isEmpty(loginUser)){
            throw new RuntimeException("用户不存在");
        }
        //用户已经进行认证的前提下,获取UserDetails的信息,从其中获取认证用户的权限和信息
        Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
        //通过SecurityContextHolder.getContext().setAuthentication方法将Authentication保存到上下文
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
            =new UsernamePasswordAuthenticationToken(loginUser,null,authorities);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

9、测试登录

设置token未空,在Security配类开启登录接口的放行,设置JWT的存活时间和Redis的存活时间为一小时

返回值为token

10、测试其他模块,该模块已导入SpringSecurity模块的包

  • 带有Token进行接口测试(成功访问)

  • 不带Token进行接口测试(禁止访问)

11、权限功能测试

  • 启动类添加注解

@EnableGlobalMethodSecurity(prePostEnabled = true)
  • 在Controller接口添加注解@PreAuthorize


    @RequestMapping("/test1")
    @PreAuthorize("hasAuthority('p1')")
    public String r(){
        return "访问资源R1";
    }

    @RequestMapping("/test2")
    @PreAuthorize("hasAuthority('p2')")
    public String test(){
        return "访问资源test";
    }
  • 添加三种权限p1,p2,p3

authList.add("p1");
authList.add("p2");
authList.add("p3");

获取当前用户的权限信息

xc_sysmanager
xc_sysmanager_user
xc_sysmanager_user_add
xc_sysmanager_user_edit
xc_sysmanager_user_view
xc_sysmanager_user_delete
xc_sysmanager_doc
xc_sysmanager_log
xc_teachmanager_course
xc_teachmanager_course_add
xc_teachmanager_course_base
p1
p2
p3

测试p1,p2接口访问(成功访问)

删除P1,2,p3权限,当前用户拥有的权限

xc_sysmanager
xc_sysmanager_user
xc_sysmanager_user_add
xc_sysmanager_user_edit
xc_sysmanager_user_view
xc_sysmanager_user_delete
xc_sysmanager_doc
xc_sysmanager_log
xc_teachmanager_course
xc_teachmanager_course_add
xc_teachmanager_course_base

测试接口(访问失败)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

生物素-磺基-活性酯,Sulfo-NHS Biotin科研用试剂简介;CAS:119616-38-5

生物素-磺基-活性酯,Sulfo-NHS Biotin 结构式&#xff1a; ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 英文名称&#xff1a;Sulfo-NHS-Biotin Sulfosuccinimidyl biotin 中文名称&#xff1a;磺酸基-Biotin-N-琥珀酰亚胺基酯 CAS&…

自己总结优化代码写法

jdk1.7新特性详解 开发期间略知jdk1.7的一些特性&#xff0c;没有真正的一个一个得展开研究&#xff0c;而是需要说明再去查&#xff0c;导致最整个新特性不是特别的清楚&#xff0c;这种情况以后得需要改变了&#xff0c;否则就会变成代码的奴隶。现在正好有时间可以细细的研…

SpringCloud第二讲 Ribbon负载均衡源码分析

前言介绍&#xff1a; 这一讲我们将依据Eureka的负载均衡规则&#xff0c;Eureka的具体服务搭建以及服务注册和服务发现可以参考基于Eureka实现服务注册和服务发现_热爱Java的编程小白的博客-CSDN博客 Eureka的服务搭建之后便可以在这上面进行服务注册&#xff0c;如果存在两个…

产品权限分析与设计

目前我们使用的访问控制授权方案&#xff0c;主要有以下4种&#xff1a;DAC自主访问控制ACL 访问控制列表MAC强制访问控制RBAC 基于角色的访问控制笔者将拆解和分析这4种权限管理方案的逻辑&#xff0c;并告诉你&#xff0c;这4种权限分别可以运用在什么样的场景中&#xff0c;…

不能注册?让小白也能使用【ChatGPT】

ChatGPT介绍最近ChatGPT可谓是非常火爆&#xff0c;使得互联网各界人士都备受关注&#xff0c;如果你还不了解ChatGPT,那你真的有点落后了哈。 简单介绍一下ChatGPTChatGPT是由美国人工智能OpenAI研究开发的一种全新聊天机器人模型&#xff0c;它能够通过学习和理解人类的语言跟…

研一寒假C++复习笔记--左值和右值的理解和使用

目录 1--左值和右值的定义 2--简单理解左值和右值的代码 3--非const引用只能接受左值 1--左值和右值的定义 左值&#xff1a;L-Value&#xff0c;L理解为 Location&#xff0c;表示可寻&#xff1b; 右值&#xff1a;R-Value&#xff0c;R理解为 Read&#xff0c;表示可读&a…

Windows 安装appium环境

1 windows Appium环境 1.1 安装Node.js Node.js的安装相对简单,下载安装包安装&#xff08;安装包node-v19.6.0-x64.msi&#xff09;, nodejs 安装 然后一路狂点下一步就可以了 安装完成后,在终端中输入node -v,显示版本号则表示安装成功 node-v16.13.1 1.2 JDK安装及环境变…

路由器刷固件

前言 我希望可以远程访问我的电脑。但&#xff0c;我不希望电脑总是处于运行状态&#xff0c;因为那样比较费电。所以需要一个方案&#xff0c;能将睡眠/关机中的电脑唤醒。 方案一&#xff1a;选用智能插座&#xff0c;远程给电脑上电。电脑设置上电自启。但&#xff0c;这存…

试题 算法训练 N皇后问题(明确清晰)

试题 算法训练 N皇后问题 提交此题 评测记录 资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;100ms Java时间限制&#xff1a;300ms Python时间限制&#xff1a;500ms 问题描述   在N*N的方格棋盘放置了N个皇后&#xff0c;使得它们不相互攻击&#xff08;即…

色彩-基础理论

颜色三大指标 色相 色相是颜色的一个属性&#xff0c;只有黑白灰没有色相这个属性(那银灰色是什么&#xff1f;) 颜色的相貌&#xff0c;指的也是给颜色一个名字 例如&#xff1a;暗红、酒红、土黄、墨绿 饱和度 颜色的鲜艳程度 纯度 饱和度主要取决于含色成分和消色成分&a…

IDE2022源码编译tomcat

因为学习需要&#xff0c;我需要源码编译运行tomcat对其源码进行一个简单的追踪分析。由于先前并未接触过java相关的知识&#xff0c;安装阻力巨大。最后请教我的开发朋友才解决了最后的问题。将其整理出来&#xff0c;让大家能够快速完成相关的部署。本文仅解决tomcat-8.5.46版…

tensorflow.js 视频图片多目标检测

前言&#xff1a;Tensorflow.js 官方提供了很多常用模型库&#xff0c;涵盖了平时开发中大部分场景的模型。例如&#xff0c;前面提到的图片识别&#xff0c;除此之外还有人体姿态识别&#xff0c;目标物体识别&#xff0c;语音文字等识别。其中一些可能是 Python 转换而来&…

前后端RSA互相加解密、加签验签、密钥对生成(Java)

目录一、序言二、关于PKCS#1和PKCS#8格式密钥1、简介2、区别二、关于JSEncrypt三、关于jsrsasign四、前端RSA加解密、加验签示例1、相关依赖2、cryptoUtils工具类封装3、测试用例五、Java后端RSA加解密、加验签1、CryptoUtils工具类封装2、测试用例六、前后端加解密、加验签交互…

导数与微分总复习——“高等数学”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰来复习一下之前学过的知识点&#xff0c;也就是导数与微分的总复习&#xff0c;依旧是高等数学的内容&#xff0c;主要是明天就要考高等数学了&#xff0c;哈哈哈&#xff0c;下面&#xff0c;让我们一起进入高等数学…

取电芯片全协议都可兼容

乐得瑞PD协议芯片/PD取电芯片/PD受电端协议芯片 支持5/9/12/15/20v定制 1、概述 LDR6328S 是乐得瑞科技有限公司开发的一款兼容 USB PD、QC 和 AFC 协议的 Sink 控制器。 LDR6328S 从支持 USB PD、QC 和 AFC 协议的适配器取电&#xff0c;然后供电给设备。比如可以配置适配器输…

二十九、异常处理

目录 ①前言: ②常见的运行时异常 ③常见的编译时异常 ④异常的处理机制 ⑤自定义异常 ①前言: 1.什么是异常&#xff1f; 异常是程序在“编译”或者“执行”的过程中可能出现的问题&#xff0c;注意&#xff1a;语法错误不算在异常体系中。 比如: 数据索引越界异常&…

C语言的程序环境和预处理详解

目录 一、程序的翻译环境和执行环境 二、编译和链接详解 2、1 翻译环境 2、2 编译过程详解 2、3 执行环境 三、预处理详解 3、1 预定义符号 3、2 #define 3、2、1 #define定义的符号 3、2、2 #define 定义宏 3、2、3 #define 替换规则 3、3 宏和函数的对比 3、4 条件编译 3、5…

CUDA中的底层驱动API

文章目录CUDA底层驱动API1. Context2. Module3. Kernel Execution4. Interoperability between Runtime and Driver APIs5. Driver Entry Point Access5.1. Introduction5.2. Driver Function Typedefs5.3. Driver Function Retrieval5.3.1. Using the driver API5.3.2. Using …

Springboot扩展点之BeanPostProcessor

前言 Springboot&#xff08;Spring&#xff09;的扩展点其实有很多&#xff0c;但是都有一个共同点&#xff0c;都是围绕着Bean和BeanFactory&#xff08;容器&#xff09;展开的&#xff0c;其实这也很好理解&#xff0c;Spring的核心是控制反转、依赖注入、面向切面编程&…

西湖论剑 2023 比赛复现

WEB real_ez_node 在 route/index.js 中&#xff1a; router.post(/copy,(req,res)>{res.setHeader(Content-type,text/html;charsetutf-8)var ip req.connection.remoteAddress;console.log(ip);var obj {msg: ,}if (!ip.includes(127.0.0.1)) {obj.msg"only for…