目錄
- 前言
- 1. JWT 令牌存儲基礎知識
- 1.1 JSON Web Token
- 2. 建構使用 JWT 令牌存儲的 OAuth2 伺服器
- 2.1 引入 pom.xml 依賴檔案
- 2.2 建立 JWT 令牌存儲
- 2.3 将 JWT 挂載到驗證服務中
- 3. 在受保護服務中使用 JWT
- 3.1 引入 pom.xml 依賴檔案
- 3.2 在受保護服務中建立 JWTTokenStoreConfig 類
- 3.3 建立自定義的 RestTemplate 類以注入 JWT 令牌
- 3.4 UserContextInterceptor 将注入 JWT 令牌到 REST 調用
- 3.5 擴充 JWT 令牌(也叫自定義、增強)
- 3.6 告訴被保護服務使用 JWTTokenEnhancer 類
- 3.7 使用 JJWT 庫在 Zuul 網關裡解析 JWT 令牌中解析自定義字段
- 3.7.1 在 Zuul 網關裡添加 pom.xml 依賴檔案
- 3.7.2 添加一個新方法,用來解析 serviceId
- 最後
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚矽谷 SpringCloud 架構開發教程 周陽》
JWT 為 OAuth2 令牌提供規範标準,并且可以自定義 JWT 令牌;
- OAuth2 是一個基于令牌的驗證架構,但它并沒有為如何定義其規範中的令牌提供任何标準;
- 由此出現了 JSON Web Token ( JWT )新标準;
- JWT是網際網路工程任務組( Internet Engineering Task Force, IETF )提出的開放标準(RFC-7519 ),旨在為 0Auth2令牌提供标準結構
- JWT 令牌的特點:小巧、密碼簽名、自包含、可擴充;
- Spring Cloud Security 為 JWT 提供了開箱即用的支援;
<!-- JWT OAuth2 庫 -->
<dependency>
<groupid>org.springframework.security</groupid>
<artifactid>spring-security-jwt</artifactid>
</dependency>
<!--security 通用安全庫-->
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-security</artifactid>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
在 security 包或 config 包下;
@Configration
public class JWTTokenStoreConfig{
@Autowired
private ServiceConfig serviceConfig;
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
//用于從出示給服務的令牌中讀取資料
@Bean
@Primary //用于告訴 Spring,如果有多個特定類型的 bean(本例中為 DefaultTokenService),那麼使用 @Primary 标注的 Bean 類型自動注入
public DefaultTikenServices tokenServices(){
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
//在 JWT 和 OAuth2 伺服器之間充當翻譯
JwtAccessTokenConverter conver = new JwtAccessTokenConverter();
//定義将用于簽署令牌的簽名密鑰
conver.setSigningKey(serviceConfig.getJwtSigningKey());
}
@Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JWTTokenEnhancer();
}
}
- 該類用于定義 Spring 将如何管理 JWT 令牌的建立、簽名和翻譯;
@Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
endpoints.tokenStore(tokenStore) //JWT,5.2中定義的指令存儲将在這裡注入
.accessTokenConverter(jwtAccessTokenConverter) //JWT,鈎子,用于告訴 Spring Security OAuth2 代碼使用 JWT
.tokenEnhancer(tokenEnhancerChain) //JWT
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("eagleeye")
.secret("thisissecret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
}
- 同 OAuth2 伺服器依賴;
<!-- JWT OAuth2 庫 -->
<dependency>
<groupid>org.springframework.security</groupid>
<artifactid>spring-security-jwt</artifactid>
</dependency>
<!--security 通用安全庫-->
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-security</artifactid>
</dependency>
<!--oauth2.0-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
- 同本篇《5.2 建立 JWT 令牌存儲》;
@Configuration
public class JWTTokenStoreConfig {
@Autowired
private ServiceConfig serviceConfig;
//JWT
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
//JWT
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
//JWT
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(serviceConfig.getJwtSigningKey());
return converter;
}
}
可以在主程式類裡,也可以在主程式類所在包及其子包裡;
@Primary
@Bean
public RestTemplate getCustomRestTemplate() {
RestTemplate template = new RestTemplate();
List interceptors = template.getInterceptors();
if (interceptors == null) {
//UserContextInterceptor 會将 Authorization 首部注入每個 REST 調用
template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
} else {
interceptors.add(new UserContextInterceptor());
template.setInterceptors(interceptors);
}
return template;
}
public class UserContextInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(
HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add(UserContext.CORRELATION_ID, UserContextHolder.getContext().getCorrelationId());
//将授權令牌添加到 HTTP 首部
headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken());
return execution.execute(request, body);
}
}
- 通過向被保護服務添加一個 Spring OAuth2 令牌增強類,擴充 JWT 令牌;
//需要擴充 TokenEnhancer 類
public class JWTTokenEnhancer implements TokenEnhancer {
@Autowired
private OrgUserRepository orgUserRepo;
//基于使用者名查找使用者的組織 ID
private String getOrgId(String userName){
UserOrganization orgUser = orgUserRepo.findByUserName( userName );
return orgUser.getOrganizationId();
}
//進行這種增強,需要覆寫 enhance() 方法
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
String orgId = getOrgId(authentication.getName());
additionalInfo.put("organizationId", orgId);
//所有附加屬性放在 HashMap 中,并設定在傳入該方法的 accessToken 變量上
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
- 首先需要公開 TokenEnhancer 類;
@Bean
public TokenEnhancer jwtTokenEnhancer() {
return new JWTTokenEnhancer();
}
- 将 TokenEnhancer 類挂鈎在驗證服務中。類似《2.3 将 JWT 挂載到驗證服務中》;
@Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private DefaultTokenServices tokenServices;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//自動裝配 TokenEnhancer 類
@Autowired
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//允許開發人員挂鈎多個令牌增強器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
endpoints.tokenStore(tokenStore) //JWT
.accessTokenConverter(jwtAccessTokenConverter) //JWT
.tokenEnhancer(tokenEnhancerChain) //JWT,将令牌增強器鍊挂鈎到傳入 configure() 方法的 endpoints 參數
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("eagleeye")
.secret("thisissecret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
}
以下配置均在在 Zuul 網關服務裡進行;
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
private String getOrganizationId(){
String result="";
if (filterUtils.getAuthToken()!=null){
//從 HTTP 首部 Authorization 解析出令牌
String authToken = filterUtils.getAuthToken().replace("Bearer ","");
try {
//傳入用于簽署令牌的簽名密鑰,使用 JWTS 類解析令牌
Claims claims = Jwts.parser()
.setSigningKey(serviceConfig.getJwtSigningKey().getBytes("UTF-8"))
.parseClaimsJws(authToken).getBody();
//從令牌中提取 xxxId
result = (String) claims.get("organizationId");
}
catch (Exception e){
e.printStackTrace();
}
}
return result;
}
新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公衆号,會分享一些更日常的東西!
如需轉載,請标注出處!