SpringSecurity为什么要这么配置?SpringSecurity判断登录源码解读

23 篇文章 0 订阅
3 篇文章 0 订阅

前言

咱们从源码的角度来看看SpringSecurity的处理过程,整个过程比较长,设计的函数调用也比较多,为了在解释详细的同时增加咱们的可读性,我在源码的关键步骤都添加了小标题,大家可以通过小标题来了解大概过程,其他步骤仅以图片形式展示,方便大家一步步跟下来。那一起来看看吧!
在这里插入图片描述

SpringSecurity需要配置什么

这里咱们以单体架构为例,使用JWT和SpringSecurity结合之后的样例来讲解。

配置类

@Configuration
//开启注解授权认证的注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
      /**
 	  * 为了方便阅读,这里省略一部分非重要代码,需要全部的代码评论即可
 	  **/

//    把我们自己写好的过滤器添加到过滤器的前面
        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}

添加自定义的Filter

首先是JwtAuthenticationTokenFilter这个过滤器,这个是咱们整合JWT进行权限验证的关键组成部分,这个过滤器完全由我们自定义,可以实现我们想要做的功能,这里提供一个样例

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    RoleandpermService roleandpermService;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//获取token
        String token = httpServletRequest.getHeader("token");
        if (!StringUtils.hasText(token)) {
//            这里放行就是让其他的过滤器帮我们解决未登录
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
//        解析token
        String userid = "";

        try {
            Claims claims = JwtUtils.getClaims(token);
            userid = (String) claims.get("userid");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解析token异常");
        }

//        redis中获取信息
        User o = (User) redisTemplate.opsForValue().get("login" + userid);
        if (Objects.isNull(o)) {
            throw new RuntimeException("token异常");
        }
        // 拿到用户的权限,并对权限进行封装
        List<Roleandperm> byUserId = roleandpermService.getByUserId(new Long(o.getUser_id()));
        List<GrantedAuthority> newList=new LinkedList<>();
        List<String> perms=new LinkedList<>();
        byUserId.forEach(p->{
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(p.getPerm());
            newList.add(simpleGrantedAuthority);
            perms.add(p.getPerm());
        });
//        存入SecurityContextgholder,因为后续的过滤器需要在这个东西中找到认证的信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(new LoginUser(o,perms), null, newList);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

大家看上面的注解其实能理解这个过滤器是干啥的,其实就是截取请求中的Headers中的token信息,然后对这个token进行解析,然后根据token中的id信息在Redis中拿到权限信息,并且封装成为UsernamePasswordAuthenticationToken对象,交给后续过滤器进行处理。

在过滤器中我们进行权限验证了吗?

很明显,没有的。我们只是把权限封装起来,交给后续的过滤器使用了。

那他到底是什么时候调用的呢?

开始进入源码状态了,大家准备好!

判断是否登录源码解析

进入过滤器链处理

首先就是FilterChainProxydoFilter方法,

public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
           // 判断是否所有过滤器都处理结束了
            if (this.currentPosition == this.size) {
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(LogMessage.of(() -> {
                        return "Secured " + FilterChainProxy.requestLine(this.firewalledRequest);
                    }));
                }

                this.firewalledRequest.reset();
                this.originalChain.doFilter(request, response);
            } else {
            // 指向过滤器的下标
                ++this.currentPosition;
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                if (FilterChainProxy.logger.isTraceEnabled()) {
                    FilterChainProxy.logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));
                }

                nextFilter.doFilter(request, response, this);
            }
        }
    }

上面的代码简单来说,就是循环遍历additionalFilters中的过滤器,然后依次对请求进行处理,我们看以下additionalFilters到底存了什么
在这里插入图片描述
你会发现我们自定义的JwtAuthenticationTokenFilter 也在里面,这说明在遍历过程中,我们自定义的Filter也会对请求进行处理。

JwtAuthenticationTokenFilter干了啥

现在咱们回到JwtAuthenticationTokenFilter中,你会发现我们把用户和权限信息放在SecurityContextHolder.getContext里面,这是干啥用的呢,咱们接着往下看。

SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

FilterSecurityInterceptor判断是否登录

前面咱们提到了在SecurityContextHolder.getContext()里把usernamePasswordAuthenticationToken放进去了,那放进去是为了干什么呢?

一个请求过来,我们是不是要判断登陆状态,那我们是不是可以基于usernamePasswordAuthenticationToken来判断是否登录呢?

答案是可以的

那他是怎么判断的呢

由于篇幅原因咱们直接来到FilterSecurityInterceptor的处理过程。
在这里插入图片描述

然后一步步点进去
在这里插入图片描述
在这里插入图片描述

获取JwtAuthenticationTokenFilter中存储的用户认证信息

在这里插入图片描述
注意这里,authenticated是不是有点熟悉,我们刚才怎么放用户信息来着,是不是

SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken)

我们点进去看看,他获取了什么
在这里插入图片描述

是不是发现了我们的老朋友SecurityContextHolder.getContext(),通过getAuthentication就获取到了我们的用户信息和权限信息

接下来返回到AbstractSecurityInterceptor,接着往下走,一路点进去

在这里插入图片描述
在这里插入图片描述

投票器判断是否用户是否登录

在这里插入图片描述
就来到了上图的地方,这里很有意思哈。我们看一下他要做什么,SpringSecurity在判断用户状态时用的是一种叫做投票器的机制。可以理解为有一个投票通过那么该请求就可以继续执行,如果一票都没通过,那就直接抛出异常就可以了。
在这里插入图片描述

voter就可以认为是投票者,点进去

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

判断Authentication是否为AnonymousAuthentication

在这里插入图片描述

关键来了,兄弟们,这里的!isAnonymous是什么意思呢?

着我们就要回溯以下之前的过滤器AnonymousAuthenticationFilter
在这里插入图片描述
注意哈,咱们的JwtAuthenticationTokenFilter执行顺序是在这个过滤器之前的

如果说你没有传递token,那我们就不会往SecurityContextHolder.getContext()放认证对象,那么AnonymousAuthenticationFilter就会帮我们放一个Anonymous的认证对象进去,这样我们在FilterSecurityInterceptor就可以判断请求的用户是否已经登陆。

接下来一路返回到咱们投票器的部分

在这里插入图片描述
这里的result为1,也就直接返回,就可以进行后续的操作了,并不会抛出异常,否则会deny++。

当没有直接返回并且deny>0,就会抛出异常也就是用户授权未通过,那就直接返回403了。

到这里为止,我们实现了什么功能?

就是一个判断用户是否登录的过程,那验权是不是还没做,别着急,咱们接着看。

不传递token会怎么处理未登录请求

我们看下不传token会怎样,直接看Voter这边
在这里插入图片描述

你会发现deny直接++

然后接下来就开始抛出异常了
在这里插入图片描述

然后交给ExceptionTranslationFilter进行处理,注意哈这也是咱们之前调用的过滤器链中的一个过滤器,然后就可以返回403了。

在这里插入图片描述

总结

到现在我们大概了解了SpringSecurity怎么判断请求是否登录的。简单来说就是我们可以通过自定过滤器的方式,将用户信息和权限信息封装起来存储到的SecurityContextHolder中,然后再由后续的FilterSecurityInterceptor判断是否登录,未登陆的话SecurityContextHolder存储的是匿名用户,这样就可以判断是否登录了。今天就到这了,下期咱们讲讲SpringSecurity怎么做的权限验证!

在这里插入图片描述

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王不头秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值