SpringSecurity两种资源放行策略

Spring Security 中,到底该怎么样给资源额外放行?

1.两种思路

在 Spring Security 中,有一个资源,如果你希望用户不用登录就能访问,那么一般来说,你有两种配置策略:

第一种就是在 configure(WebSecurity web) 方法中配置放行,像下面这样:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
}

第二种方式是在 configure(HttpSecurity http) 方法中进行配置:

http.authorizeRequests()

.antMatchers(“/hello”).permitAll()

.anyRequest().authenticated()

两种方式最大的区别在于,第一种方式是不走 Spring Security 过滤器链,而第二种方式走 Spring Security 过滤器链,在过滤器链中,给请求放行。

在我们使用 Spring Security 的时候,有的资源可以使用第一种方式额外放行,不需要验证,例如前端页面的静态资源,就可以按照第一种方式配置放行。

有的资源放行,则必须使用第二种方式,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需要登录就能访问到的,但是我们却不能将登录接口用第一种方式暴露出来,登录请求必须要走 Spring Security 过滤器链,因为在这个过程中,还有其他事情要做。

接下来我以登录接口为例,来和小伙伴们分析一下走 Spring Security 过滤器链有什么不同。

2.登录请求分析

首先大家知道,当我们使用 Spring Security,用户登录成功之后,有两种方式获取用户登录信息:

SecurityContextHolder.getContext().getAuthentication()

这两种方式获取到的数据都是来自 SecurityContextHolder,SecurityContextHolder 中的数据,本质上是保存在 ThreadLocal
中, ThreadLocal
的特点是存在它里边的数据,哪个线程存的,哪个线程才能访问到。

这样就带来一个问题,当用户登录成功之后,将用户用户数据存在 SecurityContextHolder 中(thread1),当下一个请求来的时候(thread2),想从 SecurityContextHolder 中获取用户登录信息,却发现获取不到!为啥?因为它俩不是同一个 Thread。

但实际上,正常情况下,我们使用 Spring Security 登录成功后,以后每次都能够获取到登录用户信息,这又是怎么回事呢?

这我们就要引入 Spring Security 中的 SecurityContextPersistenceFilter
了。

小伙伴们都知道,无论是 Spring Security 还是 Shiro,它的一系列功能其实都是由过滤器来完成的,在 Spring Security 中,松哥前面跟大家聊了 UsernamePasswordAuthenticationFilter
过滤器,在这个过滤器之前,还有一个过滤器就是  SecurityContextPersistenceFilter
,请求在到达  UsernamePasswordAuthenticationFilter
之前都会先经过  SecurityContextPersistenceFilter

我们来看下它的源码(部分):

public class SecurityContextPersistenceFilter extends GenericFilterBean {
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
   throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) req;
  HttpServletResponse response = (HttpServletResponse) res;
  HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
    response);
  SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
  try {
   SecurityContextHolder.setContext(contextBeforeChainExecution);
   chain.doFilter(holder.getRequest(), holder.getResponse());
  }
  finally {
   SecurityContext contextAfterChainExecution = SecurityContextHolder
     .getContext();
   SecurityContextHolder.clearContext();
   repo.saveContext(contextAfterChainExecution, holder.getRequest(),
     holder.getResponse());
  }
 }
}

原本的方法很长,我这里列出来了比较关键的几个部分:

Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);
UsernamePasswordAuthenticationFilter

至此,整个流程就很明了了。

每一个请求到达服务端的时候,首先从 session 中找出来 SecurityContext ,然后设置到 SecurityContextHolder 中去,方便后续使用,当这个请求离开的时候,SecurityContextHolder 会被清空,SecurityContext 会被放回 session 中,方便下一个请求来的时候获取。

登录请求来的时候,还没有登录用户数据,但是登录请求走的时候,会将用户登录数据存入 session 中,下个请求到来的时候,就可以直接取出来用了。

看了上面的分析,我们可以至少得出两点结论:

  1. 如果我们暴露登录接口的时候,使用了前面提到的第一种方式,没有走 Spring Security,过滤器链,则在登录成功后,就不会将登录用户信息存入 session 中,进而导致后来的请求都无法获取到登录用户信息(后来的请求在系统眼里也都是未认证的请求)
  2. 如果你的登录请求正常,走了 Spring Security 过滤器链,但是后来的 A 请求没走过滤器链(采用前面提到的第一种方式放行),那么 A 请求中,也是无法通过 SecurityContextHolder 获取到登录用户信息的,因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。

3.小结

总之,前端静态资源放行时,可以直接不走 Spring Security 过滤器链,像下面这样:

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico");
}

后端的接口要额外放行,就需要仔细考虑场景了,不过一般来说,不建议使用上面这种方式,建议下面这种方式,原因前面已经说过了:

http.authorizeRequests()
        .antMatchers("/hello").permitAll()
        .anyRequest().authenticated()

原文 

https://www.geek521.com/2020/06/23/springsecurity两种资源放行策略/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » SpringSecurity两种资源放行策略

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址