天天看點

Spring Security OAuth 2.0

續·前一篇《OAuth 2.0》

OAuth 2.0 Provider 實作

在OAuth 2.0中,provider角色事實上是把授權服務和資源服務分開,有時候它們也可能在同一個應用中,用Spring Security OAuth你可以選擇把它們分成兩個應用,當然多個資源服務可以共享同一個授權服務。

擷取token的請求由Spring MVC的控制端點處理,通路受保護的資源由标準的Spring Security請求過濾器處理。

(The requests for the tokens are handled by Spring MVC controller endpoints, and access to protected resources is handled by standard Spring Security request filters. )

為了實作OAuth 2.0授權伺服器,在Spring Security的過濾器鍊中需要有以下端點:

  • AuthorizationEndpoint    用于服務授權請求。預設URL是/oauth/authorize
  • TokenEndpoint    用于服務通路令牌請求。預設URL是/oauth/token

在OAuth 2.0的資源伺服器中需要實作下列過濾器:

  • OAuth2AuthenticationProcessingFilter    用于加載認證

對于所有的OAuth 2.0 provider特性,最簡單的配置是用Spring OAuth @Configuration擴充卡。

Authorization Server 配置

隻要你配置了授權伺服器,那麼你應該考慮用戶端用于擷取access token的授權類型(例如,授權碼,使用者憑證,重新整理token)。

伺服器的配置是用來提供client detail服務和token服務的,并且可以啟用或者禁用全局的某些機制。

每個用戶端可以配置不同的權限

@EnableAuthorizationServer注解被用來配置授權伺服器,也可以和實作了AuthorizationServerConfigurer接口的任意被标記為@Bean的Bean一起來對授權伺服器進行配置。

下列特性被委托給AuthorizationServerConfigurer:

  • ClientDetailsServiceConfigurer  :a configurer that defines the client details service
  • AuthorizationServerSecurityConfigurer  :defines the security constraints on the token endpoint
  • AuthorizationServerEndpointsConfigurer  :defines the authorization and token endpoints and the token services

一件重要的事情是,provider配置了将授權碼給OAuth用戶端的方式(PS:在授權碼類型授權過程中)

OAuth用戶端通過将end-user(最終使用者)導向授權頁,使用者可用在此輸入他的憑證。之後,授權伺服器攜帶授權碼通過重定向的方式将授權碼傳回給用戶端。

配置 Client Details

The ClientDetailsServiceConfigurer (a callback from your AuthorizationServerConfigurer) can be used to define an in-memory or JDBC implementation of the client details service.

ClientDetailsServiceConfigurer可用使用client details service的兩種實作中的任意一種:in-memory 或者 JDBC 

用戶端重要的屬性是:

  • clientId  :(必須的)用戶端ID
  • secret  :(對于信任的用戶端需要)用戶端秘鑰
  • scope  :用戶端被限定的範圍。如果scope為定義或者為空(預設為空)則用戶端不受scope限制
  • authorizedGrantTypes  :用戶端使用到的授權類型
  • authorities  :授予用戶端的權限

用戶端details可以在應用運作時被更新,通過直接通路存儲(例如:如果用JdbcClientDetailsService的話可以實時改變資料庫表中的資料)或者通過實作ClientDetailsManager接口(它們也都實作了ClientDetailsService接口)。

NOTE: the schema for the JDBC service is not packaged with the library (because there are too many variations you might like to use in practice), but there is an example you can start from in the test code in github.

注意:用于JDBC服務的資料庫schema并沒有打包到library中(因為你再實際使用的時候可能有諸多差異),但是這裡有一個例子你可以參考一下。

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

