preface
<Spring Microservices in Action>
Principle and practice of Spring Cloud Alibaba microservice
"Spring cloud framework development tutorial in Silicon Valley of station B" Zhou Yang
JWT provides specifications and standards for OAuth2 tokens, and JWT tokens can be customized;
1. Basic knowledge of JWT token storage
1.1 JSON Web Token
- OAuth2 is a token based authentication framework, but it does not provide any standard for how to define tokens in its specification;
- Thus, a new JSON web token (JWT) standard appears;
- JWT is an open standard (RFC-7519) proposed by the Internet Engineering Task Force (IETF), which aims to provide a standard structure for 0Auth2 tokens
- JWT token features: compact, password signature, self-contained and extensible;
- Spring Cloud Security provides out of the box support for JWT;
2. Build OAuth2 server using JWT token storage
2.1 introduction of POM XML dependency file
<!-- JWT OAuth2 library --> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-jwt</artifactid> </dependency> <!--security General security library--> <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>
2.2 create JWT token store
Under security package or config package;
@Configration public class JWTTokenStoreConfig{ @Autowired private ServiceConfig serviceConfig; @Bean public TokenStore tokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } //Used to read data from the token presented to the service @Bean @Primary //It is used to tell Spring that if there are multiple beans of specific types (DefaultTokenService in this case), the Bean type marked by @ Primary will be automatically injected public DefaultTikenServices tokenServices(){ DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ //Act as a translator between JWT and OAuth2 servers JwtAccessTokenConverter conver = new JwtAccessTokenConverter(); //Define the signing key that will be used to sign the token conver.setSigningKey(serviceConfig.getJwtSigningKey()); } @Bean public TokenEnhancer jwtTokenEnhancer(){ return new JWTTokenEnhancer(); } }
- This class is used to define how Spring will manage the creation, signature and translation of JWT tokens;
2.3 mount JWT into the authentication service
@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) //The command store defined in JWT, 5.2 will be injected here .accessTokenConverter(jwtAccessTokenConverter) //JWT, hook, used to tell Spring Security OAuth2 code to use 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"); } }
3. Use JWT in protected services
3.1 introduction of POM XML dependency file
- It depends on OAuth2 server;
<!-- JWT OAuth2 library --> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-jwt</artifactid> </dependency> <!--security General security library--> <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>
3.2 create JWTTokenStoreConfig class in the protected service
- The same as 5.2 creating JWT token storage in this chapter;
@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; } }
3.3 create a custom RestTemplate class to inject JWT token
It can be in the main program class, or in the package where the main program class is located and its sub packages;
@Primary @Bean public RestTemplate getCustomRestTemplate() { RestTemplate template = new RestTemplate(); List interceptors = template.getInterceptors(); if (interceptors == null) { //UserContextInterceptor will inject the Authorization header into each REST call template.setInterceptors(Collections.singletonList(new UserContextInterceptor())); } else { interceptors.add(new UserContextInterceptor()); template.setInterceptors(interceptors); } return template; }
3.4 UserContextInterceptor will inject JWT token into REST call
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()); //Add authorization token to HTTP header headers.add(UserContext.AUTH_TOKEN, UserContextHolder.getContext().getAuthToken()); return execution.execute(request, body); } }
3.5 extend JWT token (also known as customization and enhancement)
- Extend the JWT token by adding a Spring OAuth2 token enhancement class to the protected service;
//TokenEnhancer class needs to be extended public class JWTTokenEnhancer implements TokenEnhancer { @Autowired private OrgUserRepository orgUserRepo; //Finds the user's organization ID based on the user name private String getOrgId(String userName){ UserOrganization orgUser = orgUserRepo.findByUserName( userName ); return orgUser.getOrganizationId(); } //For this enhancement, you need to override the enhance() method @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); String orgId = getOrgId(authentication.getName()); additionalInfo.put("organizationId", orgId); //All additional properties are placed in the HashMap and set on the accessToken variable passed into the method ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
3.6 tell the protected service to use JWTTokenEnhancer class
- First, you need to expose the TokenEnhancer class;
@Bean public TokenEnhancer jwtTokenEnhancer() { return new JWTTokenEnhancer(); }
- Hook the TokenEnhancer class into the authentication service. Similar to 2.3 attaching JWT to authentication service;
@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; //Auto assemble TokenEnhancer class @Autowired private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //Allow developers to hook multiple token enhancers TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter)); endpoints.tokenStore(tokenStore) //JWT .accessTokenConverter(jwtAccessTokenConverter) //JWT .tokenEnhancer(tokenEnhancerChain) //JWT, hook the token enhancer chain to the endpoints parameter passed into the configure() method .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"); } }
3.7 use JJWT library to parse custom fields in JWT token in Zuul gateway
The following configurations are carried out in Zuul gateway service;
3.7.1 add POM in Zuul gateway XML dependency file
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
3.7.2 add a new method to resolve serviceId
private String getOrganizationId(){ String result=""; if (filterUtils.getAuthToken()!=null){ //Parse token from HTTP header Authorization String authToken = filterUtils.getAuthToken().replace("Bearer ",""); try { //Pass in the signature key used to sign the token, and use the JWTS class to resolve the token Claims claims = Jwts.parser() .setSigningKey(serviceConfig.getJwtSigningKey().getBytes("UTF-8")) .parseClaimsJws(authToken).getBody(); //Extract xxxId from token result = (String) claims.get("organizationId"); } catch (Exception e){ e.printStackTrace(); } } return result; }