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