1 -- used in tests that use HSQL
 2 create table oauth_client_details (
 3     client_id VARCHAR(256) PRIMARY KEY,
 4     resource_ids VARCHAR(256),
 5     client_secret VARCHAR(256),
 6     scope VARCHAR(256),
 7     authorized_grant_types VARCHAR(256),
 8     web_server_redirect_uri VARCHAR(256),
 9     authorities VARCHAR(256),
10     access_token_validity INTEGER,
11     refresh_token_validity INTEGER,
12     additional_information VARCHAR(4096),
13     autoapprove VARCHAR(256)
14 );
15 
16 create table oauth_client_token (
17     token_id VARCHAR(256),
18     token LONGVARBINARY,
19     authentication_id VARCHAR(256) PRIMARY KEY,
20     user_name VARCHAR(256),
21     client_id VARCHAR(256)
22 );
23 
24 create table oauth_access_token (
25     token_id VARCHAR(256),
26     token LONGVARBINARY,
27     authentication_id VARCHAR(256) PRIMARY KEY,
28     user_name VARCHAR(256),
29     client_id VARCHAR(256),
30     authentication LONGVARBINARY,
31     refresh_token VARCHAR(256)
32 );
33 
34 create table oauth_refresh_token (
35     token_id VARCHAR(256),
36     token LONGVARBINARY,
37     authentication LONGVARBINARY
38 );
39 
40 create table oauth_code (
41     code VARCHAR(256), authentication LONGVARBINARY
42 );
43 
44 create table oauth_approvals (
45     userId VARCHAR(256),
46     clientId VARCHAR(256),
47     scope VARCHAR(256),
48     status VARCHAR(10),
49     expiresAt TIMESTAMP,
50     lastModifiedAt TIMESTAMP
51 );
52 
53 
54 -- customized oauth_client_details table
55 create table ClientDetails (
56     appId VARCHAR(256) PRIMARY KEY,
57     resourceIds VARCHAR(256),
58     appSecret VARCHAR(256),
59     scope VARCHAR(256),
60     grantTypes VARCHAR(256),
61     redirectUrl VARCHAR(256),
62     authorities VARCHAR(256),
63     access_token_validity INTEGER,
64     refresh_token_validity INTEGER,
65     additionalInformation VARCHAR(4096),
66     autoApproveScopes VARCHAR(256)
67 );      

管理Tokens

AuthorizationServerTokenServices定義了管理OAuth 2.0 Token所必須的操作。請注意:

  • 當建立一個access token的時候,這個認證必須被存儲起來,以便後續通路資源的時候對接收到的access token進行引用校驗。
  • access token用來加載認證

當你實作了AuthorizationServerTokenServices接口,你可能考慮用DefaultTokenServices。有許多内置的插件化的政策可以用來改變access token的格式和存儲。

預設情況下,用随機值來生成token,并且用TokenService來處理所有(除了token持久化以外)事情。預設的存儲是in-memory實作,但是有其它的實作可以使用。

  • 對于單伺服器而言,預設的InMemoryTokenStore是完美的。大多數的項目是從這裡開始的,為了使它很容易啟動,也不需要其它依賴,并且可能以開發模式進行操作。
  • JdbcTokenStore是JDBC版本的Token存儲。它把Token資料存儲到關系型資料庫中。為了使用JdbcTokenStore需要classpath下有"spring-jdbc"。
  • JSON Web Token (JWT) 它将授權的token的所有資料進行編碼後存儲(沒有使用後端存儲是它最大的優勢)。這種方式的一個缺點是你不能很容易的撤銷一個access token,是以一般用該方式存儲的token的有效期很短,并且在重新整理token的時候之前的token會被廢除。另一個缺點是,token很長,因為它裡面存了很多關于使用者憑證的資訊。JwtTokenStore不會真的存儲資料,它不持久化任何資料。但是在DefaultTokenServices中,它扮演着token值和認證資訊轉換的角色。

注意:對于JDBC的schema沒有打包到library中,但是這兒有一個例子你可以參考一下test code in github。確定用@EnableTransactionManagement來防止多個用戶端在同一行建立token。注意,示例中的schema都有明确地主鍵聲明,在并發環境中這是必須的。

JWT Tokens

為了使用JWT Tokens,你需要在你的授權伺服器中有一個JwtTokenStore。資源伺服器也需要解碼這個token,是以JwtTokenStore有一個依賴JwtAccessTokenConverter,相同的實作需要被包含在授權伺服器和資源伺服器中。也就是說,授權伺服器和資源伺服器中都需要JwtTokenStore實作。預設情況下,token是被簽名的,而且資源伺服器必須能夠校驗這個簽名,是以需要有相同的對稱key,或者需要公鑰來比對授權伺服器上的私鑰。公鑰被授權伺服器暴露在/oauth/token_key端點,預設情況下這個端點的通路規則是"denyAll()"。你可以用标準的SpEL表達式(例如:permitAll())到AuthorizationServerSecurityConfigurer來開放它。

