Spring Security 多过滤链的使用详解
目录
- 一、背景
- 二、需求
- 1、给客户端使用的api
- 2、给网站使用的api
- 三、实现方案
- 方案一:
- 方案二
- 四、实现
- 1、app 端 Spring Security 的配置
- 五、实现效果
- 1、app 有权限访问 api
- 2、app 无权限访问 api
- 3、admin 用户有权限访问 网站 api
- 4、dev 用户无权限访问 网站 api
- 六、完整代码
一、背景
在我们实际的开发过程中,有些时候可能存在这么一些情况,某些api 比如: /api/**
这些是给App端使用的,数据的返回都是以JSON的格式返回,且这些API的认证方式都是使用的TOKEN进行认证。而除了 /api/**
这些API之外,都是给网页端使用的,需要使用表单认证,给前端返回的
都是某个页面。
二、需求
1、给客户端使用的api
- 拦截
/api/**
所有的请求。 /api/**
的所有请求都需要ROLE_ADMIN
的角色。- 从请求头中获取
token
,只要获取到token
的值,就认为认证成功,并赋予ROLE_ADMIN
到角色。 - 如果没有权限,则给前端返回JSON对象
{message:"您无权限访问"}
- 访问
/api/userInfo
端点- 请求头携带
token
可以访问。 - 请求头不携带
token
不可以访问。
- 请求头携带
2、给网站使用的api
- 拦截
所有的请求,但是不处理/api/**
开头的请求。 - 所有的请求需要
ROLE_ADMIN
的权限。 - 没有权限,需要使用表单登录。
- 登录成功后,访问了无权限的请求,直接跳转到百度去。
- 构建2个内建的用户
- 用户一: admin/admin 拥有 ROLE_ADMIN 角色
- 用户二:dev/dev 拥有 ROLE_DEV 角色
- 访问
/index
端点admin
用户访问,可以访问。dev
用户访问,不可以访问,权限不够。
三、实现方案
方案一:
直接拆成多个服务,其中 /api/**
的成为一个服务。非/api/**
的拆成另外一个服务。各个服务使用自己的配置,互不影响。
方案二
在同一个服务中编写。不同的请求使用不同的SecurityFilterChain
来实现。
经过考虑,此处采用
方案二
来实现,因为方案一简单,使用方案二实现,也可以记录下在同一个项目中 通过使用多条过滤器链,因为并不是所有的时候,都是可以分成多个项目的。
扩展:
1、Spring Security SecurityFilterChain
的结构
2、控制 SecurityFilterChain
的执行顺序
使用 org.springframework.core.annotation.Order
注解。
3、查看是怎样选择那个 SecurityFilterChain
的
查看 org.springframework.web.filter.DelegatingFilterProxy#doFilter
方法
四、实现
1、app 端 Spring Security 的配置
package com.huan.study.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; /** * 给 app 端用的 Security 配置 * * @author huan.fu 2021/7/13 - 下午9:06 */ @Configuration public class AppSecurityConfig { /** * 处理 给 app(前后端分离) 端使用的过滤链 * 以 json 的数据格式返回给前端 */ @Bean @Order(1) public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception { // 只处理 /api 开头的请求 return http.antMatcher("/api/**") .authorizeRequests() // 所有以 /api 开头的请求都需要 ADMIN 的权限 .antMatchers("/api/**") .hasRole("ADMIN") .and() // 捕获到异常,直接给前端返回 json 串 .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON.toString()); response.getWriter().write("{\"message:\":\"您无权访问01\"}"); }) .accessDeniedHandler((request, response, accessDeniedException) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON.toString()); response.getWriter().write("{\"message:\":\"您无权访问02\"}"); }) .and() // 用户认证 .addFilterBefore((request, response, chain) -> { // 此处可以模拟从 token 中解析出用户名、权限等 String token = ((HttpServletRequest) request).getHeader("token"); if (!StringUtils.hasText(token)) { chain.doFilter(request, response); return; } Authentication authentication = new TestingAuthenticationToken(token, null, AuthorityUtils.createAuthorityList("ROLE_ADMIN")); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); }, UsernamePasswordAuthenticationFilter.class) .build(); } }
2、网站端 Spring Secuirty 的配置
package com.huan.study.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; /** * 给 网站 应用的安全配置 * * @author huan.fu 2021/7/14 - 上午9:09 */ @Configuration public class WebSiteSecurityFilterChainConfig { /** * 处理 给 webSite(非前后端分离) 端使用的过滤链 * 以 页面 的格式返回给前端 */ @Bean @Order(2) public SecurityFilterChain webSiteSecurityFilterChain(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); // 创建用户 authenticationManagerBuilder.inMemoryAuthentication() .withUser("admin") .password(new BCryptPasswordEncoder().encode("admin")) .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN")) .and() .withUser("dev") .password(new BCryptPasswordEncoder().encode("dev")) .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_DEV")) .and() .passwordEncoder(new BCryptPasswordEncoder()); // 只处理 所有 开头的请求 return http.antMatcher("/**") .authorizeRequests() // 所有请求都必须要认证才可以访问 .anyRequest() .hasRole("ADMIN") .and() // 禁用csrf .csrf() .disable() // 启用表单登录 .formLogin() .permitAll() .and() // 捕获成功认证后无权限访问异常,直接跳转到 百度 .exceptionHandling() .accessDeniedHandler((request, response, exception) -> { response.sendRedirect("http://www.baidu.com"); }) .and() .build(); } /** * 忽略静态资源 */ @Bean public WebSecurityCustomizer webSecurityCustomizer( ){ return web -> web.ignoring() .antMatchers("/**/js/**") .antMatchers("/**/css/**"); } }
3、控制器写法
/** * 资源控制器 * * @author huan.fu 2021/7/13 - 下午9:33 */ @Controller public class ResourceController { /** * 返回用户信息 */ @GetMapping("/api/userInfo") @ResponseBody public Authentication showUserInfoApi() { return SecurityContextHolder.getContext().getAuthentication(); } @GetMapping("/index") public String index(Model model){ model.addAttribute("username","张三"); return "index"; } }
4、引入jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
五、实现效果
1、app 有权限访问 api
2、app 无权限访问 api
3、admin 用户有权限访问 网站 api
4、dev 用户无权限访问 网站 api
访问无权限的API直接跳转到 百度 首页。
六、完整代码
https://gitee.com/huan1993/Spring-Security/tree/master/multi-security-filter-chain
到此这篇关于Spring Security 多过滤链的使用详解的文章就介绍到这了,更多相关Spring Security 多过滤链 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!