在前一篇文章中,我们介绍了 Spring Security 的启动流程和配置流程,在介绍 WebSecurity 时,我们提到可以创建多个 WebSecurityConfigurerAdapter 的子类实例,也就会创建多条过滤器链,今天我们就来简单了解一下如何配置多个过滤器链。
1 简单配置
配置方法很简单,我们直接上代码:
创建一个配置类,再在该类中创建几个 WebSecurityConfigurerAdapter 的子类,并给这些类加上 @Order 和 @Configuration 注解:
MultipleWebSecurityConfig.java:
@Configuration
public class MultipleWebSecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//用户信息
@Autowired
protected void authUserInfo(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("llk")
.password(passwordEncoder().encode("1234"))
.authorities("USER")
.and()
.withUser("zhangsan")
.password(passwordEncoder().encode("123456"))
.authorities("USER");
}
@Order(1)
@Configuration
class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/book/delete").hasAuthority("ADMIN")
.antMatchers("/admin/book/update").hasAnyAuthority("ADMIN")
.anyRequest().authenticated();
;
}
}
@Order(2)
@Configuration
class CommonSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/common/**")
.csrf().disable()
.authorizeRequests()
.antMatchers("/common/book/get").hasAuthority("USER")
.anyRequest().authenticated();
;
}
}
}
需要给 http 指定一个 **antMatcher() 值 /admin 开头的请求将交给 AdminSecurityConfig 对应的过滤器链,以 /common 开头的请求将交给 CommonSecurityConfig 对应的过滤器链。
下面我们还是以书籍管理系统的接口来测试一下。
我们这里只是做一个简单的例子,普通用户具有 USER 权限,请求以 /common 开头;管理员用户具有 ADMIN 和 USER 权限,请求以 /admin 开头。
BookController.java:
@RestController
public class BookController {
@RequestMapping("/common/book/get")
public Book get(){
Book book = new Book();
book.setBookId("1");
book.setBookName("《Thinking in Java》");
book.setAuthor("Bruce Eckel");
return book;
}
@RequestMapping("/admin/book/delete")
public String delete(){
//模拟删除书籍的代码
System.out.println("删除书籍");
return "删除成功";
}
我们在浏览器输入访问书籍信息的 URL:http://localhost:8080/common/book/get ,然后回车,却发现没有显示我们的书籍信息,而是显示 403 。
出现这个的原因,是因为我们还没有登录,没有权限访问。
那么我们就去登录,照以前一样,输入 Spring Security 默认的登录界面地址:http://localhost:8080/login ,访问后我们发现报 404 错误了。
报 404 的原因:大家看一下在上一篇文章中介绍的一个图:
上图中清晰的展示了,当一个请求到 FilterChainProxy(DelegatingFilterProxy ) 时,Spring Security 会去判断当前请求应该由哪一条过滤器链来处理,由于我们前面的配置中只配置了处理以 /admin 和以 /common 开头的请求,所以当我们访问 /login 时就被 FilterChainProxy 抛弃了,因为没有与 /login 对应的过滤器链。
为解决这个问题,我们可以为每一个过滤器链都配置 formLogin() 相关的信息,具体配置我们这里不极少了,在第一篇文章中已经介绍过。
一般情况下,我们再配置一条过滤器链,拦截除了以 /admin 和 /common 以外的请求,具体的情况根据你的业务需求来定。
在 MultipleWebSecurityConfig.java 中添加如下配置:
@Order(3)
@Configuration
class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
//表单登录相关
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated();
;
}
}
该配置是向 FilterChainProxy 中添加了一个拦截路径为根路径 / 的过滤器链,即该过滤器链可以拦截所有的请求。
到此为止,配置多个过滤器链的功能,就算完成,对于每一条过滤器链要应用哪些过滤器,通过 httpSecurity 参数去配置即可,这里我们就不做介绍了。
2 注意顺序
上面的代码中,我们在 WebSecurityConfigurerAdapter 的三个子类上都添加了一个 @Order 注解,在上一篇文章中,我们在介绍 WebSecurityConfiguration 时,创建 webSecurity 对象后,对 WebSecurityConfigurerAdapter 的子类进行了由小到大的排序,然后将这些 WebSecurityConfigurerAdapter 添加到了 webSecurityConfigurers 集合中,最后在 WebSecurity 的 performBuild() 方法中循环遍历 securityFilterChainBuilders 获取配置信息并创建过滤器链,创建好的过滤器链被添加到一个 ArrayList 类型的 securityFilterChains 集合中,再设置给了 filterChainProxy 对象。
当客户端发来一个请求时,执行到 FilterChainProxy 的 doFilterInternal() 方法,在该方法中将循环遍历 securityFilterChains 中的过滤器链然后与请求地址进行匹配,如果某个过滤器链匹配到了,就执行该过滤器链。
所以,我们就得注意 @Order 的顺序了,值越小优先级越高,不标注 @Order 注解的优先级最高。谁的优先级越高,它对应的过滤器链就先被 FilterChainProxy 匹配,如果该过滤器链的匹配地址与请求地址一致则执行该过滤器链,否则继续检查下一个过滤器链是否匹配。
2.1 顺序验证
我们拿前面的 AdminSecurityConfig 和 OtherSecurityConfig 为例来说明配置之间的顺序的重要性。
在上面的代码中,AdminSecurityConfig 和 OtherSecurityConfig 的 @Order 注解的值分别为 1 和 3,所以此时是先匹配 AdminSecurityConfig 对应的过滤器链。
此时如果我们直接在未登录的情况下访问 http://localhost:8080/admin/book/delete ,会告诉我们 403,没有权限访问,至于原因我们也知道了,已 /admin/ 开头的请求直接就被 AdminSecurityConfig 对应的过滤器链给执行了,然后在权限验证的时候,访问的用户没有 ADMIN 权限,所以就报 403 错误了,这个配置我们认为是安全的,因为我们拦住了没有登录的用户(没有 ADMIN 权限的登录用户也会被拦住)。
那么接下来,我们将 AdminSecurityConfig 和 OtherSecurityConfig 的 @Order 的值改为 3 和 1,这时 OtherSecurityConfig 对应的过滤器链会优先被匹配。由于 OtherSecurityConfig 的默认匹配路径是根路径,所以任何请求都会由它对应的过滤器链来处理,当我们在浏览器中输入 http://localhost:8080/admin/book/delete 地址后,我们用没有 ADMIN 权限的用户登录后,可以直接访问到删除书籍的接口,这显然不是我们想要的。至于原因大家应该也已经很清楚了,因为在我们的 OtherSecurityConfig 中并没有配置接口的相关权限信息(我们在 AdminSecurityConfig 中配置了,但是没有执行对应的过滤器链,所以没有拦截到没有权限的用户)。
所以,大家如果想对不同的路径使用不同的配置时,一定要小心 @Order 的顺序,要保证自己的配置能够被应用上。
本篇文章的内容只是上一篇文章的一个延续,帮助大家更加深入的理解 Spring Security 的配置流程。
3 示例代码
示例代码地址:https://github.com/coderllk/spring-security-oauth2-demos