為了使用JwtTokenStore,在classpath下需要有"spring-security-jwt"

Grant Types

授權類型通過AuthorizationEndpoint來支援。預設情況下,除了password以外,所有授權類型都支援。下面是授權類型的一些屬性:

  • authenticationManager  :通過注入一個AuthenticationManager來切換成password授權
  • userDetailsService  :如果你注入一個UserDetailsService或者以任意方式配置了一個全局的UserDetailsService(例如:在GlobalAuthenticationManagerConfigurer中),那麼一個重新整理token将被包含在user detail中,為了強制賬戶是激活的。
  • authorizationCodeServices  :定義授權碼服務(AuthorizationCodeServices的執行個體)
  • implicitGrantService  :在隐式授權期間管理狀态
  • tokenGranter  :tokenGranter

Configuring the Endpoint URLs

AuthorizationServerEndpointsConfigurer有一個pathMapping()方法。它有兩個參數:

  • 端點的預設URL路徑
  • 自定義的路徑(必須以"/"開頭)

下面是架構提供的URL路徑:

  • /oauth/authorize    授權端點
  • /oauth/token    令牌端點
  • /oauth/confirm_access    使用者準許授權的端點
  • /oauth/error    用于渲染授權伺服器的錯誤
  • /oauth/check_token    資源伺服器解碼access token
  • /oauth/check_token    當使用JWT的時候,暴露公鑰的端點

授權端點/oauth/authorize應該被保護起來,以至于它隻能被認證過的使用者通路。下面是一個例子,用标準的Spring Security WebSecurityConfigurer :

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests().antMatchers("/login").permitAll().and()
        // default protection for all resources (including /oauth/authorize)
            .authorizeRequests()
                .anyRequest().hasRole("USER")
        // ... more configuration, e.g. for form login
    }      

注意:如果您的授權伺服器同時也是一個資源伺服器的話,那麼就有另一個具有較低優先級的安全過濾器鍊來控制API資源。通過通路令牌來保護這些請求,你需要它們的路徑不能與主使用者過濾器鍊中的那些相比對,是以請確定包含一個請求matcher,它隻挑選出上面的WebSecurityConfigurer中的非api資源。

Customizing the UI

授權伺服器的大多數端點主要都是被機器使用的,但是有兩個資源是需要UI,它們分别是/oauth/confirm_access和HTML響應/oauth/error。架構為它們提供的實作是空白頁,真實的情況是大多數授權伺服器可能想要提供它們自己的實作來控制樣式和内容。是以,你需要做的事情就是提供一個Spring MVC 被标注了@RequestMappings注解的Controller來映射這些端點,并且架構将用一個低的優先級來發放請求。在預設的/oauth/confirm_access你期望一個AuthorizationRequest綁定到session。你可以抓取請求的所有資料并按照自己喜歡的方式渲染它們,然後使用者需要做的就是向/oauth/authorize發送關于準許或拒絕授予的資訊。預設的UserApprovalHandler取決于是否你再AuthorizationServerEndpointsConfigurer中提供了一個ApprovalStore。标準的審批處理器如下:

  • TokenStoreUserApprovalHandler  :通過user_oauth_approval做一個簡單的yes/no決定等同于“true”或“false”
  • ApprovalStoreUserApprovalHandler  :一組"scope*"參數key。參數的值可以是"true"或者"approval"。至少有一個scope是approval才算是授權成功。(A grant is successful if at least one scope is approved.)

強制SSL

純HTTP對于測試來說是可以的,但是在生成中授權伺服器應該使用SSL。你可以在一個安全的容器或代理後面運作應用程式,如果你正确地設定代理和容器(這與OAuth2無關),那麼它應該可以正常工作。對于/authorize端點你需要把它當作正常的應用安全的一部分來做,對于/token端點在AuthorizationServerEndpointsConfigurer中有一個标記可以設定,通過用sslOnly()方法。

自定義錯誤處理

授權伺服器用标準的Spring MVC特性來進行錯誤處理。

你可以提供自己的實作,通過添加@Controller并且帶上@RequestMapping("/oauth/error")

Mapping User Roles to Scopes

