Spring Security 多過濾鏈的使用詳解
一、背景
在我們實(shí)際的開發(fā)過程中,有些時(shí)候可能存在這么一些情況,某些api 比如: /api/**
這些是給App端使用的,數(shù)據(jù)的返回都是以JSON的格式返回,且這些API的認(rèn)證方式都是使用的TOKEN進(jìn)行認(rèn)證。而除了 /api/**
這些API之外,都是給網(wǎng)頁端使用的,需要使用表單認(rèn)證,給前端返回的
都是某個(gè)頁面。
二、需求
1、給客戶端使用的api
- 攔截
/api/**
所有的請求。 /api/**
的所有請求都需要ROLE_ADMIN
的角色。- 從請求頭中獲取
token
,只要獲取到token
的值,就認(rèn)為認(rèn)證成功,并賦予ROLE_ADMIN
到角色。 - 如果沒有權(quán)限,則給前端返回JSON對象
{message:"您無權(quán)限訪問"}
- 訪問
/api/userInfo
端點(diǎn)- 請求頭攜帶
token
可以訪問。 - 請求頭不攜帶
token
不可以訪問。
- 請求頭攜帶
2、給網(wǎng)站使用的api
- 攔截
所有的請求,但是不處理/api/**
開頭的請求。 - 所有的請求需要
ROLE_ADMIN
的權(quán)限。 - 沒有權(quán)限,需要使用表單登錄。
- 登錄成功后,訪問了無權(quán)限的請求,直接跳轉(zhuǎn)到百度去。
- 構(gòu)建2個(gè)內(nèi)建的用戶
- 用戶一: admin/admin 擁有 ROLE_ADMIN 角色
- 用戶二:dev/dev 擁有 ROLE_DEV 角色
- 訪問
/index
端點(diǎn)admin
用戶訪問,可以訪問。dev
用戶訪問,不可以訪問,權(quán)限不夠。
三、實(shí)現(xiàn)方案
方案一:
直接拆成多個(gè)服務(wù),其中 /api/**
的成為一個(gè)服務(wù)。非/api/**
的拆成另外一個(gè)服務(wù)。各個(gè)服務(wù)使用自己的配置,互不影響。
方案二
在同一個(gè)服務(wù)中編寫。不同的請求使用不同的SecurityFilterChain
來實(shí)現(xiàn)。
經(jīng)過考慮,此處采用
方案二
來實(shí)現(xiàn),因?yàn)榉桨敢缓唵?,使用方案二?shí)現(xiàn),也可以記錄下在同一個(gè)項(xiàng)目中 通過使用多條過濾器鏈,因?yàn)椴⒉皇撬械臅r(shí)候,都是可以分成多個(gè)項(xiàng)目的。
擴(kuò)展:
1、Spring Security SecurityFilterChain
的結(jié)構(gòu)
2、控制 SecurityFilterChain
的執(zhí)行順序
使用 org.springframework.core.annotation.Order
注解。
3、查看是怎樣選擇那個(gè) SecurityFilterChain
的
查看 org.springframework.web.filter.DelegatingFilterProxy#doFilter
方法
四、實(shí)現(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 的權(quán)限 .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:\":\"您無權(quán)訪問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:\":\"您無權(quán)訪問02\"}"); }) .and() // 用戶認(rèn)證 .addFilterBefore((request, response, chain) -> { // 此處可以模擬從 token 中解析出用戶名、權(quán)限等 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)站 應(yī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() // 所有請求都必須要認(rèn)證才可以訪問 .anyRequest() .hasRole("ADMIN") .and() // 禁用csrf .csrf() .disable() // 啟用表單登錄 .formLogin() .permitAll() .and() // 捕獲成功認(rèn)證后無權(quán)限訪問異常,直接跳轉(zhuǎn)到 百度 .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>
五、實(shí)現(xiàn)效果
1、app 有權(quán)限訪問 api
2、app 無權(quán)限訪問 api
3、admin 用戶有權(quán)限訪問 網(wǎng)站 api
4、dev 用戶無權(quán)限訪問 網(wǎng)站 api
訪問無權(quán)限的API直接跳轉(zhuǎn)到 百度 首頁。
六、完整代碼
https://gitee.com/huan1993/Spring-Security/tree/master/multi-security-filter-chain
到此這篇關(guān)于Spring Security 多過濾鏈的使用詳解的文章就介紹到這了,更多相關(guān)Spring Security 多過濾鏈 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用兩個(gè)棧實(shí)現(xiàn)隊(duì)列Java
這篇文章主要介紹了如何使用兩個(gè)棧實(shí)現(xiàn)隊(duì)列Java,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java基礎(chǔ)第二篇方法與數(shù)據(jù)成員
在上一篇文章中介紹了Java基礎(chǔ) 從HelloWorld到面向?qū)ο螅覀兂醪搅私饬藢ο?object)。對象中的數(shù)據(jù)成員表示對象的狀態(tài)。對象可以執(zhí)行方法,表示特定的動(dòng)作。這篇文章我們進(jìn)一步深入到對象。了解Java中方法與數(shù)據(jù)成員的一些細(xì)節(jié)。2021-09-09Spring超詳細(xì)講解事務(wù)和事務(wù)傳播機(jī)制
Spring事務(wù)的本質(zhì)就是對數(shù)據(jù)庫事務(wù)的支持,沒有數(shù)據(jù)庫事務(wù),Spring是無法提供事務(wù)功能的。Spring只提供統(tǒng)一的事務(wù)管理接口,具體實(shí)現(xiàn)都是由數(shù)據(jù)庫自己實(shí)現(xiàn)的,Spring會(huì)在事務(wù)開始時(shí),根據(jù)當(dāng)前設(shè)置的隔離級(jí)別,調(diào)整數(shù)據(jù)庫的隔離級(jí)別,由此保持一致2022-06-06SpringBoot中如何統(tǒng)一接口返回與全局異常處理詳解
全局異常處理是個(gè)比較重要的功能,一般在項(xiàng)目里都會(huì)用到,這篇文章主要給大家介紹了關(guān)于SpringBoot中如何統(tǒng)一接口返回與全局異常處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09輕松學(xué)會(huì)使用JavaMail?API發(fā)送郵件
想要輕松學(xué)會(huì)使用JavaMail?API發(fā)送郵件嗎?本指南將帶你快速掌握這一技能,讓你能夠輕松發(fā)送電子郵件,無論是個(gè)人還是工作需求,跟著我們的步驟,很快你就可以在Java應(yīng)用程序中自如地處理郵件通信了!2023-12-12springboot 定時(shí)任務(wù)@Scheduled實(shí)現(xiàn)解析
這篇文章主要介紹了springboot 定時(shí)任務(wù)@Scheduled實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java程序去調(diào)用并執(zhí)行shell腳本及問題總結(jié)(推薦)
這篇文章主要介紹了Java程序去調(diào)用并執(zhí)行shell腳本及問題總結(jié),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06