在 Spring 授權伺服器的 JWT 存取權杖中新增權限作為自訂聲明
1. 概述
在許多場景中,向 JSON Web Token (JWT) 存取權杖新增自訂聲明至關重要。自訂聲明允許我們在令牌有效負載中包含附加資訊。
在本教程中,我們將學習如何將資源擁有者權限新增至 Spring 授權伺服器中的 JWT 存取權杖。
2.Spring授權伺服器
Spring Authorization Server 是 Spring 生態系統中的一個新項目,旨在為 Spring 應用程式提供 Authorization Server 支援。它旨在使用熟悉且靈活的 Spring 程式設計模型來簡化實作 OAuth 2.0 和 OpenID Connect (OIDC) 授權伺服器的過程。
2.1. Maven 依賴項
首先,我們將spring-boot-starter-web
、 spring-boot-starter-security
、 spring-boot-starter-test
和spring-security-oauth2-authorization-server
依賴項匯入到pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.5.4</version>
</dependency>
或者,我們可以將spring-boot-starter-oauth2-authorization-server
依賴項新增到pom.xml
檔中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
<version>3.2.0</version>
</dependency>
2.2.項目設定
讓我們設定 Spring 授權伺服器來頒發存取令牌。為了簡單起見,我們將使用 Spring Security OAuth 授權伺服器應用程式。
假設我們正在使用GitHub 上提供的授權伺服器專案。
3. 在 JWT 存取令牌中新增基本自訂聲明
在基於 Spring Security OAuth2 的應用程式中,**我們可以透過在授權伺服器中自訂令牌建立過程來為 JWT 存取權杖新增自訂聲明。**這種類型的聲明可用於將附加資訊注入 JWT,然後資源伺服器或身份驗證和授權流程中的其他元件可以使用這些資訊。
3.1.新增基本自訂聲明
我們可以使用OAuth2TokenCustomizer<JWTEncodingContext>
bean 將自訂聲明新增至存取權杖。透過使用它,授權伺服器頒發的每個存取權杖都將填充自訂聲明。
讓我們在DefaultSecurityConfig
類別中加入OAuth2TokenCustomizer
bean:
@Bean
@Profile("basic-claim")
public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
OAuth2TokenCustomizer
介面是 Spring Security OAuth2 函式庫的一部分,用於自訂 OAuth 2.0 令牌。在這種情況下,它在編碼過程中專門定制 JWT 令牌。
傳遞給jwtTokenCustomizer()
bean 的 lambda 表達式定義自訂邏輯。 context
參數表示令牌編碼過程中的JwtEncodingContext
。
首先,我們使用context.getTokenType()
方法檢查正在處理的令牌是否是存取令牌。然後,我們使用context.getClaims()
方法來取得與正在建構的 JWT 關聯的宣告。最後,我們為 JWT 添加自訂聲明。
在此範例中,新增了兩個具有對應值(「 claim-1
」和「value claim-2
value-1
」和「 value-2
」)。
3.2.測試自訂聲明
為了進行測試,我們將使用client_credentials
授予類型。
首先,我們將AuthorizationServerConfig
中的client_credentials
授予類型定義為RegisteredClient object
中的授權授予類型:
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
然後,我們在CustomClaimsConfigurationTest
類別中建立一個測試案例:
@ActiveProfiles(value = "basic-claim")
public class CustomClaimsConfigurationTest {
private static final String ISSUER_URL = "http://localhost:";
private static final String USERNAME = "articles-client";
private static final String PASSWORD = "secret";
private static final String GRANT_TYPE = "client_credentials";
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int serverPort;
@Test
public void givenAccessToken_whenGetCustomClaim_thenSuccess() throws ParseException {
String url = ISSUER_URL + serverPort + "/oauth2/token";
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(USERNAME, PASSWORD);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", GRANT_TYPE);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
ResponseEntity<TokenDTO> response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, TokenDTO.class);
SignedJWT signedJWT = SignedJWT.parse(response.getBody().getAccessToken());
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
Map<String, Object> claims = claimsSet.getClaims();
assertEquals("value-1", claims.get("claim-1"));
assertEquals("value-2", claims.get("claim-2"));
}
static class TokenDTO {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private String expiresIn;
@JsonProperty("scope")
private String scope;
public String getAccessToken() {
return accessToken;
}
}
}
讓我們瀏覽一下測試的關鍵部分,以了解發生了什麼:
- 首先建置 OAuth2 令牌端點的 URL。
- 從對令牌端點的 POST 請求中檢索包含
TokenDTO
類別的回應。在這裡,我們建立一個帶有標頭(基本驗證)和參數(授權類型)的 HTTP 請求實體。 - 使用
SignedJWT
類別從回應中解析存取令牌。此外,我們從 JWT 中提取聲明並將其儲存在Map<String, Object>
中。 - 使用 JUnit 斷言斷言 JWT 中的特定聲明具有預期值。
此測試確認我們的令牌編碼過程正常運作,並且我們的聲明正在按預期產生。驚人的!
另外,我們可以使用curl指令來取得存取令牌:
curl --request POST \
--url http://localhost:9000/oauth2/token \
--header 'Authorization: Basic YXJ0aWNsZXMtY2xpZW50OnNlY3JldA==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials
此處,憑證被編碼為客戶端 ID 和客戶端金鑰的 Base64 字串,並以單一冒號“:”分隔。
現在,我們可以使用設定檔basic-claim.
如果我們取得存取權杖並使用jwt.io對其進行解碼,我們會在令牌主體中找到測試聲明:
{
"sub": "articles-client",
"aud": "articles-client",
"nbf": 1704517985,
"scope": [
"articles.read",
"openid"
],
"iss": "http://auth-server:9000",
"exp": 1704518285,
"claim-1": "value-1",
"iat": 1704517985,
"claim-2": "value-2"
}
正如我們所看到的,測試索賠的價值符合預期。
我們將在下一節中討論新增權限作為存取權杖的聲明。
4. 將權限作為自訂聲明新增至 JWT 存取權令牌
將權限新增為 JWT 存取權杖的自訂聲明通常是 Spring Boot 應用程式中保護和管理存取的重要方面。權限通常由 Spring Security 中的GrantedAuthority
物件表示,指示允許使用者執行哪些操作或角色。透過將這些權限作為自訂聲明包含在 JWT 存取權杖中,我們為資源伺服器提供了一種方便且標準化的方式來了解使用者的權限。
4.1.新增權限作為自訂聲明
首先,我們使用簡單的記憶體使用者配置以及DefaultSecurityConfig
類別中的一組權限:
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
建立一個使用者名為「 admin
」、密碼為「 password
」、角色為「 USER
」的使用者。
現在,讓我們使用這些權限填充存取權杖中的自訂聲明:
@Bean
@Profile("authority-claim")
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(@Qualifier("users") UserDetailsService userDetailsService) {
return (context) -> {
UserDetails userDetails = userDetailsService.loadUserByUsername(context.getPrincipal().getName());
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
context.getClaims().claims(claims ->
claims.put("authorities", authorities.stream().map(authority -> authority.getAuthority()).collect(Collectors.toList())));
};
}
首先,我們定義一個實作OAuth2TokenCustomizer<JwtEncodingContext>
介面的 lambda 函數。該函數在編碼過程中自訂JWT。
然後,我們從注入的UserDetailsService
中檢索與目前主體(使用者)關聯的UserDetails
物件。委託人的姓名通常是使用者名稱。
之後,我們檢索與使用者關聯的GrantedAuthority
物件的集合。
最後,我們從JwtEncodingContext
檢索 JWT 聲明並應用自訂。它包括向 JWT 添加名為“ authorities
”的自訂聲明。此外,此聲明還包含從與使用者關聯的GrantedAuthority
物件取得的權限字串清單。
4.2.檢驗當局的說法
現在我們已經配置了授權伺服器,讓我們測試一下。為此,我們將使用GitHub 上提供的客戶端-伺服器專案。
讓我們建立一個 REST API 客戶端,它將從存取權杖中取得聲明清單:
@GetMapping(value = "/claims")
public String getClaims(
@RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient
) throws ParseException {
SignedJWT signedJWT = SignedJWT.parse(authorizedClient.getAccessToken().getTokenValue());
JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
Map<String, Object> claims = claimsSet.getClaims();
return claims.get("authorities").toString();
}
@RegisteredOAuth2AuthorizedClient
註解在 Spring Boot 控制器方法中使用,指示該方法期望使用指定的客戶端 ID 註冊 OAuth 2.0 授權客戶端。在本例中,客戶端 ID 為「 articles-client-authorization-code
」。
讓我們使用設定檔authority-claim.
現在,當我們進入瀏覽器並嘗試造訪http://127.0.0.1:8080/claims
頁面時,我們將自動重新導向至http://auth-server:9000/login
URL 下的 OAuth 伺服器登入頁面。
提供正確的使用者名稱和密碼後,授權伺服器會將我們重新導向回所要求的 URL,即聲明清單。
5. 結論
總的來說,為 JWT 存取權令牌添加自訂聲明的能力提供了一種強大的機制,可以根據應用程式的特定需求自訂令牌,並增強身份驗證和授權系統的整體安全性和功能。
在本文中,我們學習如何在 Spring 授權伺服器中的 JWT 存取權杖中新增自訂聲明和使用者權限。
與往常一樣,完整的源代碼可以 在 GitHub 上取得。