• Spring Security安全登录的调用过程以及获取权限的调用过程


    1.第一次登录时候调用/user/login整个流程分析

    post发送请求

    (0)权限授理

    首先调用SecurityConfig.java中的config函数将jwtAuthenticationTokenFilter过滤器放在UsernamePasswordAuthenticationFilter之前

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        ......    
        http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (1)首先运行JwtAuthenticationTokenFilter extends OncePerRequestFilter中的doFilterInternal函数

        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
            //1.获取token
            String token = httpServletRequest.getHeader("token");
            if(!StringUtils.hasText(token))
            {
                filterChain.doFilter(httpServletRequest,httpServletResponse);
                //这里可以放行的原因在于后面还有FilterSecurityInterceptor等其他过滤器,
                //如果没有认证后面还会被拦截下来
                return;
                //这里如果没有return,响应回来之后还会调用后面的代码
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里由于没有获取到token,因此调用!StringUtils.hasText(token),这里使用

    filterChain.doFilter(httpServletRequest,httpServletResponse);
    
    • 1

    继续运行其他拦截器,然后返回

    (2)接下来运行LoginServiceImpl.java中的login函数

    public class LoginServiceImpl implements LoginService{
        ......
        public ResponseResult login(@RequestBody User user){
             UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
             Authentication authentication = authenticationManager.authenticate(authenticationToken);   
             ...... 
        } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里先根据UsernamePasswordAuthenticationToken放入user的name和password创建authenticationToken类

    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
    
    • 1

    接下来调用authenticate函数认证,这里authenticate函数会调用UserDetailsService的实现类UserDetailsServiceImpl函数中的loadUserByUsername进行读取数据库

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //这里的UserDetails是一个接口,因此需要写一个对应的实现类
            //查询用户信息
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getUserName,username);
            User user = userMapper.selectOne(queryWrapper);
            //如果没有查询到用户,就抛出异常
            if(Objects.isNull(user))
            {
                throw new RuntimeException("用户名或者密码错误!");
            }
            //因为在UsernamePasswordAuthenticationFilter之后
            //会调用一个ExceptionTranslationFilter,所以只要出现
            //异常都会被捕获到
    
            //TODO 查询对应的权限信息
            List<String> list = new ArrayList<>(Arrays.asList("test","admin"));
            //封装成UserDetails返回,标注类AllArgsConstructor,因此可以直接传入
            return new LoginUser(user,list);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这里的UserDetailsService的调用有点像空中楼阁一样,原因在于它是在authenticationManager.authenticate(…)这个函数中默默的调用,因此出现的并不明显

    (3)UserDetails实现类LoginUser的构造

    这里创造新的类LoginUser并且传入user和list两大参数,进入LoginUser的构造函数和授权函数进行查看

    public class LoginUser implements UserDetails {
    
        private User user;
    
        private List<String> permissions;
    
        public LoginUser(User user,List<String> permissions)
        {
            this.user = user;
            this.permissions = permissions;
        }
    
        //redis默认不会将List authorities序列化,
        //因为会报异常,所以我们不需要把authorities这个集合存入到redis之中
        @JSONField(serialize = false)
        private List<SimpleGrantedAuthority> authorities;
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if(authorities != null)
            {
                return authorities;
            }
            authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            //SimpleGrantedAuthority::new使用流的构造器,最后Collectors.toList()将结果转为list类型
            return authorities;
        }
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    这里的通过

    authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    
    • 1

    将permissions中的两个参数"test"、"admin"分别构造SimpleGrantedAuthority类,并且放入Collectors之中,最终返回构造的Collectors权限信息,取出来的UsernamePasswordAuthenticationToken和Authentication类分别为

    authentication = UsernamePasswordAuthenticationToken [Principal=LoginUser(user=User(id=111110, userName=xiaomazai, nickName=xiaomazai, password=$2a$10$VDFx9Khqpo4FAkx/NZLL3uZO0PcBZekL3AU5JtzJuuxbn2emZUCUK, status=0, email=null, phonenumber=null, sex=null, avatar=null, userType=1, createBy=null, createTime=null, updateBy=null, updateTime=null, delFlag=0), permissions=[test, admin], authorities=[test, admin]), Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[test, admin]]
    
    • 1

    注意:这里如果调用authentication.toString()这个函数,就会调用LoginUser中的getAuthorities函数内容

    (4)redis保存结果,返回登录成功

    这里通过对userid的一个jwt加密创建出一个token,然后将token值保存到HashMap之中,最后返回登录成功响应

    //如果认证通过,使用userId生成一个jwt,jwt存入ResponseResult返回
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    String userid = loginUser.getUser().getId().toString();
    String jwt = JwtUtil.createJWT(userid);
    //把完整的用户信息存入redis之中,userId作为key,
    Map<String,String> map = new HashMap<>();
    map.put("token",jwt);
    //把完整的用户信息存入redis,userId作为key,这里面的键值对键为token值为jwt
    redisCache.setCacheObject("login:"+userid,loginUser);
    //这里必须打开redis,才能够保存得上
    return new ResponseResult(200,"登录成功",map);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.第二次登录调用

    第二次登录的时候需要在headers之中加入token头,直接访问hello接口部分
    访问hello部分接口
    进入到doFilterInternal函数之中运行,首先获取token的内容,然后根据token解析出userId的内容

    //1.获取token
    String token = httpServletRequest.getHeader("token");
    if(!StringUtils.hasText(token))
    {
        filterChain.doFilter(httpServletRequest,httpServletResponse);
        //这里可以放行的原因在于后面还有FilterSecurityInterceptor等其他过滤器,
        //如果没有认证后面还会被拦截下来
        return;
        //这里如果没有return,响应回来之后还会调用后面的代码
    }
    
    String userId;
    //2.解析token(响应不为空)
    try {
        Claims claims = JwtUtil.parseJWT(token);
        userId = claims.getSubject();
        //这里parseJWT就是一个解析过程,不需要过多深究
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException("token非法");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    获取到userId之后,从redis中根据userId找寻到对应的LoginUser类别

    String rediskey = "login:"+userId;
    LoginUser loginUser = redisCache.getCacheObject(rediskey);
    if(Objects.isNull(loginUser))
    {
        throw new RuntimeException("用户未登录");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接下来将loginUser封装成为UsernamePasswordAuthenticationToken类

    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
    
    • 1

    然后判断UsernamePasswordAuthentication是否能够通过安全认证

    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    
    • 1

    这里由于hello接口上面标注了@PreAuthorize(“hasAuthority(‘test’)”),因此可以成功放行
    完整的调用过程如下图所示
    完整的调用过程

  • 相关阅读:
    人工神经网络概念及组成,人工神经网络基本结构
    极客日报:腾讯应届生年薪40万起步;亚马逊对欧盟7.46亿欧元罚款提出上诉
    区块链(6):p2p去中心化介绍
    ChatGPT Plugin开发setup - Java(Spring Boot) Python(fastapi)
    Redis 获取、设置配置文件
    百度智能云千帆 ModelBuilder 技术实践系列:通过 SDK 快速构建并发布垂域模型
    【微信小程序】6天精准入门(第4天:自定义组件及案例界面)附源码
    公园设施3D可视化:游园新体验,触手可及的未来
    【学习笔记之我要C】预处理
    基于Java+SpringBoot+Vue前后端分离美食烹饪互动平设计和实现
  • 原文地址:https://blog.csdn.net/znevegiveup1/article/details/132652931