0


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

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

post发送请求

(0)权限授理

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

@Overrideprotectedvoidconfigure(HttpSecurity http)throwsException{......    
    http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class);}

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

@OverrideprotectedvoiddoFilterInternal(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,FilterChain filterChain)throwsServletException,IOException{//1.获取tokenString token = httpServletRequest.getHeader("token");if(!StringUtils.hasText(token)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);//这里可以放行的原因在于后面还有FilterSecurityInterceptor等其他过滤器,//如果没有认证后面还会被拦截下来return;//这里如果没有return,响应回来之后还会调用后面的代码}

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

filterChain.doFilter(httpServletRequest,httpServletResponse);

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

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

publicclassLoginServiceImplimplementsLoginService{......publicResponseResultlogin(@RequestBodyUser user){UsernamePasswordAuthenticationToken authenticationToken =newUsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());Authentication authentication = authenticationManager.authenticate(authenticationToken);......}}

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

UsernamePasswordAuthenticationToken authenticationToken =newUsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());

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

@ServicepublicclassUserDetailsServiceImplimplementsUserDetailsService{@AutowiredprivateUserMapper userMapper;@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{//这里的UserDetails是一个接口,因此需要写一个对应的实现类//查询用户信息LambdaQueryWrapper<User> queryWrapper =newLambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);User user = userMapper.selectOne(queryWrapper);//如果没有查询到用户,就抛出异常if(Objects.isNull(user)){thrownewRuntimeException("用户名或者密码错误!");}//因为在UsernamePasswordAuthenticationFilter之后//会调用一个ExceptionTranslationFilter,所以只要出现//异常都会被捕获到//TODO 查询对应的权限信息List<String> list =newArrayList<>(Arrays.asList("test","admin"));//封装成UserDetails返回,标注类AllArgsConstructor,因此可以直接传入returnnewLoginUser(user,list);}

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

(3)UserDetails实现类LoginUser的构造

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

publicclassLoginUserimplementsUserDetails{privateUser user;privateList<String> permissions;publicLoginUser(User user,List<String> permissions){this.user = user;this.permissions = permissions;}//redis默认不会将List<SimpleGrantedAuthority> authorities序列化,//因为会报异常,所以我们不需要把authorities这个集合存入到redis之中@JSONField(serialize =false)privateList<SimpleGrantedAuthority> authorities;@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){if(authorities !=null){return authorities;}
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());//SimpleGrantedAuthority::new使用流的构造器,最后Collectors.toList()将结果转为list类型return authorities;}......}

这里的通过

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

将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]]

注意:这里如果调用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 =newHashMap<>();
map.put("token",jwt);//把完整的用户信息存入redis,userId作为key,这里面的键值对键为token值为jwt
redisCache.setCacheObject("login:"+userid,loginUser);//这里必须打开redis,才能够保存得上returnnewResponseResult(200,"登录成功",map);

2.第二次登录调用

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

//1.获取tokenString 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();thrownewRuntimeException("token非法");}

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

String rediskey ="login:"+userId;LoginUser loginUser = redisCache.getCacheObject(rediskey);if(Objects.isNull(loginUser)){thrownewRuntimeException("用户未登录");}

接下来将loginUser封装成为UsernamePasswordAuthenticationToken类

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =newUsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());

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

SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

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

标签: spring 安全 java

本文转载自: https://blog.csdn.net/znevegiveup1/article/details/132652931
版权归原作者 唐僧爱吃唐僧肉 所有, 如有侵权,请联系我们删除。

“Spring Security安全登录的调用过程以及获取权限的调用过程”的评论:

还没有评论