有時候,為了限制token的scope,不僅僅要根據指定的用戶端的範圍,也要根據使用者自己的權限來進行限制。如果你在你的AuthorizationEndpoint用DefaultOAuth2RequestFactory,你可以設定checkUserScopes=true來限制比對的使用者角色的允許範圍。AuthorizationServerEndpointsConfigurer允許你注入一個自定義的OAuth2RequestFactory

資源伺服器配置

一個資源伺服器(可能與授權伺服器是相同的應用,也可能與授權伺服器是分開的應用)通過OAuth2 Token服務受保護的資源。

Spring OAuth 提供一個Spring Security認證過濾器來實作這個保護。你可以

你可以在一個@Configuration類上用@EnableResourceServer來切換它,并且用ResourceServerConfigurer配置它。下列特性可以被配置:

  • tokenServices  :一個ResourceServerTokenServices的執行個體
  • resourceId  :資源ID(推薦的,如果存在的話會被授權伺服器校驗)
  • 資源伺服器的其它擴充端點
  • request matchers for protected resources (defaults to all)
  • access rules for protected resources (defaults to plain "authenticated")
  • 其它通過HttpSecurity配置的自定義的受保護的資源

@EnableResourceServer注釋将自動添加一個OAuth2AuthenticationProcessingFilter類型的過濾器到Spring安全過濾器鍊中。

OAuth 2.0 用戶端

受保護的資源配置

受保護的資源(或者叫遠端資源)可以用OAuth2ProtectedResourceDetails類型的bean來定義。一個被保護的資源由下列屬性:

  • id :資源的id。這個id隻是用于用戶端查找資源。
  • clientId :OAuth Client id。
  • clientSecret :關聯的資源的secret。預設非空
  • accessTokenUri :提供access_token的端點的uri
  • scope :逗号分隔的字元串,代表通路資源的範圍。預設為空
  • clientAuthenticationScheme :用戶端認證所采用的schema。建議的值:"http_basic"和"form"。預設是"http_basic"。

不同的授權類型有不同的OAuth2ProtectedResourceDetails的具體實作(例如:ClientCredentialsResourceDetails是"client_credentials"類型的具體實作)

  • userAuthorizationUri :使用者授權uri,非必需的。

用戶端配置

對于OAuth 2.0用戶端配置,簡化的配置用@EnableOAuth2Client。這個注解做兩件事情:

  • 建立一個過濾器(ID是oauth2ClientContextFilter)來存儲目前的請求和上下文。在請求期間需要進行身份認證時,它管理重定向URI。
  • 在請求範圍内建立一個AccessTokenRequest類型的bean。對于授權代碼(或隐式)授予用戶端是很有用的,可以避免與單個使用者相關的狀态發生沖突。

AccessTokenRequest可以用在一個OAuth2RestTemplate中,就像下面這樣:

@Autowired
private OAuth2ClientContext oauth2Context;

@Bean
public OAuth2RestTemplate sparklrRestTemplate() {
    return new OAuth2RestTemplate(sparklr(), oauth2Context);
}      

通路受保護的資源

建議用RestTemplate通路受保護的資源。

Spring Security為OAuth提供了一個擴充的RestTemplate隻需要你提供一個OAuth2ProtectedResourceDetails的執行個體即可。為了使它和使用者token(授權碼方式授權)一起使用,你應該考慮用@EnableOAuth2Client配置。

一般來說,web應用程式不應該使用密碼授予,是以如果您可以支援AuthorizationCodeResourceDetails,請避免使用ResourceOwnerPasswordResourceDetails。

為了和使用者令牌(授權碼)一起使用,你應該考慮用@EnableOAuth2Client配置。

用戶端持久化Token

用戶端不需要持久化令牌,但是最好不要在每次重新開機用戶端應用程式時都要求使用者準許新的令牌授予。

ClientTokenServices接口定義了為特定使用者儲存OAuth 2.0令牌所需的操作。這是一個JDBC實作,但是如果您希望實作自己的服務,以便在持久資料庫中存儲通路令牌和相關的身份驗證執行個體,則可以這樣做。如果你想要使用這個特性,你需要為OAuth2RestTemplate提供一個經過特殊配置的TokenProvider。例如:

@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations restTemplate() {
    OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
    AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider()));
    provider.setClientTokenServices(clientTokenServices());
    return template;
}      

參考

https://projects.spring.io/spring-security-oauth/docs/oauth2.html