SpringSecurity如何放行资源

news/2024/11/8 12:05:07/

SpringSecurity配置放行资源

permitAll配置实例

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/css/**", "/js/**","/fonts/**").permitAll().anyRequest().authenticated();}
}

web ignore配置实例 

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/css/**");web.ignoring().antMatchers("/js/**");web.ignoring().antMatchers("/fonts/**");}
}

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

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

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

 

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

  1. SecurityContextHolder.getContext().getAuthentication()
  2. 在 Controller 的方法中,加入 Authentication 参数

这两种办法,都可以获取到当前登录用户信息。

这两种方式获取到的数据都是来自 SecurityContextHolderSecurityContextHolder 中的数据,本质上是保存在 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 过滤器链。

 

小结

总之,前端静态资源放行时,可以直接不走 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()

如果使用的是security6版本,以上两种方式稍有不同,如下

@Configuration
@EnableWebSecurity
public class MySecurityConfig {//配置不需要认证的资源路径@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(conf -> conf.requestMatchers(SystemConstants.HTTP_ACT_MATCHERS).permitAll().anyRequest()..authenticated()).build();}/*** 配置全局的某些通用事物,例如静态资源等* @return*/@Beanpublic WebSecurityCustomizer securityCustomizer() {return (web) -> web.ignoring().requestMatchers("/static/**");}}

如果项目是前后端分离项目,通过token交互的,我们一般会定义token过滤器,但是会出现SpringSecurity 配置permitAll之后仍然会走自定义过滤器Filter的问题,看到网上解决方案是将token过滤器filter不要注册为bean而是交给security管理并且配置WebSecurityCustomizer securityCustomizer(),代码如大体如下

@Configuration
@EnableWebSecurity
public class MySecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {//不需要认证的请求return http.authorizeHttpRequests(conf -> conf.requestMatchers("/login", "/info").permitAll().anyRequest().authenticated())//通过new JwtFilter()的方式,而不是bean组件注入的方式.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class).csrf(withDefaults()).cors(withDefaults()).build();}/*** 配置全局的某些通用事物,例如静态资源等* @return*/@Beanpublic WebSecurityCustomizer securityCustomizer() {return (web) -> web.ignoring().requestMatchers("/static/**");}
}

但是本地实测,放行的info和login还是会走token 过滤器,其实走token过滤器没啥影响,我们只需在token过滤器判断跳过主要token校验逻辑即可,token过滤器参考代码如下

@Component
public class JwtFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token");//放行if (token == null || request.getRequestURI().equals("info")){filterChain.doFilter(request, response);return;}try {String username = JwtUtils.getValue(token, "username");UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(User.withUsername(username), null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request, response);}catch (Exception e){e.printStackTrace();}}
}


http://www.ppmy.cn/news/1031525.html

相关文章

手机照片误删怎么办,电脑照片误删怎么办怎么才能找回,EasyRecovery来帮您

手机照片误删怎么办,电脑照片误删怎么办怎么才能找回,EasyRecovery 2023来帮您!!! EasyRecovery 2023是一款操作安全、价格便宜、用户自主操作的 数据恢复 方案,它支持从各种各样的 存储介质 恢复删除 或者…

opencv实战项目 手势识别-手势音量控制(opencv)

本项目是使用了谷歌开源的框架mediapipe,里面有非常多的模型提供给我们使用,例如面部检测,身体检测,手部检测等。 手势识别系列文章 1.opencv实现手部追踪(定位手部关键点) 2.opencv实战项目 实现手势跟踪…

Vue学习:创建Vue

一、安装Node.js和NPM 1.安装node.js 从node.js官网下载并安装node.js 网址: Download | Node.js 2.双击” node-v8.1.2-x64.msi”开始安装 至此Node.js已经安装完成,可以先进行下简单的测试安装是否成功了,后面还要进行环境配置在键盘按…

如何初始化Git仓库

如何将目录初始化为Git仓库 一级目录二级目录三级目录 一、准备1、安装 gh2、登录 二、初始化 Git 仓库 一级目录 二级目录 三级目录 一、准备 ​ 在这里,我们需要借助一个非常好用的工具,大家也可以参照官方文档进行阅读,下面介绍常用的…

IPv4分组

4.3.1 IPv4分组 IP协议定义数据传送的基本单元——IP分组及其确切的数据格式 1. IPv4分组的格式 IPv4分组由首部和数据部分(TCP、UDP段)组成,其中首部分为固定部分(20字节)和可选字段(长度可变&#xff0…

SpringCloud Alibaba——Nacos1.x配置中心长轮询机制

目录 一、Nacos1.x配置中心长轮询机制二、Nacos1.x配置中心长轮询机制流程图解 一、Nacos1.x配置中心长轮询机制 客户端会轮询向服务端发出一个长连接请求,这个长连接最多30s就会超时,服务端收到客户端的请求会先判断当前是否有配置更新,有则…

用Python实现百度AI接口对接,让你的程序更聪明

用Python实现百度AI接口对接,让你的程序更聪明 百度AI接口提供了丰富的人工智能服务,包括图像识别、文字识别、语音识别等功能。通过对接这些接口,我们可以让我们的程序具备更加智能的能力。本文将以Python为例,介绍如何使用百度AI接口实现一些常见的功能。 首先,我们需…

基于Java+SpringBoot+Vue的网吧管理系统设计与实现(源码+LW+部署文档等)

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…