Spring Security OAuth2 授權(quán)碼模式的實(shí)現(xiàn)
寫在前邊
在文章OAuth 2.0 概念及授權(quán)流程梳理 中我們談到OAuth 2.0的概念與流程,這里我準(zhǔn)備分別記一記這幾種授權(quán)模式的demo,一方面為自己的最近的學(xué)習(xí)做個總結(jié),另一方面做下知識輸出,如果文中有錯誤的地方,請評論指正,在此不勝感激
受眾前提
閱讀本文,默認(rèn)讀者已經(jīng)過Spring Security有一定的了解,對OAuth2流程有一定了解
本文目標(biāo)
帶領(lǐng)讀者對Spring Security OAuth2框架的授權(quán)碼模式有一個比較直觀的概念,能使用框架搭建授權(quán)碼模式授權(quán)服務(wù)器與資源服務(wù)器(分離版本)
授權(quán)碼模式流程回顧
授權(quán)碼模式要求:用戶登錄并對第三方應(yīng)用(客戶端)進(jìn)行授權(quán),出示授權(quán)碼交給客戶端,客戶端憑授權(quán)碼換取access_token(訪問憑證)
此模式要求授權(quán)服務(wù)器與用戶直接交互,在此過程中,第三方應(yīng)用是無法獲取到用戶輸入的密碼等信息的,這個模式也是OAuth 2.0中最安全的一個
Demo基本結(jié)構(gòu)
這里主要關(guān)注authorization-code-authorization-server
與authorization-code-resource-server
這兩個模塊
本文以及后續(xù)文章的demo均放在GitHub上,歡迎大家Star & Fork,源碼地址:https://github.com/hellxz/spring-security-oauth2-learn
authorization-code-client-resttemplate-jdbc
這個項目是用來測試非OAuth2服務(wù)使用RestTemplate與JdbcTemplate對接OAuth2授權(quán)服務(wù)的,流程這里不講,有興趣可以debug看看,可能會讓您對整個流程會有更清晰的感受
Maven依賴
<!--Spring Security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--Spring Boot Starter Web 所有demo均使用web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security OAuth2 --> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>${spring-security-oauth2.version}</version> </dependency>
搭建授權(quán)服務(wù)器(Authorization Server)
文中服務(wù)器均使用demo級別配置,請勿直接使用到生產(chǎn)環(huán)境
授權(quán)服務(wù)器結(jié)構(gòu)主體如下:
啟動類自不多說,先說下SecurityConfig
package com.github.hellxz.oauth2.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Collections; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // @formatter: off auth.inMemoryAuthentication() .withUser("hellxz") .password(passwordEncoder().encode("xyz")) .authorities(Collections.emptyList()); // @formatter: on } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() //所有請求都需要通過認(rèn)證 .and() .httpBasic() //Basic登錄 .and() .csrf().disable(); //關(guān)跨域保護(hù) } }
通過@Configuration
和@EnableWebSecurity
開啟Spring Security配置,繼承WebSecurityConfigurerAdapter
的方法,實(shí)現(xiàn)個性化配置,這里我們使用內(nèi)存保存一個名為hellxz
、密碼為xyz
的用戶,與授權(quán)服務(wù)器交互的用戶就是他了
除了配置用戶,我們需要對服務(wù)的資源進(jìn)行保護(hù),這里將所有的請求都要求通過認(rèn)證才可以訪問,用戶登錄需要使用httpBasic形式(就是那種網(wǎng)頁彈個窗要求登錄的那種😄)
Spring Security 5.x版本后,要求顯示聲明使用的密碼器,就是PasswordEncoder
了,常用BCryptPasswordEncoder
,簡單的可以認(rèn)為它是使用時間戳和鹽進(jìn)行加密的一種算法,同一個密碼被加密后也不會相同
接著看看授權(quán)服務(wù)器的配置
,畫重點(diǎn)
package com.github.hellxz.oauth2.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; //授權(quán)服務(wù)器配置 @Configuration @EnableAuthorizationServer //開啟授權(quán)服務(wù) public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //允許表單提交 security.allowFormAuthenticationForClients() .checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // @formatter: off clients.inMemory() .withClient("client-a") //client端唯一標(biāo)識 .secret(passwordEncoder.encode("client-a-secret")) //客戶端的密碼,這里的密碼應(yīng)該是加密后的 .authorizedGrantTypes("authorization_code") //授權(quán)模式標(biāo)識 .scopes("read_user_info") //作用域 .resourceIds("resource1") //資源id .redirectUris("http://localhost:9001/callback"); //回調(diào)地址 // @formatter: on } }
1.通過@Configuration
和EnableAuthorizationServer
開啟授權(quán)服務(wù)器配置,通過重寫AuthorizationServerConfigurerAdapter
的方法來完成自定義授權(quán)服務(wù)器
2.OAuth2授權(quán)碼模式中,要求不僅僅用戶需要登錄,還要求客戶端也需要登錄,這里就需要在configure(ClientDetailsServiceConfigurer clients)
這個方法中配置客戶端(第三方應(yīng)用)的登錄信息,
withClient中配置的是客戶端id(client_id)
secret為客戶端的密碼,要求使用加密器進(jìn)行加密
授權(quán)碼的authorizedGrantTypes必須配置有"authorization_code"(授權(quán)碼模式),這里是可以同時支持多種授權(quán)模式的,為了簡單只寫一個
scopes,請求資源作用域,用于限制客戶端與用戶無法訪問沒有作用域的資源
resourceIds,可選,資源id,可以對應(yīng)一個資源服務(wù)器,個人理解為某個資源服務(wù)器的所有資源標(biāo)識
redirectUris,回調(diào)地址,有兩個作用:1.回調(diào)客戶端地址,返回授權(quán)碼; 2.校驗(yàn)是否是同一個客戶端
redirectUris校驗(yàn)是否同一個客戶端這個,可能說的不是很準(zhǔn)確,說下大體流程,我們在授權(quán)服務(wù)器上配置了這個回調(diào)地址,授權(quán)服務(wù)器在用戶授權(quán)成功后,返回授權(quán)碼的地址就是它,另外我們后續(xù)申請token時,也需要傳遞這個回調(diào)地址,所以我的理解是校驗(yàn)是否是同一客戶端發(fā)來的第二次請求(換token時)
3.configure(AuthorizationServerSecurityConfigurer security)
這里配置資源客戶端(第三方應(yīng)用)的表單提交權(quán)限,類似Spring Security配置的permitAll()
等權(quán)限控制標(biāo)識,如果不配置,客戶端將無法換取token
4.application.properties
這里我只配置了server.port=8080
這樣我們就配置了相當(dāng)簡易的授權(quán)服務(wù)器,啟動測試下
獲取授權(quán)碼的流程,一般是由客戶端使用自己的client_id與密碼+response_type=code拼接url,讓瀏覽器跳轉(zhuǎn)完成的,用戶的登錄與授權(quán)過程都需要在瀏覽器中完成,啟動項目后訪問下列url
http://localhost:8080/oauth/authorize?client_id=client-a&client_secret=client-a-secret&response_type=code
登錄用戶/密碼: hellxz/xyz ,選擇Approve表示接受授權(quán),Deny反之,如下動圖所示
最后我們得到了回調(diào)地址http://localhost:9001/callback?code=2e6450
這里的code就是授權(quán)碼,接下來我們使用授權(quán)碼進(jìn)行換取token
POST請求,http://localhost:8080/oauth/token,參數(shù)如圖
BasicAuth:這里填的是客戶端配置的client_id和client_secret的值,相當(dāng)于curl --user client_id:client_secret
,配置后會在Header中添加Authorization:Basic Y2xpZW50LWE6Y2xpZW50LWEtc2VjcmV0
,Basic空格
后的是client_id:client_secret
具體值被Base64后得到的值
請求參數(shù)列表:
- code=授權(quán)碼
- grant_type=authorization_code
- redirect_uri=回調(diào)url ,要與配置處和獲取授權(quán)碼處相同
- scope=作用域
最后我們獲得了授權(quán)服務(wù)的響應(yīng),包含token的json
{ "access_token": "99435e13-f9fe-438a-a94e-3b00d549b329", //訪問token "token_type": "bearer", //token類型,使用時需要拼接在token前并在token前加空格 "expires_in": 43199, //過期時間 "scope": "read_user_info" //作用域 }
在access_token未過期之前,同一個用戶名使用同一個客戶端訪問都會是同一個access_token
授權(quán)服務(wù)器先放在這里,不要關(guān)服,接下來搭建資源服務(wù)器
搭建資源服務(wù)器(Resource Server)
資源服務(wù)器結(jié)構(gòu)
入口類不多說,先搭建資源服務(wù)器主要配置,這里直接使用ResourceConfig
進(jìn)行配置
package com.github.hellxz.oauth2.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; @Configuration @EnableResourceServer public class ResourceConfig extends ResourceServerConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Primary @Bean public RemoteTokenServices remoteTokenServices() { final RemoteTokenServices tokenServices = new RemoteTokenServices(); //設(shè)置授權(quán)服務(wù)器check_token端點(diǎn)完整地址 tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); //設(shè)置客戶端id與secret,注意:client_secret值不能使用passwordEncoder加密! tokenServices.setClientId("client-a"); tokenServices.setClientSecret("client-a-secret"); return tokenServices; } @Override public void configure(HttpSecurity http) throws Exception { //設(shè)置創(chuàng)建session策略 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); //@formatter:off //所有請求必須授權(quán) http.authorizeRequests() .anyRequest().authenticated(); //@formatter:on } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId("resource1").stateless(true); } }
1.通過@Configuration
和@EnableResourceServer
這兩個注解標(biāo)識服務(wù)是一個資源服務(wù)器,重寫ResourceServerConfigurerAdapter
來實(shí)現(xiàn)自定義授權(quán)服務(wù)器
2.配置configure(HttpSecurity http)
方法,這里可以代替Spring Security同名方法配置,開啟所有請求需要授權(quán)才可訪問
3.配置資源相關(guān)設(shè)置configure(ResourceServerSecurityConfigurer resources)
,這里只設(shè)置resourceId
后續(xù)的使用redis校驗(yàn)token也在這里設(shè)置
4.校驗(yàn)token的配置,這里使用了遠(yuǎn)程調(diào)用授權(quán)服務(wù)器幫忙校驗(yàn)token的方式,只需要顯示注入RemoteTokenServices remoteTokenServices()
的Bean,就可以調(diào)用授權(quán)服務(wù)器的/oauth/check_token端點(diǎn),設(shè)置客戶端配置的值,詳見注釋
這樣一來我們就配置好了資源服務(wù)器,當(dāng)然光有配置是不夠的,我們搞一個資源接口做測試用
上邊的ResourceController
與UserVO
都比較簡單,傳入一個名稱,返回用戶對象,包含用戶名和郵箱信息
package com.github.hellxz.oauth2.web.controller; import com.github.hellxz.oauth2.web.vo.UserVO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class ResourceController { @GetMapping("/user/{username}") public UserVO user(@PathVariable String username){ return new UserVO(username, username + "@foxmail.com"); } }
package com.github.hellxz.oauth2.web.vo; public class UserVO { private String username; private String email; public UserVO(String username, String email) { this.username = username; this.email = email; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
application.properties中配置了與授權(quán)服務(wù)器不同的端口:8081
server.port=8081
啟動資源服務(wù)測試
什么也不傳,直接訪問接口,提示資源需要授權(quán)
復(fù)制之前獲取到的token,添加token訪問接口http://localhost:8081/user/hellxz001
Bearer Token相當(dāng)于在Headers中添加Authorization:Bearer空格access_token
至此我們成功的搭建并測試了授權(quán)碼模式下的最簡單的授權(quán)服務(wù)與資源服務(wù)分離的demo
尾聲
授權(quán)碼模式就先在這里告一段落,寫的比較基礎(chǔ),自認(rèn)為該說到的點(diǎn)都說到了,后續(xù)還會寫其它模式的文章,如文中有何遺漏,請不吝評論反饋,本人會盡快改正,謝謝
本文以及后續(xù)文章的demo均放在GitHub上,歡迎大家Star & Fork,源碼地址:https://github.com/hellxz/spring-security-oauth2-learn ,Demo中的README文檔寫得比較詳細(xì),也可堪一看
原文出處https://www.cnblogs.com/hellxz/p/oauth2_oauthcode_pattern.html
到此這篇關(guān)于Spring Security OAuth2 授權(quán)碼模式的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring Security OAuth2 授權(quán)碼模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring gateway配置Spring Security實(shí)現(xiàn)統(tǒng)一權(quán)限驗(yàn)證與授權(quán)示例源碼
- Springboot使用Security實(shí)現(xiàn)OAuth2授權(quán)驗(yàn)證完整過程
- springboot+jwt+springSecurity微信小程序授權(quán)登錄問題
- Spring Security OAuth2認(rèn)證授權(quán)示例詳解
- Spring Security 控制授權(quán)的方法
- 詳解使用Spring Security OAuth 實(shí)現(xiàn)OAuth 2.0 授權(quán)
- Spring security中的授權(quán)
相關(guān)文章
Java concurrency之LockSupport_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency之LockSupport的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06實(shí)例詳解Java實(shí)現(xiàn)圖片與base64字符串之間的轉(zhuǎn)換
這篇文章主要介紹了Java實(shí)現(xiàn)圖片與base64字符串之間的轉(zhuǎn)換實(shí)例代碼,非常不錯,具有參考借鑒價值,需要的朋友參考下2016-12-12如何在Netty中注解使用Service或者M(jìn)apper
這篇文章主要介紹了如何在Netty中注解使用Service或者M(jìn)apper,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java RabbitMQ 中的消息長期不消費(fèi)會過期嗎
RabbitMQ支持消息的過期時間,在消息發(fā)送時可以進(jìn)行指定。 RabbitMQ支持隊列的過期時間,從消息入隊列開始計算,只要超過了隊列的超時時間配置,那么消息會自動的清除2021-09-09mybatis新增save結(jié)束后自動返回主鍵id詳解
這篇文章主要介紹了mybatis新增save結(jié)束后自動返回主鍵id詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12SpringBoot如何實(shí)現(xiàn)定時任務(wù)示例詳解
使用定時任務(wù)完成一些業(yè)務(wù)邏輯,比如天氣接口的數(shù)據(jù)獲取,定時發(fā)送短信,郵件。以及商城中每天用戶的限額,定時自動收貨等等,這篇文章主要給大家介紹了關(guān)于SpringBoot如何實(shí)現(xiàn)定時任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-10-10