目錄
前言
1.介紹
2.Oauth2.0過程詳解?
3.Oauth 整合到 Spring Boot 實踐?
4.方法及配置詳解:
總結
前言
Oauth2.0 是非常流行的網絡授權表準,已經廣泛應用在全球范圍內,比較大的公司,如騰訊等都有大量的應用場景。
1.介紹
Oauth2.0 全稱是 Open Authorization,是一種開放授權協議。目前使用的版本是 2.0 版本,也就是?Oauth2.0,它主要用于授權認證環節。
從官網文檔可知道 Oauth 具有如下特點:
- 需要第三方進行授權,會存儲用戶登錄授權憑據。
- 服務器必須支持密碼認證。
- 有限范圍內獲取所有者(用戶)的部分數據。?
簡而言之,就是用于第三方在用戶授權的前提下獲取用戶的相關信息。而 Oauth2.0 的授權模式比較多,常用的有如下四種:
- 授權碼模式:最常規的模式,適用于有服務器端的第三方應用(如網頁應用)。在這種模式下,用戶代理(如瀏覽器)首先被重定向到授權服務器進行身份驗證,授權服務器通過用戶代理回調客戶端,并提供一個授權碼。客戶端隨后會使用這個授權碼向授權服務器后端請求訪問令牌(access token),支持刷新的 token。
- Clinet 模式:適用于客戶端應用直接訪問它們自己的資源服務器,沒有用戶直接參與的場景。在這個模式中,客戶端應用憑借自己的憑據(客戶端ID及客戶端密鑰)向授權服務器請求訪問令牌。其他應用或者程序通過 api 進行調用,獲取對應的 token。
- 簡化模式:用于沒有服務器端能力的客戶端應用,如 JavaScript 應用。在簡化模式中,客戶端通過用戶代理直接從授權服務器獲取訪問令牌,不通過中間的授權碼交換步驟。由于這種模式暴露了訪問令牌,因此不如授權碼模式安全。
- 密碼模式?:如果用戶對第三方應用高度信任(例如,設備操作系統或具備原生應用的第三方服務),則可以直接將用戶名和密碼提供給應用,應用使用這些憑據直接從授權服務器獲取訪問令牌。由于此流程涉及明文傳遞用戶憑據,因此安全性較低。
總結:從中其實可以看出,最關鍵的部分就是這三方相互確認的過程。授權碼模式就可以看成三方之間都不信任,所以需要不斷地相互確認;而簡化模式則可以看作授權服務器對第三方應用比較信任,表示直接就給了你我的令牌,你并不會拿我的令牌去做壞事,也就是說它們之間比較互信;密碼模式則是資源擁有者對第三方應用比較信任,可以將自己的信息放心的放給第三方;客戶端模式則可以看成三方都比較信任,不需要過多的驗證,請求就給令牌就行了,比較適合我們內部的應用場景。
梳理 Oauth2.0 中有一個主線,就是三方參與者之間的信任程度。
2.Oauth2.0過程詳解?
前面我們對四個常見的模式進行了詳細的了解,為了更加方便理解,對基本的過程再次進行詳細解釋。Oauth 的基本認正過程如圖,分為三個角色:用戶、第三方、以及認證服務器。
首先,用戶去訪問第三方應用,第三方應用引導用戶進行授權,跳轉到授權頁面。用戶進行授權后將數據傳遞給認證服務器,認證服務器返回 code 給第三方應用,第三方應用發起新的請求來獲取訪問授權令牌(access token),最后用戶獲取到授權結果。?
3.Oauth 整合到 Spring Boot 實踐?
客戶端憑證是最常用的認證方式之一。例如,微信授權就是這種方式,通過攜帶 access token 來獲取用戶資源。
新建Spring Boot 項目,實現方案是使用 SpringSecurity 和 Oauth2.0 模塊,pom.xml 添加如下依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.5.RELEASE</version></dependency>
為了實現 Oauth 認證,需要對兩方面進行配置,一是認證服務配置,包含 token?定義,用戶客戶端的信息以及授權服務和令牌服務。二是需要對資源服務進行配置,如資源訪問權限設置,哪些需要 token 驗證。
首先,編寫認證服務配置,定義授權以及令牌服務。
package org.example.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
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.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;/*** 認證服務器類** @author freejava*/
@Configuration
//此注解不像其他的 Enable注解一樣內部有 Component 注解
@EnableAuthorizationServer
public class MySuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate AuthorizationCodeServices authorizationCodeServices;//令牌訪問安全策略@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {//用于表單方式提交 client_id,client_secretsecurity.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();}@Beanpublic PasswordEncoder noOpPasswordEncoder(){return NoOpPasswordEncoder.getInstance();}/*** 配置客戶端信息 哪些客戶端可以發送請求* @param clients 定義客戶端信息的配置們,可以初始化客戶端信息。* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()//client_id.withClient("myClientId")// client_secret.secret(NoOpPasswordEncoder.getInstance().encode("123456"))//授權方式.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token").resourceIds("res1")//可以訪問哪些資源//授權范圍.scopes("all")//write.autoApprove(false)//false跳轉到授權頁面//加上驗證回調地址.redirectUris("http://www.baidu.com");}@Autowiredprivate ClientDetailsService clientDetailsService;// 令牌服務@Beanpublic AuthorizationServerTokenServices tokenService(){DefaultTokenServices services = new DefaultTokenServices();services.setClientDetailsService(clientDetailsService);services.setSupportRefreshToken(true);services.setTokenStore(tokenStore());services.setAccessTokenValiditySeconds(7200);//2小時services.setRefreshTokenValiditySeconds(259200);//刷新令牌默認有效期 3 天return services;}//設置授權碼模式如何存儲@Beanpublic AuthorizationCodeServices authorizationCodeServices() {return new InMemoryAuthorizationCodeServices();}private String SIGNING_KEY = "uaa123";//內存方式@Beanpublic TokenStore tokenStore(){return new InMemoryTokenStore();//new JwtTokenStore(accessTokenConverter());}/* @Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey(SIGNING_KEY); //對稱秘鑰,資源服務器使用該秘鑰來驗證return converter;}*//*** 定義授權和令牌服務* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//super.configure(endpoints);endpoints.authenticationManager(authenticationManager).authorizationCodeServices(authorizationCodeServices)//授權碼服務.tokenServices(tokenService())//令牌管理服務.allowedTokenEndpointRequestMethods(HttpMethod.POST,HttpMethod.GET);//允許POST提交}
}
資源配置編寫,先編寫一個 Controller 文件,代碼如下:
package org.example.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ResController {@GetMapping("/res/{id}")public String testOauth(@PathVariable String id) {return "Get the resource " + id;}
}
然后編寫該資源的訪問權限配置,代碼如下:
package org.example.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.TokenStore;/*** 用于攔截請求的配置類*/
@Configuration
@EnableResourceServer
public class MyResourceServerConfigurer extends ResourceServerConfigurerAdapter {@Autowiredprivate TokenStore tokenStore;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {//這里重點注意resources.tokenStore(tokenStore).resourceId("res1");}/*** 用于攔截 http 請求* @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/oauth/**").permitAll() // 允許所有人訪問 /oauth/* 下面的內容。.anyRequest().authenticated(); // 其他所有請求都需要認證。/*http.authorizeRequests().anyRequest().authenticated();//("/res/**").authenticated();*/}
}
為授權碼模式編寫一個webconfig:
package org.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;/*** @author Administrator* @version 1.0**/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//認證管理器@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}//安全攔截機制(最重要)@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/orther/**").hasAnyAuthority("p1").antMatchers("/login*").permitAll().anyRequest().authenticated().and().formLogin();}
}
通過 Postman 進行測試,訪問 localhost:8080/res/1 會返回一個 unauthorized 的錯誤返回,這里需要傳遞 access token,所以需要先請求獲取 access token 的接口 /oauth/token,之后再用該token進行請求即可。?
4.方法及配置詳解:
ClientDetailsServiceConfigurer
?詳解:?
ClientDetailsServiceConfigurer
?是用于配置客戶端詳細信息服務的類。客戶端詳細信息服務定義了客戶端(應用程序)的信息,這包括客戶端ID、客戶端密鑰、授權模式、令牌有效期以及重定向的URI等信息。客戶端詳細信息可以通過硬編碼、數據庫等方式來存儲和管理。
以下是?ClientDetailsServiceConfigurer
?的一些常用方法:
-
inMemory()
在內存中配置客戶端存儲,主要用于測試或簡單應用程序,不適合生產使用。 -
jdbc(DataSource dataSource)
使用 JDBC 連接數據庫,并在數據庫中維護客戶端信息。 -
withClient(String clientId)
創建新的客戶端,并設置其客戶端id。 -
secret(String secret)
設置客戶端的密鑰。對于使用Spring Security 5的應用程序,需要注意密碼存儲格式,通常是?{bcrypt}
、{noop}
?等。 -
redirectUris(String… redirectUris)
設置客戶端的重定向URI,這些URI在OAuth2授權碼模式下使用。 -
authorizedGrantTypes(String… authorizedGrantTypes)
設置客戶端允許的授權模式,如?authorization_code
,?password
,?refresh_token
,?client_credentials
。 -
authorities(String… authorities)
為客戶端設置Spring Security的權限。 -
scopes(String… scopes)
設置客戶端的作用域(scope),限定客戶端的訪問范圍。 -
accessTokenValiditySeconds(int accessTokenValiditySeconds)
設置訪問令牌的有效時間(以秒計)。 -
refreshTokenValiditySeconds(int refreshTokenValiditySeconds)
設置刷新令牌的有效時間(以秒計)。 -
additionalInformation(Map<String, ?> additionalInformation)
設置一個包含其他客戶端詳細信息的Map。 -
autoApprove(boolean autoApprove)
如果設置為true,則不需要用戶在授權階段進行手動批準。 -
autoApprove(String… scopes)
指定特定范圍的自動批準。 -
resourceIds(String… resourceIds)
限定客戶端可以訪問的資源服務器的ID。?
AuthorizationServerEndpointsConfigurer詳解:
默認得到訪問路徑:
- /oauth/authorize: 授權端點
- /oauth/token : 令牌端點。
- /loauth/confirm_access : 用戶確認授權提交端點。
- /loauth/error: 授權服務錯誤信息端點。
- /oauth/check_token : 用于資源服務訪問的令牌解析端點
- /oauth/token_key : 提供公有密匙的端點,如果你使用JWT令牌的話.?
AuthorizationServerEndpointsConfigurer
是Spring Security OAuth2中用于定義授權服務器端點配置的類,它允許你定義與認證相關的各種參數和實現。也就是配置令牌的訪問端點(發放令牌的地址)和令牌服務(如何發放),下面是一些?AuthorizationServerEndpointsConfigurer
?的主要配置方法,以及它們的用途:
-
authenticationManager(AuthenticationManager authenticationManager)
設置認證管理器。在password
授權模式下,需要配置此項來驗證用戶的用戶名和密碼。 -
userDetailsService(UserDetailsService userDetailsService)
設置用戶細節服務。如果你設置了一個自定義的用戶細節服務,可以通過這個方法來配置。 -
authorizationCodeServices(AuthorizationCodeServices authorizationCodeServices)
設置授權碼服務。用于authorization_code
授權類型。這能夠改變默認的授權碼存儲方式(通常是內存)。 -
tokenStore(TokenStore tokenStore)
設置令牌存儲方式。可以使用各種不同的存儲方式,如內存、JDBC或JWT tokens。 -
accessTokenConverter(AccessTokenConverter accessTokenConverter)
設置訪問令牌轉換器。例如,使用JWT token時,可以配置一個JwtAccessTokenConverter
。 -
tokenEnhancer(TokenEnhancer tokenEnhancer)
設置令牌增強器。可以用來擴展JWT token內容。 -
approvalStore(ApprovalStore approvalStore)
設置批準存儲。這跟token的保存方式類似,定義了用戶批準記錄的存儲方式。 -
reuseRefreshTokens(boolean reuseRefreshTokens)
設置是否復用refresh tokens。默認為true,即當新的access token被創建后,原始的refresh token將會被復用。 -
refreshTokenGranter(TokenGranter refreshTokenGranter)
設置自定義的refresh token授權者。 -
grantTypes(TokenGranter tokenGranter)
設置自定義授權類型,可以添加或覆蓋默認的授權類型來擴展功能。 -
tokenValidator(OAuth2TokenValidator<Jwt> tokenValidator)
在使用JWT時設置令牌驗證器,用以驗證JWT的有效性。 -
tokenServices(DefaultTokenServices tokenServices)
設置token服務的實例。這個服務負責創建和管理OAuth2 tokens。 -
exceptionTranslator(WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator)
設置異常翻譯器。用來定義認證異常的響應格式。 -
requestFactory(OAuth2RequestFactory requestFactory)
設置OAuth2請求工廠。這允許你插入自定義邏輯來解析傳入的請求。?
AuthorizationServerSecurityConfigurer :
用來配置令牌端點的安全約束,比如哪些人可以訪問。
ResourceServerSecurityConfigurer詳解
:
ResourceServerSecurityConfigurer
?是 Spring Security OAuth2 中用于配置資源服務器的安全細節的一個類。它允許你設置一些關于資源服務器的關鍵配置,比如 tokenStore、resourceId 和 tokenServices 等。下面是一些?ResourceServerSecurityConfigurer
?的常用方法及其簡要說明:
-
tokenStore(TokenStore tokenStore)
設置用于讀取令牌信息的?TokenStore
。這個?TokenStore
?被資源服務器用來解碼訪問令牌。常見的實現是?JdbcTokenStore
、JwtTokenStore
?和?InMemoryTokenStore
。 -
resourceId(String resourceId)
設置此資源服務器的資源ID。這通常用于客戶端請求的 Scope 限制,以及在多個資源服務器存在時加以區分。 -
tokenServices(TokenServices tokenServices)
設置?ResourceServerTokenServices
?實例,這是用來解碼訪問令牌的另一種方式。如果你的令牌是 JWT,則可以提供?JwtTokenServices
。 -
tokenExtractor(TokenExtractor tokenExtractor)
設置用于提取訪問令牌的?TokenExtractor
。默認情況下,Spring OAuth2 使用?BearerTokenExtractor
?來處理來自請求頭或請求參數的 Bearer 令牌。 -
authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint)
定制用于處理認證錯誤的?AuthenticationEntryPoint
。通常情況下,這用于處理資源服務器返回給客戶端的401響應。 -
accessDeniedHandler(AccessDeniedHandler accessDeniedHandler)
設置自定義?AccessDeniedHandler
?以處理接收到訪問被拒絕時的情況。這通常用來自定義服務器對403 Forbidden響應的處理。 -
stateless(Boolean stateless)
設置此資源服務器是否只關心無狀態請求應答。這確定了資源服務器是否會在請求間創建?HttpSession
。 -
authenticationManager(AuthenticationManager authenticationManager)
設置?AuthenticationManager
。如果需要,可以用來登陸用戶或驗證用戶的訪問令牌。 -
eventPublisher(ApplicationEventPublisher eventPublisher)
設置?ApplicationEventPublisher
?以發布認證事件。這可以用來記錄認證成功或者失敗等事件。
總結
OAuth 2.0 是一個用于授權的開放標準,由 IETF OAuth 工作組于 2012 年發布。它允許用戶授權第三方應用訪問其在某一服務上的信息,而無需將用戶名和密碼提供給第三方應用,大大促進了當前互聯網間的信息共享。