OAuth2.0的四種授權模式:
配置一個授權服務,你需要考慮幾種授權類型(Grant Type),不同的授權類型為客戶端(Client)提供了不同的獲取令牌(Token)方式,為了實現並確定這幾種授權,需要配置使用 ClientDetailsService 和 TokenService 來開啟或者禁用這幾種授權機制。到這里就請注意了,不管你使用什么樣的授權類型(Grant Type),每一個客戶端(Client)都能夠通過明確的配置以及權限來實現不同的授權訪問機制。這也就是說,假如你提供了一個支持"client_credentials"的授權方式,並不意味着客戶端就需要使用這種方式來獲得授權。
-
authorization_code:授權碼類型。
-
-
password:資源所有者(即用戶)密碼類型。
-
client_credentials:客戶端憑據(客戶端ID以及Key)類型。
-
refresh_token:通過以上授權獲得的刷新令牌來獲取新的令牌。
可以用 @EnableAuthorizationServer 注解來配置OAuth2.0 授權服務機制,通過使用@Bean注解的幾個方法一起來配置這個授權服務。下面咱們介紹幾個配置類,這幾個配置是由Spring創建的獨立的配置對象,它們會被Spring傳入AuthorizationServerConfigurer中:
-
ClientDetailsServiceConfigurer:用來配置客戶端詳情服務(ClientDetailsService),客戶端詳情信息在這里進行初始化,你能夠把客戶端詳情信息寫死在這里或者是通過數據庫來存儲調取詳情信息。
-
AuthorizationServerSecurityConfigurer:用來配置令牌端點(Token Endpoint)的安全約束.
-
AuthorizationServerEndpointsConfigurer:用來配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)。
(注:以下1.*中對所有代碼都配置在繼承AuthorizationServerConfigurerAdapter的配置類中)
1.1、配置客戶端詳情信息(Client Details):
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一個回調配置項,見上的概述) 能夠使用內存或者JDBC來實現客戶端詳情服務(ClientDetailsService),有幾個重要的屬性如下列表:
-
clientId:(必須的)用來標識客戶端的Id。
-
secret:(需要值得信任的客戶端)客戶端安全碼,如果有的話。
-
scope:用來限制客戶端的訪問范圍,如果為空(默認)的話,那么客戶端擁有全部的訪問范圍。
-
authorizedGrantTypes:此客戶端可以使用的授權類型,默認為空。
-
authorities:此客戶端可以使用的權限(基於Spring Security authorities)。
另:在使用JDBC模式時,數據庫會建表存儲標識客戶端Id的client_id(對應配置中的clientId)和client_secret(對應配置中的secret),注意這里的客戶端並非是指瀏覽器或者是app,而是第三方需要獲取授權的應用:例如百度網盤可以用QQ登錄,百度網盤作為客戶端消費的是QQ的資源(用戶頭像、用戶名、年齡等),所以百度網盤需要先向QQ官方申請client_Id和client_secret(雙方都將其記住),以后只要百度網盤有用戶用QQ登錄都會使用代表百度網盤的唯一client_Id告訴QQ的授權服務器,當前用戶是通過百度網盤客戶端過來申請消費資源的。QQ授權服務器通過百度網盤發送過來的請求攜帶的參數校驗client_id和client_secret是否正確,校驗成功后在發放token令牌。
代碼選自:
@Autowired private ClientDetailsService clientDetailsService; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); /* clients.inMemory() .withClient("c1") .secret(new BCryptPasswordEncoder().encode("secret"))//$2a$10$0uhIO.ADUFv7OQ/kuwsC1.o3JYvnevt5y3qX/ji0AUXs4KYGio3q6 .resourceIds("r1") .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token") .scopes("all") .autoApprove(false) .redirectUris("https://www.baidu.com");*/ }
代碼選自:
//為了測試客戶端與憑證存儲在內存(生產應該用數據庫來存儲,oauth有標准數據庫模板) @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client1-code") // client_id .secret(bCryptPasswordEncoder.encode("123")) // client_secret .authorizedGrantTypes("authorization_code") // 該client允許的授權類型 .scopes("app") // 允許的授權范圍 .redirectUris("https://www.baidu.com") .resourceIds("goods", "mechant") //資源服務器id,需要與資源服務器對應 .and() .withClient("client2-credentials") .secret(bCryptPasswordEncoder.encode("123")) .authorizedGrantTypes("client_credentials") .scopes("app") .resourceIds("goods", "mechant") .and() .withClient("client3-password") .secret(bCryptPasswordEncoder.encode("123")) .authorizedGrantTypes("password") .scopes("app") .resourceIds("mechant") .and() .withClient("client4-implicit") .authorizedGrantTypes("implicit") .scopes("app") .resourceIds("mechant"); }
代碼選自:
// 數據庫連接池對象,SpringBoot 配置完成后自動注入 @Autowired private DataSource dataSource; // 客戶端信息來源 @Bean public ClientDetailsService jdbcClientDetailsService(){ return new JdbcClientDetailsService(dataSource); } // 指定客戶端信息的數據庫來源 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(jdbcClientDetailsService()); }
客戶端詳情(Client Details)能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務(例如將客戶端詳情存儲在一個關系數據庫的表中,就可以使用 JdbcClientDetailsService)或者通過 ClientDetailsManager 接口(同時你也可以實現 ClientDetailsService 接口)來進行管理。
1.2、管理令牌(Managing Token):
AuthorizationServerTokenServices 接口定義了一些操作使得你可以對令牌進行一些必要的管理,在使用這些操作的時候請注意以下幾點:
-
當一個令牌被創建了,你必須對其進行保存,這樣當一個客戶端使用這個令牌對資源服務進行請求的時候才能夠引用這個令牌。
-
當一個令牌是有效的時候,它可以被用來加載身份信息,里面包含了這個令牌的相關權限。
代碼選自:
@Autowired private TokenStore tokenStore; @Autowired private ClientDetailsService clientDetailsService; @Bean public AuthorizationServerTokenServices tokenServices(){ DefaultTokenServices services = new DefaultTokenServices(); services.setClientDetailsService(clientDetailsService); services.setSupportRefreshToken(true); services.setTokenStore(tokenStore); services.setAccessTokenValiditySeconds(7200); services.setRefreshTokenValiditySeconds(259200); return services; }
代碼選自:
// 令牌管理 @Bean public AuthorizationServerTokenServices tokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); // token 保存策略 tokenServices.setTokenStore(tokenStore()); // 支持刷新模式 tokenServices.setSupportRefreshToken(true); // 客戶端信息來源 tokenServices.setClientDetailsService(jdbcClientDetailsService()); // token 有效期自定義設置,默認 12 小時 tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // refresh token 有效期自定義設置,默認 30 天 tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7); return tokenServices; }
當你自己創建 AuthorizationServerTokenServices 這個接口的實現時,你可能需要考慮一下使用 DefaultTokenServices 這個類,里面包含了一些有用實現,你可以使用它來修改令牌的格式和令牌的存儲。默認的,當它嘗試創建一個令牌的時候,是使用隨機值來進行填充的,除了持久化令牌是委托一個 TokenStore 接口來實現以外,這個類幾乎幫你做了所有的事情。並且 TokenStore 這個接口有一個默認的實現,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了內存中。除了使用這個類以外,你還可以使用一些其他的預定義實現,下面有幾個版本,它們都實現了TokenStore接口:
-
InMemoryTokenStore:這個版本的實現是被默認采用的,它可以完美的工作在單服務器上(即訪問並發量壓力不大的情況下,並且它在失敗的時候不會進行備份),大多數的項目都可以使用這個版本的實現來進行嘗試,你可以在開發的時候使用它來進行管理,因為不會被保存到磁盤中,所以更易於調試。
-
JdbcTokenStore:這是一個基於JDBC的實現版本,令牌會被保存進關系型數據庫。使用這個版本的實現時,你可以在不同的服務器之間共享令牌信息,使用這個版本的時候請注意把"spring-jdbc"這個依賴加入到你的classpath當中。
-
JwtTokenStore:這個版本的全稱是 JSON Web Token(JWT),它可以把令牌相關的數據進行編碼(因此對於后端服務來說,它不需要進行存儲,這將是一個重大優勢),但是它有一個缺點,那就是撤銷一個已經授權令牌將會非常困難,所以它通常用來處理一個生命周期較短的令牌以及撤銷刷新令牌(refresh_token)。另外一個缺點就是這個令牌占用的空間會比較大,如果你加入了比較多用戶憑證信息。JwtTokenStore 不會保存任何數據,但是它在轉換令牌值以及授權信息方面與 DefaultTokenServices 所扮演的角色是一樣的。
1.3、管理訪問端點配置:
AuthorizationServerEndpointsConfigurer 這個對象的實例可以完成令牌服務以及令牌 endpoint 配置。通過設定以下屬性決定支持的授權類型(Grant Types):
-
authenticationManager
:認證管理器,當選擇了密碼(password)授權類型的時候,請設置這個屬性注入一個 AuthenticationManager 對象。 -
userDetailsService
:如果你設置了這個屬性的話,那說明你有一個自己的 UserDetailsService 接口的實現,或者你可以把它設置到全局域上面去,當你設置了這個之后,那么 refresh_token 即刷新令牌授權類型模式的流程中就會包含一個檢查,用來確保這個賬號是否仍然有效。 -
authorizationCodeServices
:這個屬性是用來設置授權碼服務的,主要用於 authorization_code 授權碼類型模式。 -
implicitGrantService
:這個屬性用於設置隱式授權模式,用來管理隱式授權模式的狀態。 -
tokenGranter
:當你設置了這個東西,那么授權將會交由你來完全掌控,並且會忽略掉上面的這幾個屬性,這個屬性一般是用作拓展用途的,即標准的四種授權模式已經滿足不了你的需求的時候,才會考慮使用這個。
代碼選自:
@Autowired private AuthenticationManager authenticationManager; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) //證管理器,當選擇了密碼(password)授權類型的時候,請設置這個屬性注入一個 AuthenticationManager 對象。 .authorizationCodeServices(authorizationCodeServices) .tokenServices(tokenServices()) .allowedTokenEndpointRequestMethods(HttpMethod.POST); endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access"); }
代碼選自:
@Autowired private AuthenticationManager authenticationManager; // OAuth2 的主配置信息 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .approvalStore(approvalStore()) .authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices()) .tokenServices(tokenServices()); } }
代碼選自:
@Autowired private AuthenticationManager authenticationManager //配置token倉庫 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //authenticationManager配合password模式使用 endpoints.authenticationManager(authenticationManager) //這里使用內存存儲token,也可以使用redis和數據庫 .tokenStore(new InMemoryTokenStore()); endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); endpoints.tokenEnhancer(new TokenEnhancer() { @Override public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { //在返回token的時候可以加上一些自定義數據 DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) oAuth2AccessToken; Map<String, Object> map = new LinkedHashMap<>(); map.put("nickname", "測試姓名"); token.setAdditionalInformation(map); return token; } });
AuthorizationServerEndpointsConfigurer 這個配置對象有一個叫做 pathMapping() 的方法用來配置端點 URL 鏈接,它有兩個參數,第一個參數:String 類型的,這個端點URL的默認鏈接。第二個參數:String 類型的,你要進行替代的 URL 鏈接。以上的參數都將以 /
字符為開始的字符串,框架的默認 URL 鏈接如下列表,可以作為 pathMapping() 方法的第一個參數:
-
/oauth/authorize
:授權端點。 -
/oauth/token
:令牌端點。 -
/oauth/confirm_access
:用戶確認授權提交端點。 -
/oauth/error
:授權服務錯誤信息端點。 -
/oauth/check_token
:用於資源服務訪問的令牌解析端點。 -
/oauth/token_key
:提供公有密匙的端點,如果你使用JWT令牌的話。
2、資源服務配置
一個資源服務(可以和授權服務在同一個應用中,當然也可以分離開成為兩個不同的應用程序)提供一些受token令牌保護的資源,Spring OAuth提供者是通過Spring Security authentication filter 即驗證過濾器來實現的保護,你可以通過 @EnableResourceServer 注解到一個 @Configuration 配置類上,並且必須使用 ResourceServerConfigurer 這個配置對象來進行配置(可以選擇繼承自 ResourceServerConfigurerAdapter 然后覆寫其中的方法,參數就是這個對象的實例),下面是一些可以配置的屬性:
-
tokenServices:ResourceServerTokenServices 類的實例,用來實現令牌服務。
-
resourceId:這個資源服務的ID,這個屬性是可選的,但是推薦設置並在授權服務中進行驗證。
-
其他的拓展屬性例如 tokenExtractor 令牌提取器用來提取請求中的令牌。
-
請求匹配器,用來設置需要進行保護的資源路徑,默認的情況下是受保護資源服務的全部路徑。
-
受保護資源的訪問規則,默認的規則是簡單的身份驗證(plain authenticated)。
-
其他的自定義權限保護規則通過 HttpSecurity 來進行配置。
@EnableResourceServer 注解自動增加了一個類型為 OAuth2AuthenticationProcessingFilter 的過濾器鏈
(注:以下2.*中對所有代碼都配置在繼承ResourceServerConfigurerAdapter的配置類中)
代碼來自:
@Bean public ResourceServerTokenServices resourceServerTokenServices(){ RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); remoteTokenServices.setCheckTokenEndpointUrl("http://127.0.0.1:30000/oauth/check_token"); remoteTokenServices.setClientId("c1"); remoteTokenServices.setClientSecret("secret"); return remoteTokenServices; }
3、運行流程及源碼講解:
3.1、
根據以下代碼進行自繪圖:
@EnableResourceServer注解: