javascript
可能是第二好的 Spring OAuth 2.0 文章,艿艿端午在家写了 3 天~
本文在提供完整代碼示例,可見 https://github.com/YunaiV/SpringBoot-Labs 的 lab-68-spring-security-oauth 目錄。
原創(chuàng)不易,給點(diǎn)個(gè) Star 嘿,一起沖鴨!
1. 概述
在《芋道 Spring Boot 安全框架 Spring Security 入門》文章中,艿艿分享了如何使用 Spring Security 實(shí)現(xiàn)認(rèn)證與授權(quán)的功能,獲得廣大女粉絲的好評(píng)。
于是乎,艿艿準(zhǔn)備再來分享一波 Spring Security OAuth 框架,看看在 Spring Security 如何實(shí)現(xiàn) OAuth2.0 實(shí)現(xiàn)授權(quán)的功能。
“旁白君:實(shí)際上艿艿很早寫了一篇關(guān)于 Spring Security OAuth 的文章,考慮到版本太老,提供的示例又過于簡(jiǎn)單,所以本文也是該文章的升級(jí)版。
可能有胖友對(duì) OAuth2.0 不是很了解,所以我們先來簡(jiǎn)單介紹下它。可能胖友看 OAuth2.0 的概念會(huì)有點(diǎn)懵逼,不要擔(dān)心,后續(xù)看完艿艿提供的示例代碼,會(huì)突然清晰的哈。
另外,阮一峰提供了幾篇關(guān)于 OAuth2.0 非常不錯(cuò)的文章,推薦胖友去從瞅瞅。同時(shí),本文也會(huì)直接引用它的內(nèi)容,方便胖友統(tǒng)一理解。
-
《理解 OAuth2.0》
-
《OAuth2.0 的一個(gè)簡(jiǎn)單解釋》
-
《OAuth2.0 的四種方式》
-
《GitHub OAuth 第三方登錄示例教程》
1.1 OAuth2.0 是什么?
“FROM 《維基百科 —— 開放授權(quán)》
OAuth(Open Authorization)是一個(gè)開放標(biāo)準(zhǔn),允許用戶讓第三方應(yīng)用訪問該用戶在某一網(wǎng)站上存儲(chǔ)的私密的資源(如照片,視頻,聯(lián)系人列表),而無需將用戶名和密碼提供給第三方應(yīng)用。
“旁白君:很多團(tuán)隊(duì),內(nèi)部會(huì)采用 OAuth2.0 實(shí)現(xiàn)一個(gè)授權(quán)服務(wù),避免每個(gè)上層應(yīng)用或者服務(wù)重復(fù)開發(fā)。
OAuth 允許用戶提供一個(gè)令牌,而不是用戶名和密碼來訪問他們存放在特定服務(wù)提供者的數(shù)據(jù)。
每一個(gè)令牌授權(quán)一個(gè)特定的網(wǎng)站(例如,視頻編輯網(wǎng)站)在特定的時(shí)段(例如,接下來的 2 小時(shí)內(nèi))內(nèi)訪問特定的資源(例如僅僅是某一相冊(cè)中的視頻)。這樣,OAuth 讓用戶可以授權(quán)第三方網(wǎng)站訪問他們存儲(chǔ)在另外服務(wù)提供者的某些特定信息,而非所有內(nèi)容。
“旁白君:如果胖友對(duì)接過微信網(wǎng)頁授權(quán)功能,就會(huì)發(fā)現(xiàn)分成兩種方式:靜默授權(quán)、手動(dòng)授權(quán)。前者只能獲取到用戶的?openid,而后者可以獲取到用戶的基本信息。
OAuth2.0 是用于授權(quán)的行業(yè)標(biāo)準(zhǔn)協(xié)議。OAuth2.0 為簡(jiǎn)化客戶端開發(fā)提供了特定的授權(quán)流,包括 Web 應(yīng)用、桌面應(yīng)用、移動(dòng)端應(yīng)用等。
“旁白君:OAuth 1.0 協(xié)議體系本身存在一些問題,現(xiàn)已被各大開發(fā)平臺(tái)逐漸廢棄。
1.2 OAuth2.0 角色解釋
在 OAuth2.0 中,有如下角色:
①?Authorization?Server:認(rèn)證服務(wù)器,用于認(rèn)證用戶。如果客戶端認(rèn)證通過,則發(fā)放訪問資源服務(wù)器的令牌。
②?Resource?Server:資源服務(wù)器,擁有受保護(hù)資源。如果請(qǐng)求包含正確的訪問令牌,則可以訪問資源。
“友情提示:提供管理后臺(tái)、客戶端 API 的服務(wù),都可以認(rèn)為是 Resource Server。
③?Client:客戶端。它請(qǐng)求資源服務(wù)器時(shí),會(huì)帶上訪問令牌,從而成功訪問資源。
“友情提示:Client 可以是瀏覽器、客戶端,也可以是內(nèi)部服務(wù)。
④ Resource?Owner:資源擁有者。最終用戶,他有訪問資源的賬號(hào)與密碼。
“友情提示:可以簡(jiǎn)單把 Resource Owner 理解成人,她在使用 Client 訪問資源。
1.3 OAuth 2.0 運(yùn)行流程
如下是 OAuth 2.0 的授權(quán)碼模式的運(yùn)行流程:
OAuth 2.0 運(yùn)行流程?
“-
(A)用戶打開客戶端以后,客戶端要求用戶給予授權(quán)。
-
(B)用戶同意給予客戶端授權(quán)。
-
(C)客戶端使用上一步獲得的授權(quán),向認(rèn)證服務(wù)器申請(qǐng)令牌。
-
(D)認(rèn)證服務(wù)器對(duì)客戶端進(jìn)行認(rèn)證以后,確認(rèn)無誤,同意發(fā)放令牌。
-
(E)客戶端使用令牌,向資源服務(wù)器申請(qǐng)獲取資源。
-
(F)資源服務(wù)器確認(rèn)令牌無誤,同意向客戶端開放資源。
上述的六個(gè)步驟,B 是關(guān)鍵,即用戶如何給客戶端進(jìn)行授權(quán)。有了授權(quán)之,客戶端就可以獲取令牌,進(jìn)而憑令牌獲取資源。
“友情提示:如果胖友有對(duì)接過三方開放平臺(tái),例如說微信、QQ、微博等三方登錄,就會(huì)很容易理解這個(gè)步驟過程。
這個(gè)時(shí)候的資源,資源主要指的是三方開放平臺(tái)的用戶資料等等。
1.4 OAuth 2.0 授權(quán)模式
客戶端必須得到用戶的授權(quán)(Authorization Grant),才能獲得訪問令牌(Access Token)。
OAuth2.0 定義了四種授權(quán)方式:
-
授權(quán)碼模式(Authorization Code)
-
密碼模式(Resource Owner Password Credentials)
-
簡(jiǎn)化模式(Implicit)
-
客戶端模式(Client Credentials)
其中,密碼模式和授權(quán)碼模式比較常用。至于如何選擇,艿艿這里先提前劇透下,后續(xù)慢慢細(xì)品。
“FROM 《深度剖析 OAuth2 和微服務(wù)安全架構(gòu)》
授權(quán)類型選擇?
當(dāng)然,對(duì)于黃框部分,對(duì)于筆者還是比較困惑的。筆者認(rèn)為,第三方的單頁應(yīng)用 SPA ,也是適合采用 Authorization Code Grant 授權(quán)模式的。例如,《微信網(wǎng)頁授權(quán)》 :
“具體而言,網(wǎng)頁授權(quán)流程分為四步:
-
1、引導(dǎo)用戶進(jìn)入授權(quán)頁面同意授權(quán),獲取 code
-
2、通過 code 換取網(wǎng)頁授權(quán) access_token(與基礎(chǔ)支持中的 access_toke n不同)
-
3、如果需要,開發(fā)者可以刷新網(wǎng)頁授權(quán) access_token,避免過期
-
4、通過網(wǎng)頁授權(quán) access_token 和 openid 獲取用戶基本信息(支持 UnionID 機(jī)制)
所以,艿艿猜測(cè),之所以圖中畫的是 Implicit Grant 的原因是,受 Google 的 《OAuth 2.0 for Client-side Web Applications》 一文中,推薦使用了 Implicit Grant 。
當(dāng)然,具體使用 Implicit Grant 還是 Authorization Code Grant 授權(quán)模式,沒有定論。筆者,偏向于使用?Authorization Code Grant,對(duì)于第三方客戶端的場(chǎng)景。
2. 密碼模式
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo02-authorization-server-with-resource-owner-password-credentials
-
資源服務(wù)器:lab-68-demo02-resource-server
本小節(jié),我們來學(xué)習(xí)密碼模式(Resource Owner Password Credentials Grant)。
密碼模式,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向授權(quán)服務(wù)器索要授權(quán)。
在這種模式中,用戶必須把自己的密碼給客戶端,但是客戶端不得儲(chǔ)存密碼。這通常用在用戶對(duì)客戶端高度信任的情況下,比如客戶端是操作系統(tǒng)的一部分,或者由一個(gè)著名公司出品。而授權(quán)服務(wù)器只有在其他授權(quán)模式無法執(zhí)行的情況下,才能考慮使用這種模式。
“旁白君:如果客戶端和授權(quán)服務(wù)器都是自己公司的,顯然符合。
密碼模式?
“-
(A)用戶向客戶端提供用戶名和密碼。
-
(B)客戶端將用戶名和密碼發(fā)給授權(quán)服務(wù)器,向后者請(qǐng)求令牌。
-
(C)授權(quán)服務(wù)器確認(rèn)無誤后,向客戶端提供訪問令牌。
下面,我們來新建兩個(gè)項(xiàng)目,搭建一個(gè)密碼模式的使用示例。如下圖所示:
項(xiàng)目結(jié)構(gòu)?
-
lab-68-demo02-authorization-server-with-resource-owner-password-credentials:授權(quán)服務(wù)器。
-
lab-68-demo02-resource-server:資源服務(wù)器。
2.1 搭建授權(quán)服務(wù)器
創(chuàng)建?lab-68-demo02-authorization-server-with-resource-owner-password-credentials?項(xiàng)目,搭建授權(quán)服務(wù)器。
2.1.1 引入依賴
創(chuàng)建?pom.xml?文件,引入 Spring Security OAuth 依賴。
<?xml?version="1.0"?encoding="UTF-8"?> <project?xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>lab-68</artifactId><groupId>cn.iocoder.springboot.labs</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-68-demo02-authorization-server-with-resource-owner-password-credentials</artifactId><properties><!--?依賴相關(guān)配置?--><spring.boot.version>2.2.4.RELEASE</spring.boot.version><!--?插件相關(guān)配置?--><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.source>1.8</maven.compiler.source></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!--?實(shí)現(xiàn)對(duì)?Spring?MVC?的自動(dòng)配置?--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--?實(shí)現(xiàn)對(duì)?Spring?Security?OAuth2?的自動(dòng)配置?--><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>${spring.boot.version}</version></dependency></dependencies></project>添加?spring-security-oauth2-autoconfigure?依賴,引入 Spring Security OAuth 并實(shí)現(xiàn)自動(dòng)配置。同時(shí),它也引入了 Spring Security 依賴。如下圖所示:
spring-security-oauth2-autoconfigure?
2.1.2 SecurityConfig
創(chuàng)建 SecurityConfig 配置類,提供一個(gè)賬號(hào)密碼為「yunai/1024」的用戶。代碼如下:
@Configuration @EnableWebSecurity public?class?SecurityConfig?extends?WebSecurityConfigurerAdapter?{@Override@Bean(name?=?BeanIds.AUTHENTICATION_MANAGER)public?AuthenticationManager?authenticationManagerBean()?throws?Exception?{return?super.authenticationManagerBean();}@Beanpublic?static?NoOpPasswordEncoder?passwordEncoder()?{return?(NoOpPasswordEncoder)?NoOpPasswordEncoder.getInstance();}@Overrideprotected?void?configure(AuthenticationManagerBuilder?auth)?throws?Exception?{auth.//?使用內(nèi)存中的?InMemoryUserDetailsManagerinMemoryAuthentication()//?不使用?PasswordEncoder?密碼編碼器.passwordEncoder(passwordEncoder())//?配置?yunai?用戶.withUser("yunai").password("1024").roles("USER");}}我們通過?Spring Security 提供認(rèn)證功能,所以這里需要配置一個(gè)用戶。
“友情提示:看不懂這個(gè)配置的胖友,后續(xù)可回《芋道 Spring Boot 安全框架 Spring Security 入門》重造下。
2.1.3 OAuth2AuthorizationServerConfig
創(chuàng)建 OAuth2AuthorizationServerConfig 配置類,進(jìn)行授權(quán)服務(wù)器。代碼如下:
@Configuration @EnableAuthorizationServer public?class?OAuth2AuthorizationServerConfig?extends?AuthorizationServerConfigurerAdapter?{/***?用戶認(rèn)證?Manager*/@Autowiredprivate?AuthenticationManager?authenticationManager;@Overridepublic?void?configure(AuthorizationServerEndpointsConfigurer?endpoints)?throws?Exception?{endpoints.authenticationManager(authenticationManager);}@Overridepublic?void?configure(AuthorizationServerSecurityConfigurer?oauthServer)?throws?Exception?{oauthServer.checkTokenAccess("isAuthenticated()");}@Overridepublic?void?configure(ClientDetailsServiceConfigurer?clients)?throws?Exception?{clients.inMemory()?//?<4.1>.withClient("clientapp").secret("112233")?//?<4.2> Client 賬號(hào)、密碼。.authorizedGrantTypes("password")?//?<4.2>?密碼模式.scopes("read_userinfo",?"read_contacts")?//?<4.2>?可授權(quán)的?Scope //????????????????.and().withClient()?//?<4.3>?可以繼續(xù)配置新的?Client;}}① 在類上添加?@EnableAuthorizationServer?注解,聲明開啟 OAuth?授權(quán)服務(wù)器的功能。
同時(shí),繼承 AuthorizationServerConfigurerAdapter 類,進(jìn)行 OAuth?授權(quán)服務(wù)器的配置。
②?#configure(AuthorizationServerEndpointsConfigurer endpoints)?方法,配置使用的 AuthenticationManager 實(shí)現(xiàn)用戶認(rèn)證的功能。其中,authenticationManager?是由「2.1.2 SecurityConfig」創(chuàng)建,Spring Security 的配置類。
③?#configure(AuthorizationServerSecurityConfigurer oauthServer)?方法,設(shè)置?/oauth/check_token?端點(diǎn),通過認(rèn)證后可訪問。
“友情提示:這里的認(rèn)證,指的是使用?client-id?+?client-secret?進(jìn)行的客戶端認(rèn)證,不要和用戶認(rèn)證混淆。
其中,/oauth/check_token?端點(diǎn)對(duì)應(yīng) CheckTokenEndpoint 類,用于校驗(yàn)訪問令牌的有效性。
-
在客戶端訪問資源服務(wù)器時(shí),會(huì)在請(qǐng)求中帶上訪問令牌。
-
在資源服務(wù)器收到客戶端的請(qǐng)求時(shí),會(huì)使用請(qǐng)求中的訪問令牌,找授權(quán)服務(wù)器確認(rèn)該訪問令牌的有效性。
④?#configure(ClientDetailsServiceConfigurer clients)?方法,進(jìn)行 Client 客戶端的配置。
<4.1>?處,設(shè)置使用基于內(nèi)存的 Client 存儲(chǔ)器。實(shí)際情況下,最好放入數(shù)據(jù)庫中,方便管理。
ClientDetailsService 子類<4.2>?處,創(chuàng)建一個(gè) Client 配置。如果要繼續(xù)添加另外的 Client 配置,可以在?<4.3>?處使用?#and()?方法繼續(xù)拼接。注意,這里的?.withClient("clientapp").secret("112233")?代碼段,就是?client-id?和?client-secret。
“補(bǔ)充知識(shí):可能會(huì)有胖友會(huì)問,為什么要?jiǎng)?chuàng)建 Client 的?client-id?和?client-secret?呢?
通過?client-id?編號(hào)和?client-secret,授權(quán)服務(wù)器可以知道調(diào)用的來源以及正確性。這樣,即使“壞人”拿到 Access Token ,但是沒有?client-id?編號(hào)和?client-secret,也不能和授權(quán)服務(wù)器發(fā)生有效的交互。
2.1.4 AuthorizationServerApplication
創(chuàng)建 AuthorizationServerApplication 類,授權(quán)服務(wù)器的啟動(dòng)類。代碼如下:
@SpringBootApplication public?class?AuthorizationServerApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(AuthorizationServerApplication.class,?args);}}2.1.5 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。下面,我們使用?Postman 模擬一個(gè) Client。
①?POST?請(qǐng)求 http://localhost:8080/oauth/token 地址,使用密碼模式進(jìn)行授權(quán)。如下圖所示:
?
- client-id?+?client-secret?進(jìn)行 Client 認(rèn)證 密碼模式的認(rèn)證
- 密碼模式的認(rèn)證
請(qǐng)求說明:
-
通過 Basic Auth 的方式,填寫?client-id?+?client-secret?作為用戶名與密碼,實(shí)現(xiàn) Client 客戶端有效性的認(rèn)證。
-
請(qǐng)求參數(shù)?grant_type?為?"password",表示使用密碼模式。
-
請(qǐng)求參數(shù)?username?和?password,表示用戶的用戶名與密碼。
響應(yīng)說明:
-
響應(yīng)字段?access_token?為訪問令牌,后續(xù)客戶端在訪問資源服務(wù)器時(shí),通過它作為身份的標(biāo)識(shí)。
-
響應(yīng)字段?token_type?為令牌類型,一般是?bearer?或是?mac?類型。
-
響應(yīng)字段?expires_in?為訪問令牌的過期時(shí)間,單位為秒。
-
響應(yīng)字段?scope?為權(quán)限范圍。
友情提示:/oauth/token?對(duì)應(yīng) TokenEndpoint 端點(diǎn),提供 OAuth2.0 的四種授權(quán)模式。感興趣的胖友,可以后續(xù)去擼擼。
②?POST?請(qǐng)求 http://localhost:8080/oauth/check_token 地址,校驗(yàn)訪問令牌的有效性。如下圖所示:
?
- client-id?+?client-secret?進(jìn)行 Client 認(rèn)證 密碼模式的認(rèn)證
- 密碼模式的認(rèn)證
請(qǐng)求和響應(yīng)比較簡(jiǎn)單,胖友自己瞅瞅即可。
2.2 搭建資源服務(wù)器
創(chuàng)建?lab-68-demo02-resource-server?項(xiàng)目,搭建資源服務(wù)器。
2.2.1 引入依賴
創(chuàng)建?pom.xml?文件,引入 Spring Security OAuth 依賴。
<?xml?version="1.0"?encoding="UTF-8"?> <project?xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>lab-68</artifactId><groupId>cn.iocoder.springboot.labs</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>lab-68-demo02-resource-server</artifactId><properties><!--?依賴相關(guān)配置?--><spring.boot.version>2.2.4.RELEASE</spring.boot.version><!--?插件相關(guān)配置?--><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.source>1.8</maven.compiler.source></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><!--?實(shí)現(xiàn)對(duì)?Spring?MVC?的自動(dòng)配置?--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--?實(shí)現(xiàn)對(duì)?Spring?Security?OAuth2?的自動(dòng)配置?--><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>${spring.boot.version}</version></dependency></dependencies></project> “友情提示:和「2.1.1 引入依賴」小節(jié),是一致的哈。
2.2.2 配置文件
創(chuàng)建?application.yml?配置文件,添加 Spring Security OAuth 相關(guān)配置。
server:port:?9090security:oauth2:#?OAuth2?Client?配置,對(duì)應(yīng)?OAuth2ClientProperties?類client:client-id:?clientappclient-secret:?112233#?OAuth2?Resource?配置,對(duì)應(yīng)?ResourceServerProperties?類resource:token-info-uri:?http://127.0.0.1:8080/oauth/check_token?#?獲得?Token?信息的?URL#?訪問令牌獲取?URL,自定義的access-token-uri:?http://127.0.0.1:8080/oauth/token①?security.oauth2.client?配置項(xiàng),OAuth2 Client 配置,對(duì)應(yīng) OAuth2ClientProperties 類。在這個(gè)配置項(xiàng)中,我們添加了客戶端的?client-id?和?client-secret。
為什么要添加這個(gè)配置項(xiàng)呢?因?yàn)橘Y源服務(wù)器會(huì)調(diào)用授權(quán)服務(wù)器的?/oauth/check_token?接口,而考慮到安全性,我們配置了該接口需要進(jìn)過客戶端認(rèn)證。
“友情提示:這里艿艿偷懶了,其實(shí)單獨(dú)給資源服務(wù)器配置一個(gè) Client 的?client-id?和?client-secret。我們可以把資源服務(wù)器理解成授權(quán)服務(wù)器的一個(gè)特殊的客戶端。
②?security.oauth2.resource?配置項(xiàng),OAuth2 Resource 配置,對(duì)應(yīng) ResourceServerProperties 類。
這里,我們通過?token-info-uri?配置項(xiàng),設(shè)置使用授權(quán)服務(wù)器的?/oauth/check_token?接口,校驗(yàn)訪問令牌的有效性。
③?security.access-token-uri?配置項(xiàng),是我們自定義的,設(shè)置授權(quán)服務(wù)器的?oauth/token?接口,獲取訪問令牌。因?yàn)樯院笪覀儗⒃?LoginController 中,實(shí)現(xiàn)一個(gè)?/login?登錄接口。
2.2.3 OAuth2ResourceServerConfig
創(chuàng)建 OAuth2ResourceServerConfig 類,進(jìn)行資源服務(wù)器。代碼如下:
@Configuration @EnableResourceServer public?class?OAuth2ResourceServerConfig?extends?ResourceServerConfigurerAdapter?{@Overridepublic?void?configure(HttpSecurity?http)?throws?Exception?{http.authorizeRequests()//?設(shè)置?/login?無需權(quán)限訪問.antMatchers("/login").permitAll()//?設(shè)置其它請(qǐng)求,需要認(rèn)證后訪問.anyRequest().authenticated();}}① 在類上添加?@EnableResourceServer?注解,聲明開啟 OAuth?資源服務(wù)器的功能。
同時(shí),繼承 ResourceServerConfigurerAdapter 類,進(jìn)行 OAuth?資源服務(wù)器的配置。
②?#configure(HttpSecurity http)?方法,設(shè)置 HTTP 權(quán)限。這里,我們?cè)O(shè)置?/login?接口無需權(quán)限訪問,其它接口認(rèn)證后可訪問。
這樣,客戶端在訪問資源服務(wù)器時(shí),其請(qǐng)求中的訪問令牌會(huì)被資源服務(wù)器調(diào)用授權(quán)服務(wù)器的?/oauth/check_token?接口,進(jìn)行校驗(yàn)訪問令牌的正確性。
2.2.4 ExampleController
創(chuàng)建 ExampleController 類,提供?/api/example/hello?接口,表示一個(gè)資源。代碼如下:
@RestController @RequestMapping("/api/example") public?class?ExampleController?{@RequestMapping("/hello")public?String?hello()?{return?"world";}}2.2.5 ResourceServerApplication
創(chuàng)建 ResourceServerApplication 類,資源服務(wù)器的啟動(dòng)類。代碼如下:
@SpringBootApplication public?class?ResourceServerApplication?{public?static?void?main(String[]?args)?{SpringApplication.run(ResourceServerApplication.class,?args);}}2.2.6 簡(jiǎn)單測(cè)試(第一彈)
執(zhí)行 ResourceServerApplication 啟動(dòng)資源服務(wù)器。下面,我們來請(qǐng)求服務(wù)器的 <127.0.0.1:9090/api/example/hello> 接口,進(jìn)行相應(yīng)的測(cè)試。
① 首先,請(qǐng)求 <127.0.0.1:9090/api/example/hello> 接口,不帶訪問令牌,則請(qǐng)求會(huì)被攔截。如下圖所示:
不帶訪問令牌② 然后,請(qǐng)求 <127.0.0.1:9090/api/example/hello> 接口,帶上錯(cuò)誤的訪問令牌,則請(qǐng)求會(huì)被攔截。如下圖所示:
錯(cuò)誤的訪問令牌?
“友情提示:訪問令牌需要在請(qǐng)求頭?"Authorization"?上設(shè)置,并且以?"Bearer "?開頭。
③ 最后,請(qǐng)求 <127.0.0.1:9090/api/example/hello> 接口,帶上正確的訪問令牌,則請(qǐng)求會(huì)被通過。如下圖所示:
正確的訪問令牌?
2.2.7 LoginController
創(chuàng)建 LoginController 類,提供?/login?登錄接口。代碼如下:
@RestController @RequestMapping("/") public?class?LoginController?{@Autowiredprivate?OAuth2ClientProperties?oauth2ClientProperties;@Value("${security.oauth2.access-token-uri}")private?String?accessTokenUri;@PostMapping("/login")public?OAuth2AccessToken?login(@RequestParam("username")?String?username,@RequestParam("password")?String?password)?{//?<1>?創(chuàng)建?ResourceOwnerPasswordResourceDetails?對(duì)象ResourceOwnerPasswordResourceDetails?resourceDetails?=?new?ResourceOwnerPasswordResourceDetails();resourceDetails.setAccessTokenUri(accessTokenUri);resourceDetails.setClientId(oauth2ClientProperties.getClientId());resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());resourceDetails.setUsername(username);resourceDetails.setPassword(password);//?<2>?創(chuàng)建?OAuth2RestTemplate?對(duì)象OAuth2RestTemplate?restTemplate?=?new?OAuth2RestTemplate(resourceDetails);restTemplate.setAccessTokenProvider(new?ResourceOwnerPasswordAccessTokenProvider());//?<3>?獲取訪問令牌return?restTemplate.getAccessToken();}}在?/login?接口中,資源服務(wù)器扮演的是一個(gè) OAuth?客戶端的角色,調(diào)用授權(quán)服務(wù)器的?/oauth/token?接口,使用密碼模式進(jìn)行授權(quán),獲得訪問令牌。
①?<1>?處,創(chuàng)建 ResourceOwnerPasswordResourceDetails 對(duì)象,填寫密碼模式授權(quán)需要的請(qǐng)求參數(shù)。
②?<2>?處,創(chuàng)建 OAuth2RestTemplate 對(duì)象,它是 Spring Security OAuth 封裝的工具類,用于請(qǐng)求授權(quán)服務(wù)器。
同時(shí),將 ResourceOwnerPasswordAccessTokenProvider 設(shè)置到其中,表示使用密碼模式授權(quán)。
“友情提示:這一步非常重要,艿艿在這里卡了非常非常非常久,一度自閉要放棄。
③?<3>?處,調(diào)用 OAuth2RestTemplate 的?#getAccessToken()?方法,調(diào)用授權(quán)服務(wù)器的?/oauth/token?接口,進(jìn)行密碼模式的授權(quán)。
注意,OAuth2RestTemplate 是有狀態(tài)的工具類,所以需要每次都重新創(chuàng)建。
2.2.8 簡(jiǎn)單測(cè)試(第二彈)
重新執(zhí)行 ResourceServerApplication 啟動(dòng)資源服務(wù)器。下面,我們來進(jìn)行?/login?接口的測(cè)試。
① 首先,請(qǐng)求 http://127.0.0.1:9090/login 接口,使用用戶的用戶名與密碼進(jìn)行登錄,獲得訪問令牌。如下圖所示:
測(cè)試?/login?接口響應(yīng)結(jié)果和授權(quán)服務(wù)器的?/oauth/token?接口是一致的,因?yàn)榫褪钦{(diào)用它,嘿嘿~
② 然后,請(qǐng)求 <127.0.0.1:9090/api/example/hello> 接口,帶剛剛的訪問令牌,則請(qǐng)求會(huì)被通過。如下圖所示:
正確的訪問令牌?
3. 授權(quán)碼模式
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo02-authorization-server-with-resource-owner-password-credentials
-
資源服務(wù)器:lab-68-demo02-resource-server
本小節(jié),我們來學(xué)習(xí)授權(quán)碼模式(Authorization Code)。
授權(quán)碼模式,是功能最完整、流程最嚴(yán)密的授權(quán)模式。它的特點(diǎn)就是通過客戶端的后臺(tái)服務(wù)器,與授權(quán)務(wù)器進(jìn)行互動(dòng)。
“旁白君:一般情況下,在有客戶端的情況下,我們與第三方平臺(tái)常常采用這種方式。
授權(quán)碼模式?
“-
(A)用戶訪問客戶端,后者將前者跳轉(zhuǎn)到到授權(quán)服務(wù)器。
-
(B)用戶選擇是否給予客戶端授權(quán)。
-
(C)假設(shè)用戶給予授權(quán),授權(quán)服務(wù)器將跳轉(zhuǎn)到客戶端事先指定的"重定向 URI"(Redirection URI),同時(shí)附上一個(gè)授權(quán)碼。
-
(D)客戶端收到授權(quán)碼,附上早先的"重定向 URI",向認(rèn)證服務(wù)器申請(qǐng)令牌。這一步是在客戶端的后臺(tái)的服務(wù)器上完成的,對(duì)用戶不可見。
-
(E)認(rèn)證服務(wù)器核對(duì)了授權(quán)碼和重定向 URI,確認(rèn)無誤后,向客戶端發(fā)送訪問令牌。
下面,我們來新建兩個(gè)項(xiàng)目,搭建一個(gè)授權(quán)碼模式的使用示例。如下圖所示:
項(xiàng)目結(jié)構(gòu)-
lab-68-demo02-authorization-server-with-resource-owner-password-credentials:授權(quán)服務(wù)器。
-
lab-68-demo02-resource-server:資源服務(wù)器。
3.1 搭建授權(quán)服務(wù)器
復(fù)制出?lab-68-demo02-authorization-server-with-resource-owner-password-credentials?項(xiàng)目,修改搭建授權(quán)服務(wù)器。改動(dòng)點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)僅僅需要修改 OAuth2AuthorizationServerConfig 類,設(shè)置使用?"authorization_code"?授權(quán)碼模式,并設(shè)置回調(diào)地址。
🙂 注意,這里設(shè)置的回調(diào)地址,稍后我們會(huì)在「3.2 搭建資源服務(wù)器」中實(shí)現(xiàn)。
3.1.1 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。
① 使用瀏覽器,訪問 http://127.0.0.1:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://127.0.0.1:9090/callback&response_type=code&scope=read_userinfo 地址,獲取授權(quán)。請(qǐng)求參數(shù)說明如下:
-
client_id?參數(shù),必傳,為我們?cè)?OAuth2AuthorizationServer 中配置的 Client 的編號(hào)。
-
redirect_uri?參數(shù),可選,回調(diào)地址。當(dāng)然,如果?client_id?對(duì)應(yīng)的 Client 未配置?redirectUris?屬性,會(huì)報(bào)錯(cuò)。
-
response_type?參數(shù),必傳,返回結(jié)果為?code?授權(quán)碼。
-
scope?參數(shù),可選,申請(qǐng)授權(quán)的 Scope 。如果多個(gè),使用逗號(hào)分隔。
-
state?參數(shù),可選,表示客戶端的當(dāng)前狀態(tài),可以指定任意值,授權(quán)服務(wù)器會(huì)原封不動(dòng)地返回這個(gè)值。
友情提示:state?參數(shù),未在上述 URL 中體現(xiàn)出來。
因?yàn)槲覀儾⑽?strong>登錄授權(quán)服務(wù)器,所以被攔截跳轉(zhuǎn)到登錄界面。如下圖所示:
登錄界面② 輸入用戶的賬號(hào)密碼「yunai/1024」進(jìn)行登錄。登錄完成后,進(jìn)入授權(quán)界面。如下圖所示:
“旁白君:和我們?nèi)粘J褂玫尿v訊 QQ、微信、微博等等三方登錄,是一模一樣的,除了丑了點(diǎn),嘿嘿~
授權(quán)界面③ 選擇?scope.read_userinfo?為 Approve 允許,點(diǎn)擊「Authorize」按鈕,完成授權(quán)操作。瀏覽器自動(dòng)重定向到 Redirection URI 地址,并且在 URI 上可以看到?code?授權(quán)碼。如下圖所示:
回調(diào)界面?
“友情提示:/oauth/authorize?對(duì)應(yīng) AuthorizationEndpoint 端點(diǎn)。
④ 因?yàn)槲覀儠簳r(shí)沒有啟動(dòng)資源服務(wù)器,所以顯示無法訪問。這里,我們先使用 Postman 模擬請(qǐng)求 http://localhost:8080/oauth/token 地址,使用授權(quán)碼獲取到訪問令牌。如下圖所示:
?
- client-id?+?client-secret?進(jìn)行 Client 認(rèn)證 授權(quán)碼模式的認(rèn)證
- 授權(quán)碼模式的認(rèn)證
?
請(qǐng)求說明:
-
通過 Basic Auth 的方式,填寫?client-id?+?client-secret?作為用戶名與密碼,實(shí)現(xiàn) Client 客戶端有效性的認(rèn)證。
-
請(qǐng)求參數(shù)?grant_type?為?"authorization_code",表示使用授權(quán)碼模式。
-
請(qǐng)求參數(shù)?code,從授權(quán)服務(wù)器獲取到的授權(quán)碼。
-
請(qǐng)求參數(shù)?redirect_uri,Client 客戶端的?Redirection URI?地址。
注意,授權(quán)碼僅能使用一次,重復(fù)請(qǐng)求會(huì)報(bào)?Invalid authorization code:?錯(cuò)誤。如下圖所示:
授權(quán)碼模式的認(rèn)證 - 失敗3.2 搭建資源服務(wù)器
復(fù)用?lab-68-demo02-resource-server?項(xiàng)目,主要是提供回調(diào)地址。如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 新建 CallbackController 類,提供?/callback?回調(diào)地址。
② 在 OAuth2ResourceServerConfig 配置類中,設(shè)置?/callback?回調(diào)地址無需權(quán)限驗(yàn)證,不然回調(diào)都跳轉(zhuǎn)不過來哈。
3.2.1 CallbackController
創(chuàng)建 CallbackController 類,提供?/callback?回調(diào)地址,在獲取到授權(quán)碼時(shí),請(qǐng)求授權(quán)服務(wù)器,通過授權(quán)碼獲取訪問令牌。代碼如下:
@RestController @RequestMapping("/") public?class?CallbackController?{@Autowiredprivate?OAuth2ClientProperties?oauth2ClientProperties;@Value("${security.oauth2.access-token-uri}")private?String?accessTokenUri;@GetMapping("/callback")public?OAuth2AccessToken?login(@RequestParam("code")?String?code)?{//?創(chuàng)建?AuthorizationCodeResourceDetails?對(duì)象AuthorizationCodeResourceDetails?resourceDetails?=?new?AuthorizationCodeResourceDetails();resourceDetails.setAccessTokenUri(accessTokenUri);resourceDetails.setClientId(oauth2ClientProperties.getClientId());resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());//?創(chuàng)建?OAuth2RestTemplate?對(duì)象OAuth2RestTemplate?restTemplate?=?new?OAuth2RestTemplate(resourceDetails);restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAuthorizationCode(code);?//?<1>?設(shè)置?coderestTemplate.getOAuth2ClientContext().getAccessTokenRequest().setPreservedState("http://127.0.0.1:9090/callback");?//?<2>?通過這個(gè)方式,設(shè)置?redirect_uri?參數(shù)restTemplate.setAccessTokenProvider(new?AuthorizationCodeAccessTokenProvider());//?獲取訪問令牌return?restTemplate.getAccessToken();}}代碼比較簡(jiǎn)單,還是使用 OAuth2RestTemplate 進(jìn)行請(qǐng)求授權(quán)服務(wù)器,胖友自己瞅瞅哈。
需要注意的是?<1>?和?<2>?處,設(shè)置請(qǐng)求授權(quán)服務(wù)器需要的?code?和?redirect_uri?參數(shù)。
3.2.2 簡(jiǎn)單測(cè)試
執(zhí)行 ResourceServerApplication 啟動(dòng)資源服務(wù)器。
重復(fù)「3.2.1 簡(jiǎn)單測(cè)試」的過程,成功獲取到訪問令牌。如下圖所示:
授權(quán)碼模式的認(rèn)證 - 成功4. 簡(jiǎn)化模式
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo02-authorization-server-with-implicit
-
資源服務(wù)器:lab-68-demo02-resource-server
本小節(jié),我們來學(xué)習(xí)簡(jiǎn)化模式(Implicit)。
簡(jiǎn)化模式,不通過第三方應(yīng)用程序的服務(wù)器,直接在瀏覽器中向授權(quán)服務(wù)器申請(qǐng)令牌,跳過了“授權(quán)碼”這個(gè)步驟,因此得名。所有步驟在瀏覽器中完成,令牌對(duì)訪問者是可見的,且客戶端不需要授權(quán)。
簡(jiǎn)化模式?
“-
(A)用戶訪問客戶端,后者將前者跳轉(zhuǎn)到到授權(quán)服務(wù)器。
-
(B)用戶選擇是否給予客戶端授權(quán)。
-
(C)假設(shè)用戶給予授權(quán),授權(quán)服務(wù)器將用戶導(dǎo)向客戶端指定的"重定向URI",并在 URI 的?Hash 部分包含了訪問令牌。
-
(D)瀏覽器向資源服務(wù)器發(fā)出請(qǐng)求,其中不包括上一步收到的 Hash 值。
-
(E)資源服務(wù)器返回一個(gè)網(wǎng)頁,其中包含的代碼可以獲取 Hash 值中的令牌。
-
(F)瀏覽器執(zhí)行上一步獲得的腳本,提取出令牌。
-
(G)瀏覽器將令牌發(fā)給客戶端。
?
-
lab-68-demo02-authorization-server-with-implicit:授權(quán)服務(wù)器。
-
lab-68-demo02-resource-server:資源服務(wù)器。
4.1 搭建授權(quán)服務(wù)器
復(fù)制出?lab-68-demo02-authorization-server-with-implicit?項(xiàng)目,修改搭建授權(quán)服務(wù)器。改動(dòng)點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)僅僅需要修改 OAuth2AuthorizationServerConfig 類,設(shè)置使用?"implicit"?簡(jiǎn)化模式,并設(shè)置回調(diào)地址。
🙂 注意,這里設(shè)置的回調(diào)地址,稍后我們會(huì)在「4.2 搭建資源服務(wù)器」中實(shí)現(xiàn)。
4.2 搭建資源服務(wù)器
復(fù)用?lab-68-demo02-resource-server?項(xiàng)目,主要是提供回調(diào)地址。如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 新建 Callback02Controller 類,提供?/callback02?回調(diào)地址。代碼如下:
@RestController @RequestMapping("/") public?class?Callback02Controller?{@GetMapping("/callback02")public?String?login()?{return?"假裝這里有一個(gè)頁面";}} “友情提示:考慮到暫時(shí)不想做頁面,所以這里先假裝一下,嘿嘿。
② 在 OAuth2ResourceServerConfig 配置類中,設(shè)置?/callback02?回調(diào)地址無需權(quán)限驗(yàn)證,不然回調(diào)都跳轉(zhuǎn)不過來哈。
4.3 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。
執(zhí)行 ResourceServerApplication 啟動(dòng)資源服務(wù)器。
① 使用瀏覽器,訪問 http://127.0.0.1:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://127.0.0.1:9090/callback02&response_type=token&scope=read_userinfo 地址,獲取授權(quán)。請(qǐng)求參數(shù)說明如下:
-
client_id?參數(shù),必傳,為我們?cè)?OAuth2AuthorizationServer 中配置的 Client 的編號(hào)。
-
redirect_uri?參數(shù),可選,回調(diào)地址。當(dāng)然,如果?client_id?對(duì)應(yīng)的 Client 未配置?redirectUris?屬性,會(huì)報(bào)錯(cuò)。
-
response_type?參數(shù),必傳,返回結(jié)果為?token?訪問令牌。
-
scope?參數(shù),可選,申請(qǐng)授權(quán)的 Scope 。如果多個(gè),使用逗號(hào)分隔。
-
state?參數(shù),可選,表示客戶端的當(dāng)前狀態(tài),可以指定任意值,授權(quán)服務(wù)器會(huì)原封不動(dòng)地返回這個(gè)值。
友情提示:state?參數(shù),未在上述 URL 中體現(xiàn)出來。
因?yàn)槲覀儾⑽?strong>登錄授權(quán)服務(wù)器,所以被攔截跳轉(zhuǎn)到登錄界面。如下圖所示:
登錄界面② 輸入用戶的賬號(hào)密碼「yunai/1024」進(jìn)行登錄。登錄完成后,進(jìn)入授權(quán)界面。如下圖所示:
“旁白君:和我們?nèi)粘J褂玫尿v訊 QQ、微信、微博等等三方登錄,是一模一樣的,除了丑了點(diǎn),嘿嘿~
授權(quán)界面③ 選擇?scope.read_userinfo?為 Approve 允許,點(diǎn)擊「Authorize」按鈕,完成授權(quán)操作。瀏覽器自動(dòng)重定向到 Redirection URI 地址,并且在 URI 上的?Hash 部分可以看到?access_token?訪問令牌。如下圖所示:
回調(diào)界面后續(xù),可以通過編寫 Javascript 腳本的代碼,獲取 URI 上的?Hash 部分的訪問令牌。
5. 客戶端模式
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo02-authorization-server-with-client-credentials
-
資源服務(wù)器:lab-68-demo02-resource-server
本小節(jié),我們來學(xué)習(xí)客戶端模式(Client Credentials)。
客戶端模式,指客戶端以自己的名義,而不是以用戶的名義,向授權(quán)服務(wù)器進(jìn)行認(rèn)證。
嚴(yán)格地說,客戶端模式并不屬于 OAuth 框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊(cè),客戶端以自己的名義要求授權(quán)服務(wù)器提供服務(wù),其實(shí)不存在授權(quán)問題。
“旁白君:我們對(duì)接微信公眾號(hào)時(shí),就采用的客戶端模式。我們的后端服務(wù)器就扮演“客戶端”的角色,與微信公眾號(hào)的后端服務(wù)器進(jìn)行交互。
客戶端模式?
“-
(A)客戶端向授權(quán)服務(wù)器進(jìn)行身份認(rèn)證,并要求一個(gè)訪問令牌。
-
(B)授權(quán)服務(wù)器確認(rèn)無誤后,向客戶端提供訪問令牌。
下面,我們來新建兩個(gè)項(xiàng)目,搭建一個(gè)客戶端模式的使用示例。如下圖所示:
項(xiàng)目結(jié)構(gòu)?
-
lab-68-demo02-authorization-server-with-client-credentials:授權(quán)服務(wù)器。
-
lab-68-demo02-resource-server:資源服務(wù)器。
5.1 搭建授權(quán)服務(wù)器
復(fù)制出?lab-68-demo02-authorization-server-with-client-credentials?項(xiàng)目,修改搭建授權(quán)服務(wù)器。改動(dòng)點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 刪除 SecurityConfig 配置類,因?yàn)榭蛻舳四J较?#xff0c;無需 Spring Security 提供用戶的認(rèn)證功能。
但是,Spring Security OAuth 需要一個(gè) PasswordEncoder Bean,否則會(huì)報(bào)錯(cuò),因此我們?cè)?OAuth2AuthorizationServerConfig 類的?#passwordEncoder()?方法進(jìn)行創(chuàng)建。
② 修改 OAuth2AuthorizationServerConfig 類,設(shè)置使用?"client_credentials"?客戶端模式。
5.1.1 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。下面,我們使用?Postman 模擬一個(gè) Client。
①?POST?請(qǐng)求 http://localhost:8080/oauth/token 地址,使用客戶端模式進(jìn)行授權(quán)。如下圖所示:
- client-id?+?client-secret?進(jìn)行 Client 認(rèn)證 客戶端模式的認(rèn)證
?
- 客戶端模式的認(rèn)證
?
請(qǐng)求說明:
-
通過 Basic Auth 的方式,填寫?client-id?+?client-secret?作為用戶名與密碼,實(shí)現(xiàn) Client 客戶端有效性的認(rèn)證。
-
請(qǐng)求參數(shù)?grant_type?為?"client_credentials",表示使用客戶端模式。
響應(yīng)就是訪問令牌,胖友自己瞅瞅即可。
5.2 搭建資源服務(wù)器
復(fù)用?lab-68-demo02-resource-server?項(xiàng)目,修改點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 新建 ClientLoginController 類,提供?/client-login?接口,實(shí)現(xiàn)調(diào)用授權(quán)服務(wù)器,進(jìn)行客戶端模式的授權(quán),獲得訪問令牌。代碼如下:
@RestController @RequestMapping("/") public?class?ClientLoginController?{@Autowiredprivate?OAuth2ClientProperties?oauth2ClientProperties;@Value("${security.oauth2.access-token-uri}")private?String?accessTokenUri;@PostMapping("/client-login")public?OAuth2AccessToken?login()?{//?創(chuàng)建?ClientCredentialsResourceDetails?對(duì)象ClientCredentialsResourceDetails?resourceDetails?=?new?ClientCredentialsResourceDetails();resourceDetails.setAccessTokenUri(accessTokenUri);resourceDetails.setClientId(oauth2ClientProperties.getClientId());resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret());//?創(chuàng)建?OAuth2RestTemplate?對(duì)象OAuth2RestTemplate?restTemplate?=?new?OAuth2RestTemplate(resourceDetails);restTemplate.setAccessTokenProvider(new?ClientCredentialsAccessTokenProvider());//?獲取訪問令牌return?restTemplate.getAccessToken();}}代碼比較簡(jiǎn)單,還是使用 OAuth2RestTemplate 進(jìn)行請(qǐng)求授權(quán)服務(wù)器,胖友自己瞅瞅哈。
② 在 OAuth2ResourceServerConfig 配置類中,設(shè)置?/client-login?接口無需權(quán)限驗(yàn)證,不然無法調(diào)用哈。
5.2.1 簡(jiǎn)單測(cè)試
執(zhí)行 ResourceServerApplication 啟動(dòng)資源服務(wù)器。
① 使用「5.1.1 簡(jiǎn)單測(cè)試」小節(jié)獲得的訪問令牌,請(qǐng)求 <127.0.0.1:9090/api/example/hello> 接口時(shí)帶上,則請(qǐng)求會(huì)被通過。如下圖所示:
正確的訪問令牌② 請(qǐng)求 http://127.0.0.1:9090/clientlogin 接口,使用客戶端模式進(jìn)行授權(quán),獲得訪問令牌。如下圖所示:
測(cè)試?client-login?接口響應(yīng)結(jié)果和授權(quán)服務(wù)器的?/oauth/token?接口是一致的,因?yàn)榫褪钦{(diào)用它,嘿嘿~
6. 合并服務(wù)器
“旁白君:這個(gè)小節(jié)的標(biāo)題,艿艿有點(diǎn)不知道怎么取了,就先叫合并服務(wù)器吧 = =!
在項(xiàng)目比較小時(shí),考慮到節(jié)省服務(wù)器資源,會(huì)考慮將授權(quán)服務(wù)器和資源服務(wù)器合并到一個(gè)項(xiàng)目中,避免啟動(dòng)多個(gè) Java 進(jìn)程。良心的艿艿,編寫了四種授權(quán)模式的示例,如下圖所示:
示例項(xiàng)目
-
基于密碼模式的示例:lab-68-demo01-resource-owner-password-credentials-server
-
基于授權(quán)碼模式的示例:lab-68-demo01-authorization-code-server
-
基于簡(jiǎn)化模式的示例:lab-68-demo01-implicit-server
-
基于客戶端模式的示例:lab-68-demo01-client-credentials-server
具體的代碼實(shí)現(xiàn),實(shí)際和上述每個(gè)授權(quán)模式對(duì)應(yīng)的小節(jié)是基本一致的,只是說將代碼“放”在了一個(gè)項(xiàng)目中。嘿嘿~
7. 刷新令牌
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo03-authorization-server-with-client-credentials
在 OAuth2.0 中,一共有兩類令牌:
-
訪問令牌(Access Token)
-
刷新令牌(Refresh Token)
在訪問令牌過期時(shí),我們可以使用刷新令牌向授權(quán)服務(wù)器獲取一個(gè)新的訪問令牌。
可能會(huì)胖友有疑惑,為什么會(huì)有刷新令牌呢?每次請(qǐng)求資源服務(wù)器時(shí),都會(huì)在請(qǐng)求上帶上訪問令牌,這樣它的泄露風(fēng)險(xiǎn)是相對(duì)高的。
因此,出于安全性的考慮,訪問令牌的過期時(shí)間比較短,刷新令牌的過期時(shí)間比較長(zhǎng)。這樣,如果訪問令牌即使被盜用走,那么在一定的時(shí)間后,訪問令牌也能在較短的時(shí)間吼過期。當(dāng)然,安全也是相對(duì)的,如果使用刷新令牌后,獲取到新的訪問令牌,訪問令牌后續(xù)又可能被盜用。
艿艿整理了下,大家常用開放平臺(tái)的令牌過期時(shí)間,讓大家更好的理解:
| 微信開放平臺(tái) | 2 小時(shí) | 未知 |
| 騰訊開放平臺(tái) | 90 天 | 未知 |
| 小米開放平臺(tái) | 90 天 | 10 年 |
7.1 示例項(xiàng)目
下面,復(fù)制出?lab-68-demo03-authorization-server-with-client-credentials?項(xiàng)目,搭建提供訪問令牌的授權(quán)服務(wù)器。改動(dòng)點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 在 OAuth2AuthorizationServerConfig 的?#configure(ClientDetailsServiceConfigurer clients)?方法中,在配置的 Client 的授權(quán)模式中,額外新增?"refresh_token"?刷新令牌。
通過?#accessTokenValiditySeconds(int accessTokenValiditySeconds)?方法,設(shè)置訪問令牌的有效期。
通過?#refreshTokenValiditySeconds(int refreshTokenValiditySeconds)?方法,設(shè)置刷新令牌的有效期。
② 在 OAuth2AuthorizationServerConfig 的?#configure(AuthorizationServerEndpointsConfigurer endpoints)?方法中,設(shè)置使用的?userDetailsService?用戶詳情 Service。
而該?userDetailsService?是在 SecurityConfig 的?#userDetailsServiceBean()?方法創(chuàng)建的 UserDetailsService Bean。
“友情提示:如果不進(jìn)行 UserDetailsService 的設(shè)置,在使用刷新令牌獲取新的訪問令牌時(shí),會(huì)拋出異常。
7.2 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。下面,我們使用?Postman 模擬一個(gè) Client。
①?POST?請(qǐng)求 http://localhost:8080/oauth/token 地址,使用密碼模式進(jìn)行授權(quán)。如下圖所示:
密碼模式的認(rèn)證額外多返回了?refresh_token?刷新令牌。
②?POST?請(qǐng)求 http://localhost:8080/oauth/token 地址,使用刷新令牌模式進(jìn)行授權(quán)。如下圖所示:
刷新令牌模式的認(rèn)證請(qǐng)求說明:
-
通過 Basic Auth 的方式,填寫?client-id?+?client-secret?作為用戶名與密碼,實(shí)現(xiàn) Client 客戶端有效性的認(rèn)證。
-
請(qǐng)求參數(shù)?grant_type?為?"refresh_token",表示使用刷新令牌模式。
-
請(qǐng)求參數(shù)?refresh_token,表示刷新令牌。
在響應(yīng)中,返回了新的?access_token?訪問令牌。注意,老的?access_token?訪問令牌會(huì)失效,無法繼續(xù)使用。
8. 刪除令牌
“示例代碼對(duì)應(yīng)倉庫:
-
授權(quán)服務(wù)器:lab-68-demo03-authorization-server-with-client-credentials
在用戶登出系統(tǒng)時(shí),我們會(huì)有刪除令牌的需求。雖然說,可以通過客戶端本地刪除令牌的方式實(shí)現(xiàn)。但是,考慮到真正的徹底的實(shí)現(xiàn)刪除令牌,必然服務(wù)端自身需要?jiǎng)h除令牌。
“友情提示:客戶端本地刪除令牌的方式實(shí)現(xiàn),指的是清楚本地 Cookie、localStorage 的令牌緩存。
在 Spring Security OAuth2 中,并沒有提供內(nèi)置的接口,所以需要自己去實(shí)現(xiàn)。筆者參看 《Spring Security OAuth2 – Simple Token Revocation》 文檔,實(shí)現(xiàn)刪除令牌的 API 接口。
具體的實(shí)現(xiàn),通過調(diào)用 ConsumerTokenServices 的?#revokeToken(String tokenValue)?方法,刪除訪問令牌和刷新令牌。如下圖所示:
ConsumerTokenServices 實(shí)現(xiàn)類8.1 示例項(xiàng)目
下面,我們直接在授權(quán)服務(wù)器?lab-68-demo03-authorization-server-with-resource-owner-password-credentials?項(xiàng)目,修改接入刪除令牌的功能。改動(dòng)點(diǎn)如下圖所示:
項(xiàng)目改動(dòng)點(diǎn)① 創(chuàng)建 TokenDemoController 類,提供?/token/demo/revoke?接口,調(diào)用 ConsumerTokenServices 的?#revokeToken(String tokenValue)?方法,刪除訪問令牌和刷新令牌。代碼如下:
@RestController @RequestMapping("/token/demo") public?class?TokenDemoController?{@Autowiredprivate?ConsumerTokenServices?tokenServices;@PostMapping(value?=?"/revoke")public?boolean?revokeToken(@RequestParam("token")?String?token)?{return?tokenServices.revokeToken(token);}}② 在 SecurityConfig 配置類,設(shè)置?/token/demo/revoke?接口無需授權(quán),方便測(cè)試。代碼如下:
//?SecurityConfig.java@Override protected?void?configure(HttpSecurity?http)?throws?Exception?{http.csrf().disable().authorizeRequests()//?設(shè)置?/token/demo/revoke?無需授權(quán).mvcMatchers("/token/demo/revoke").permitAll()//?設(shè)置其它接口需要授權(quán).anyRequest().authenticated(); }8.2 簡(jiǎn)單測(cè)試
執(zhí)行 AuthorizationServerApplication 啟動(dòng)授權(quán)服務(wù)器。下面,我們使用?Postman 模擬一個(gè) Client。
①?POST?請(qǐng)求 http://localhost:8080/oauth/token 地址,使用密碼模式進(jìn)行授權(quán)。如下圖所示:
密碼模式的認(rèn)證②?POST?請(qǐng)求 http://localhost:8080/token/demo/revoke 地址,刪除令牌。如下圖所示:
刪除令牌刪除成功。后續(xù),胖友可以自己調(diào)用授權(quán)服務(wù)器的?oauth/check_token?接口,測(cè)試訪問令牌是否已經(jīng)被刪除。
666. 彩蛋
至此,我們完整學(xué)習(xí) Spring Security OAuth 框架。不過 Spring 團(tuán)隊(duì)宣布該框架處于 Deprecation?廢棄狀態(tài)。如下圖所示:
Spring Security OAuth 被廢棄
同時(shí),Spring 團(tuán)隊(duì)正在實(shí)現(xiàn)新的 Spring Authorization Server?授權(quán)服務(wù)器,目前還處于 Experimental?實(shí)驗(yàn)狀態(tài)。
實(shí)際項(xiàng)目中,根據(jù)艿艿了解到的情況,很少項(xiàng)目會(huì)直接采用 Spring Security OAuth 框架,而是自己參考它進(jìn)行 OAuth2.0 的實(shí)現(xiàn)。并且,一般只會(huì)實(shí)現(xiàn)密碼授權(quán)模式。
在本文中,我們采用基于內(nèi)存的 InMemoryTokenStore,實(shí)現(xiàn)訪問令牌和刷新令牌的存儲(chǔ)。它會(huì)存在兩個(gè)明顯的缺點(diǎn):
-
重啟授權(quán)服務(wù)器時(shí),令牌信息會(huì)丟失,導(dǎo)致用戶需要重新授權(quán)。
-
多個(gè)授權(quán)服務(wù)器時(shí),令牌信息無法共享,導(dǎo)致用戶一會(huì)授權(quán)成功,一會(huì)授權(quán)失敗。
因此,下一篇《芋道 Spring Security OAuth2 存儲(chǔ)器》文章,我們來學(xué)習(xí) Spring Security OAuth 提供的基于數(shù)據(jù)庫和?Redis的存儲(chǔ)器。走起~
總結(jié)
以上是生活随笔為你收集整理的可能是第二好的 Spring OAuth 2.0 文章,艿艿端午在家写了 3 天~的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis性能监控指标汇总
- 下一篇: 为什么 MySQL 使用 B+ 树,而不