Spring Security 两种资源放行策略,千万别用错了!(1)

文章详细解释了SpringSecurity中登录接口的处理过程,重点在于SecurityContextPersistenceFilter如何在请求中保存和传递用户信息,以及未通过过滤器链的请求将无法获取登录用户信息的情况。作者还提醒读者关于前端静态资源和接口权限管理的最佳实践。
摘要由CSDN通过智能技术生成

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

2.登录请求分析


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

  1. SecurityContextHolder.getContext().getAuthentication()

  2. 在 Controller 的方法中,加入 Authentication 参数

这两种办法,都可以获取到当前登录用户信息。具体的操作办法,大家可以看看松哥之前发布的教程:Spring Security 如何动态更新已登录用户信息?

这两种方式获取到的数据都是来自 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());

}

}

}

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

  1. SecurityContextPersistenceFilter 继承自 GenericFilterBean,而 GenericFilterBean 则是 Filter 的实现,所以 SecurityContextPersistenceFilter 作为一个过滤器,它里边最重要的方法就是 doFilter 了。

  2. 在 doFilter 方法中,它首先会从 repo 中读取一个 SecurityContext 出来,这里的 repo 实际上就是 HttpSessionSecurityContextRepository,读取 SecurityContext 的操作会进入到 readSecurityContextFromSession 方法中,在这里我们看到了读取的核心方法 Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);,这里的 springSecurityContextKey 对象的值就是 SPRING_SECURITY_CONTEXT,读取出来的对象最终会被转为一个 SecurityContext 对象。

  3. SecurityContext 是一个接口,它有一个唯一的实现类 SecurityContextImpl,这个实现类其实就是用户信息在 session 中保存的 value。

  4. 在拿到 SecurityContext 之后,通过 SecurityContextHolder.setContext 方法将这个 SecurityContext 设置到 ThreadLocal 中去,这样,在当前请求中,Spring Security 的后续操作,我们都可以直接从 SecurityContextHolder 中获取到用户信息了。

  5. 接下来,通过 chain.doFilter 让请求继续向下走(这个时候就会进入到 UsernamePasswordAuthenticationFilter 过滤器中了)。

  6. 在过滤器链走完之后,数据响应给前端之后,finally 中还有一步收尾操作,这一步很关键。这里从 SecurityContextHolder 中获取到 SecurityContext,获取到之后,会把 SecurityContextHolder 清空,然后调用 repo.saveContext 方法将获取到的 SecurityContext 存入 session 中。

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

每一个请求到达服务端的时候,首先从 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()

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
存中…(img-8EuR3qz6-1713302646491)]

[外链图片转存中…(img-Tn1OjwQa-1713302646491)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 26
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值