Spring Security 多過濾鏈的使用詳解
一、背景
在我們實際的開發(fā)過程中,有些時候可能存在這么一些情況,某些api 比如: /api/** 這些是給App端使用的,數(shù)據(jù)的返回都是以JSON的格式返回,且這些API的認證方式都是使用的TOKEN進行認證。而除了 /api/** 這些API之外,都是給網(wǎng)頁端使用的,需要使用表單認證,給前端返回的
都是某個頁面。
二、需求
1、給客戶端使用的api
- 攔截
/api/**所有的請求。 /api/**的所有請求都需要ROLE_ADMIN的角色。- 從請求頭中獲取
token,只要獲取到token的值,就認為認證成功,并賦予ROLE_ADMIN到角色。 - 如果沒有權限,則給前端返回JSON對象
{message:"您無權限訪問"} - 訪問
/api/userInfo端點- 請求頭攜帶
token可以訪問。 - 請求頭不攜帶
token不可以訪問。
- 請求頭攜帶
2、給網(wǎng)站使用的api
- 攔截
所有的請求,但是不處理/api/**開頭的請求。 - 所有的請求需要
ROLE_ADMIN的權限。 - 沒有權限,需要使用表單登錄。
- 登錄成功后,訪問了無權限的請求,直接跳轉到百度去。
- 構建2個內建的用戶
- 用戶一: admin/admin 擁有 ROLE_ADMIN 角色
- 用戶二:dev/dev 擁有 ROLE_DEV 角色
- 訪問
/index端點admin用戶訪問,可以訪問。dev用戶訪問,不可以訪問,權限不夠。
三、實現(xiàn)方案
方案一:
直接拆成多個服務,其中 /api/** 的成為一個服務。非/api/**的拆成另外一個服務。各個服務使用自己的配置,互不影響。
方案二
在同一個服務中編寫。不同的請求使用不同的SecurityFilterChain來實現(xiàn)。
經(jīng)過考慮,此處采用
方案二來實現(xiàn),因為方案一簡單,使用方案二實現(xiàn),也可以記錄下在同一個項目中 通過使用多條過濾器鏈,因為并不是所有的時候,都是可以分成多個項目的。
擴展:
1、Spring Security SecurityFilterChain 的結構

2、控制 SecurityFilterChain 的執(zhí)行順序
使用 org.springframework.core.annotation.Order 注解。
3、查看是怎樣選擇那個 SecurityFilterChain 的
查看 org.springframework.web.filter.DelegatingFilterProxy#doFilter方法
四、實現(xiàn)
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 的數(shù)據(jù)格式返回給前端
*/
@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、網(wǎng)站端 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;
/**
* 給 網(wǎng)站 應用的安全配置
*
* @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);
// 創(chuàng)建用戶
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();
}
/**
* 忽略靜態(tài)資源
*/
@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>
五、實現(xiàn)效果
1、app 有權限訪問 api

2、app 無權限訪問 api

3、admin 用戶有權限訪問 網(wǎng)站 api

4、dev 用戶無權限訪問 網(wǎng)站 api

訪問無權限的API直接跳轉到 百度 首頁。
六、完整代碼
https://gitee.com/huan1993/Spring-Security/tree/master/multi-security-filter-chain
到此這篇關于Spring Security 多過濾鏈的使用詳解的文章就介紹到這了,更多相關Spring Security 多過濾鏈 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot中如何統(tǒng)一接口返回與全局異常處理詳解
全局異常處理是個比較重要的功能,一般在項目里都會用到,這篇文章主要給大家介紹了關于SpringBoot中如何統(tǒng)一接口返回與全局異常處理的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-09-09
springboot 定時任務@Scheduled實現(xiàn)解析
這篇文章主要介紹了springboot 定時任務@Scheduled實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-09-09
Java程序去調用并執(zhí)行shell腳本及問題總結(推薦)
這篇文章主要介紹了Java程序去調用并執(zhí)行shell腳本及問題總結,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06

