本文來(lái)自于公眾號(hào)鏈接: 徹底理解瀏覽器同源策略SOP
- 多種認(rèn)證方式的優(yōu)先級(jí)問(wèn)題瘫镇,如何杜絕沖突的問(wèn)題
- 兩個(gè)示例的描述不清晰的問(wèn)題
這是一篇理論和實(shí)戰(zhàn)相結(jié)合的干貨文章,建議手機(jī)閱讀者收藏本文没讲,然后使用電腦下載源碼進(jìn)行實(shí)戰(zhàn)
大綱:
- 一.概述
- 二.源碼
- 三.示例1:接口分多組禽翼,每組認(rèn)證方式不同
- 四.示例2:同組接口同時(shí)支持多種認(rèn)證
- 五.總結(jié)
- 六.參考
一.概述
很多朋友雖然使用Spring Security做過(guò)很多項(xiàng)目捍壤,但是總是感覺(jué)沒(méi)有理解和掌握Spring Security的核心思想续誉。Spring Security比Shiro更強(qiáng)大更靈活的同時(shí),確實(shí)帶來(lái)了一定的復(fù)雜性扛吞。然而這種復(fù)雜性并不是因?yàn)镾pring Security設(shè)計(jì)得不好造成的呻惕,而是因?yàn)榘踩枨蟊旧砭拖褚粓F(tuán)亂麻一樣錯(cuò)綜復(fù)雜,Spring Security成功地將這團(tuán)亂麻梳理柔順后呈現(xiàn)給代價(jià)滥比。在解決復(fù)雜安全問(wèn)題場(chǎng)景下亚脆,Spring Security已經(jīng)足夠簡(jiǎn)潔了,我們更應(yīng)該關(guān)注Spring Security的靈活性盲泛。
認(rèn)證濒持、鑒權(quán)和漏洞防御是安全框架的核心功能。Spring Security的認(rèn)證功能強(qiáng)大并且非常靈活寺滚,支持的認(rèn)證方式也在不斷地水平擴(kuò)展柑营,已經(jīng)預(yù)置了非常多的認(rèn)證方式,如:
- Form認(rèn)證
最常用的(用戶名/密碼)認(rèn)證方式及其變體玛迄,如(手機(jī)號(hào)/驗(yàn)證碼)
插圖:用戶名密碼 - HTTP BASIC認(rèn)證
插圖:瀏覽器彈出框 - LDAP認(rèn)證
大型環(huán)境如企業(yè)用戶經(jīng)常采用LDAP - CAS認(rèn)證
單點(diǎn)登錄場(chǎng)景下常用解決方案 - HTTP Digest 認(rèn)證頭 ( IETF RFC-based 標(biāo)準(zhǔn))
- HTTP X.509 客戶端證書交換 ( IETF RFC-based 標(biāo)準(zhǔn))
- OpenID 認(rèn)證
- ......
如果預(yù)置的認(rèn)證仍然不滿足需求由境,Spring Security支持用戶自定義認(rèn)證方式。
除了認(rèn)證方式的豐富性蓖议,Spring Security對(duì)認(rèn)證的配置也非常靈活。Spring Security支持多種認(rèn)證方式自由組合讥蟆。
本文主要通過(guò)兩個(gè)代碼示例來(lái)展示Spring Security認(rèn)證的靈活性:
- 第一個(gè)配置示例: 接口分多組勒虾,每組認(rèn)證方式不同
- 第二個(gè)配置示例: 同組接口同時(shí)支持多種認(rèn)證
二.源碼
本文源碼維護(hù)在Github,非常具有參考價(jià)值瘸彤,建議下載源碼:
有兩個(gè)模塊:
插圖: twomodule
- spring-security-url-separate-sample: 本文展示Spring Security認(rèn)證的靈活性的配置示例修然。
- authorizationserver:自建的OAuth2認(rèn)證服務(wù)器。
三.示例1:接口分多組,每組認(rèn)證方式不同
假設(shè)一個(gè)web應(yīng)用的一部分接口是暴露在互聯(lián)網(wǎng)上提供服務(wù)API接口愕宋,而另一部分接口是只需內(nèi)部訪問(wèn)的內(nèi)部接口玻靡。我們常常把這個(gè)web應(yīng)用配置為OAuth2資源服務(wù)器,其中API接口采用OAuth2資源服務(wù)歐認(rèn)證中贝,內(nèi)部接口則采用傳統(tǒng)的認(rèn)證方式如Form認(rèn)證囤捻。
Spring Security支持這種“接口分多組,每組認(rèn)證方式不同”類型的需求邻寿。
1.新建Spring Boot工程
新建Spring Boot工程蝎土,命名為“spring-security-url-separate-sample”,依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-oauth2-resource-server是OAuth2資源服務(wù)器功能包绣否。
2.增加兩個(gè)Controller
FooController:
@RestController
public class FooController {
@GetMapping("/foo")
public String getSample() {
return "get foo";
}
}
和BarController:
@RestController
public class BarController {
@GetMapping("/bar")
public String getSample() {
return "get bar";
}
}
兩個(gè)Controller用來(lái)模擬兩組API接口誊涯。
3.兩個(gè)Spring Security配置類
GroupOneSecurityConfig配置所有以“/foo”開頭的接口使用OAuth2資源服務(wù)進(jìn)行認(rèn)證和鑒權(quán):
@Configuration
//此處配置Order=1,因而比 GroupTwoSecurityConfig 配置類先執(zhí)行蒜撮,配置優(yōu)先級(jí)高暴构,因?yàn)镾pring Security的配置規(guī)則是"先執(zhí)行的優(yōu)先級(jí)高"。
@Order(1)
public class GroupOneSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/foo/**")
.and().authorizeRequests().anyRequest().fullyAuthenticated()
.and().oauth2ResourceServer().jwt();
}
}
GroupTwoSecurityConfig配置除了以“/foo”開頭的接口外剩余的其他接口使用From認(rèn)證:
@EnableWebSecurity(debug = true)
public class GroupTwoSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/error", "/login**").permitAll()
.anyRequest().fullyAuthenticated()
.and().formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN", "USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
GroupTwoSecurityConfig類配置的接口選擇范圍為“所有接口”段磨,包含了GroupOneSecurityConfig選擇的以“/foo”開頭的接口取逾,但是不用擔(dān)心兩個(gè)配置類的范圍沖突問(wèn)題,因?yàn)槲覀冊(cè)?GroupOneSecurityConfig上增加了@Order(1)注解薇溃。@Order表示執(zhí)行優(yōu)先級(jí)菌赖,WebSecurityConfigurerAdapter默認(rèn)的優(yōu)先級(jí)是100:
插圖:order
Spring Security的@Order規(guī)則是:“數(shù)字越小,越先執(zhí)行沐序,并且先執(zhí)行的配置優(yōu)先級(jí)高”琉用。
@Order(1)小于默認(rèn)的@Order(100),所以GroupOneSecurityConfig的接口選擇“/foo”的優(yōu)先級(jí)高策幼、先生效邑时,而GroupTwoSecurityConfig只能選擇到除“/foo”以外剩余的接口。
工程的接口分為了兩組:
- 以“/foo”開頭的接口特姐,使用OAuth2資源服務(wù)認(rèn)證
- 除“/foo”開頭接口外剩余的接口晶丘,使用From認(rèn)證。
注意GroupTwoSecurityConfig要配置“/error”和“/login**”為permitAll()
4.配置文件
server:
servlet:
session:
cookie:
name: UISESSIONCOUPON
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9999/.well-known/jwks.json
自建OAuth2認(rèn)證服務(wù)器和本工程都以localhost啟動(dòng)唐含,配置不同的cookie名稱浅浮,防止沖突報(bào)錯(cuò),并且使用JWK接口驗(yàn)證 JWT Token 的有效性捷枯。
5.運(yùn)行與演示
假設(shè)spring-security-url-separate-sample是一個(gè)在互聯(lián)網(wǎng)提供API接口的服務(wù)器滚秩,其中“/foo”接口暴露在互聯(lián)網(wǎng)提供服務(wù)的API接口,而 “/bar”接口為內(nèi)部接口淮捆,則有如下訪問(wèn)場(chǎng)景:
-
內(nèi)部接口可以直接使用Form認(rèn)證:
- 啟動(dòng)“spring-security-url-separate-sample”
- 隱身模式打開瀏覽器郁油,輸入:http://localhost:8080/bar
插圖:formlogin - 輸入用戶名/密碼:admin/123456本股,可以登錄成功
- 瀏覽器再輸入:http://localhost:8080/foo ,也可以成功返回字符串“get foo”桐腌。
用戶使用Form登錄后既可以訪問(wèn)API接口也可以訪問(wèn)內(nèi)部接口拄显。
-
暴露在互聯(lián)網(wǎng)的API接口只能使用OAuth2資源服務(wù)認(rèn)證:隨便來(lái)自于互聯(lián)網(wǎng)的用戶不能獲得內(nèi)部的用戶名和密碼
啟動(dòng)“spring-security-url-separate-sample”
啟動(dòng)“sauthorizationserver”
隱身模式打開瀏覽器,輸入:http://localhost:8080/foo
插圖:foo401
未認(rèn)證時(shí)案站,OAuth2資源服務(wù)認(rèn)證不會(huì)自動(dòng)重定向到認(rèn)證中心躬审,而是顯示要401錯(cuò)誤嘶朱。調(diào)用
“/foo”接口是需要攜帶OAuth2的access token的跺涤。-
使用curl命令行工具模擬OAuth2請(qǐng)求,使用密碼模式獲取一個(gè)access token:
curl -i -X POST -d "username=admin&password=admin&grant_type=password&client_id=client-for-server&client_secret=client-for-server" http://localhost:9999/oauth/token
插圖:gettoken
-
攜帶access_token請(qǐng)求“/foo”接口:
curl -i -H "Authorization:Bearer {此處粘貼上一步驟返回的access_token}" http://localhost:8080/foo
插圖:foosuccess
外部用戶成功調(diào)用“/foo”接口昂灵,但是仍然無(wú)法調(diào)用到內(nèi)部“/bar”接口舱禽。內(nèi)部接口”被成功地保護(hù)起來(lái)了炒刁。
此示例將應(yīng)用接口分為兩組,分別使用不同的認(rèn)證方式誊稚。理論上Spring Security可以將接口分為任意多組翔始,使用任意多種認(rèn)證方式,非常靈活里伯。
四.示例2:同組接口同時(shí)支持多種認(rèn)證
除了多組接口使用不同的認(rèn)證方式外城瞎。對(duì)于同一組接口,可以同時(shí)支持多種認(rèn)證方式疾瓮。
我們繼續(xù)對(duì)“spring-security-url-separate-sample”進(jìn)行擴(kuò)展脖镀。
1.增加OAuth2 Client依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
2.在GroupTwoSecurityConfig中增加配置
@Override
public void configure(HttpSecurity http) throws Exception {
...
.and().formLogin()
.and().oauth2Login();
}
對(duì)于除“/foo"以外的其他接口同時(shí)支持Form認(rèn)證和OAuth2客戶端認(rèn)證。
3.配置文件增加配置
spring:
security:
oauth2:
client:
registration:
github:
client-id: b7fb29a538bb19b09365
client-secret: 2fbfd22e69e61d873bad55143538770748a76d3a
custom:
client-id: client-for-server
client-secret: client-for-server
provider: custom
client-name: 自建OAuth2認(rèn)證服務(wù)
authorization-grant-type: authorization_code
redirect-uri: "http://localhost:8080/login/oauth2/code/custom"
provider:
custom:
authorization-uri: http://localhost:9999/oauth/authorize
token-uri: http://localhost:9999/oauth/token
user-info-uri: http://localhost:9999/me
user-name-attribute: "name"
jwk-set-uri: http://localhost:9999/.well-known/jwks.json
配置兩個(gè)OAuth2客戶端狼电,一個(gè)是Github的OAuth2的客戶端蜒灰,另一是自建OAuth2認(rèn)證服務(wù)器的客戶端。
4.運(yùn)行與演示
對(duì)于“/bar”接口肩碟,同時(shí)支持Form認(rèn)證和OAuth2客戶端認(rèn)證强窖。
- 啟動(dòng)“spring-security-url-separate-sample”
- 啟動(dòng)“sauthorizationserver”
- 隱身模式打開瀏覽器,輸入:http://localhost:8080/bar
插圖:oauth2login
此時(shí)我們有三種選擇:- 輸入用戶名/密碼:admin/123456登錄成功削祈。
- 點(diǎn)擊“Github”藍(lán)色按鈕翅溺,使用Github賬號(hào)登錄成功。
- 點(diǎn)擊“自建OAuth2認(rèn)證服務(wù)”藍(lán)色按鈕髓抑,使用自建OAuth2認(rèn)證服務(wù)咙崎,輸入用戶名/密碼;admin/admin吨拍,登錄成功叙凡。
插圖: customas
此示例演示了Spring Security支持同組接口同時(shí)支持多種認(rèn)證。
五.總結(jié)
spring-security-url-separate-sample整體來(lái)看同時(shí)支持了Form密末、GitHub認(rèn)證握爷、自建OAuth2認(rèn)證服務(wù)客戶端認(rèn)證和自建OAuth2認(rèn)證服務(wù)資源服務(wù)認(rèn)證四種認(rèn)證方式,并且不互相沖突严里。每種認(rèn)證方式都有各自的應(yīng)用場(chǎng)景新啼。
Spring Security實(shí)現(xiàn)之所以可以實(shí)現(xiàn)如此靈活的認(rèn)證配置,正是因?yàn)椤鞍葱柩b配”這一核心思想刹碾。
首先無(wú)論Spring Security應(yīng)用在Servlet還是響應(yīng)式(Reactive)場(chǎng)景燥撞,都是一種AOP切面原理。
插圖:authenticaiton
其次一個(gè)web應(yīng)用根據(jù)需求迷帜,需要使用哪種認(rèn)證方式就裝配哪種物舒。
插圖;asneed
總之戏锹,Spring Security除了支持漏洞防御冠胯,針對(duì)認(rèn)證和鑒權(quán)的需求做了的非常強(qiáng)大且靈活的設(shè)計(jì)。
本文的相關(guān)源碼上傳到了Github锦针,地址:https://github.com/andyzhaozhao/spring-security-url-separate
如果有任何問(wèn)題和建議荠察,可以右下角點(diǎn)贊后評(píng)論,我們會(huì)第一時(shí)間回復(fù)奈搜。
六.參考
更多干貨都在《spring security實(shí)戰(zhàn)》
本為官方出處微信公眾號(hào): 碼聞