Json web token (JWT) is an open standard based on JSON, which is used to transfer statements between network application environments( RFC 7519 )The token is designed to be compact and secure, which is especially suitable for SSO scenarios of distributed sites. JWT declaration is generally used to transfer the authenticated user identity information between identity provider and service provider, so as to obtain resources from resource server. It can also add some additional declaration information necessary for other business logic. The token can also be directly used for authentication or encryption.
JWT composition
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE1MTY3MjY4MTMsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6ImJmZmY0NjRjLTFiNTktNGZkNy1hNTE4LWU3YjY5MDFiNzU3YyIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.gp5t9nY9mGp5O2-yqdflc0nEAsTeCQG7VugA8q8XcF4
Header
The Header contains some metadata, at least indicating the token type and signature method.
{ "typ": "JWT", "alg": "HS256" }
Claims (Payload)
The Claims section contains some important information about this token.
{ "user_name": "admin", "scope": [ "all" ], "exp": 1516726813, "blog": "https://longfeizheng.github.io/", "authorities": [ "ROLE_USER" ], "jti": "bfff464c-1b59-4fd7-a518-e7b6901b757c", "client_id": "merryyou" }
Signature
The JWT standard generates signatures according to the JSON Web Signature (JWS) standard. Signature is mainly used to verify whether the token is valid or tampered.
JWT flow diagram
Spring Security Oauth2 implementation of JWT
Configure TokenStoreConfig to store tokens
@Configuration public class TokenStoreConfig { /** * redis Connection factory */ @Autowired private RedisConnectionFactory redisConnectionFactory; /** * Used to store token * @return */ @Bean @ConditionalOnProperty(prefix = "merryyou.security.oauth2", name = "storeType", havingValue = "redis") public TokenStore redisTokenStore() { return new RedisTokenStore(redisConnectionFactory); } /** * jwt TOKEN configuration information */ @Configuration @ConditionalOnProperty(prefix = "merryyou.security.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true) public static class JwtTokenCofnig{ /** * Using jwtTokenStore to store tokens * @return */ @Bean public TokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } /** * For generating jwt * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); accessTokenConverter.setSigningKey("merryyou");//Generate signed key return accessTokenConverter; } /** * Used to extend JWT * @return */ @Bean @ConditionalOnMissingBean(name = "jwtTokenEnhancer") public TokenEnhancer jwtTokenEnhancer(){ return new MerryyouJwtTokenEnhancer(); } } }
MerryyouJwtTokenEnhancer
public class MerryyouJwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> info = new HashMap<>(); info.put("blog", "https://longfeizheng.github.io / "); / / token returned by extension ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); return accessToken; } }
Configure authentication server
@Configuration @EnableAuthorizationServer public class MerryyouAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired(required = false) private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired(required = false) private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); //Extension token return result if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> enhancerList = new ArrayList(); enhancerList.add(jwtTokenEnhancer); enhancerList.add(jwtAccessTokenConverter); tokenEnhancerChain.setTokenEnhancers(enhancerList); //jwt endpoints.tokenEnhancer(tokenEnhancerChain) .accessTokenConverter(jwtAccessTokenConverter); } } /** * Configure some client information * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("merryyou") .secret("merryyou") .accessTokenValiditySeconds(7200) .authorizedGrantTypes("refresh_token", "password", "authorization_code")//Authentication mode supported by OAuth2 .scopes("all"); } }
Configure resource server
@Configuration @EnableResourceServer public class MerryyouResourceServerConfig extends ResourceServerConfigurerAdapter { /** * Custom login success processor */ @Autowired private AuthenticationSuccessHandler appLoginInSuccessHandler; @Override public void configure(HttpSecurity http) throws Exception { http.formLogin() .successHandler(appLoginInSuccessHandler)//Login success processor .and() .authorizeRequests().anyRequest().authenticated().and() .csrf().disable(); } }
Parsing extended Token
@GetMapping("/user") public Object getCurrentUser1(Authentication authentication, HttpServletRequest request) throws UnsupportedEncodingException { log.info("[SecurityOauth2Application] getCurrentUser1 authenticaiton={}", JsonUtil.toJson(authentication)); String header = request.getHeader("Authorization"); String token = StringUtils.substringAfter(header, "bearer "); Claims claims = Jwts.parser().setSigningKey("merryyou".getBytes("UTF-8")).parseClaimsJws(token).getBody(); String blog = (String) claims.get("blog"); log.info("[SecurityOauth2Application] getCurrentUser1 blog={}", blog); return authentication; }
test method
@Test public void signInTest() throws Exception { RestTemplate rest = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.add("authorization", getBasicAuthHeader()); MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("username", "admin"); params.add("password", "123456"); HttpEntity<?> entity = new HttpEntity(params, headers); // pay attention, if using get with headers, should use exchange instead of getForEntity / getForObject ResponseEntity<String> result = rest.exchange(SIGN_IN_URI, HttpMethod.POST, entity, String.class, new Object[]{null}); log.info("Results returned by login information={}", JsonUtil.toJson(result)); }
Print:
"body": "{\"access_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE1MTY3MjkxNDIsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjMzOTUxNDk1LTBjOGYtNGQ5NS1iZDYyLTAxMjEyYWNjZDU1ZCIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.7Lrpmn3CaNweqcMeADJeZJGDTEZYN-gg5OpAzbKIEqQ\",\"token_type\":\"bearer\",\"refresh_token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiIzMzk1MTQ5NS0wYzhmLTRkOTUtYmQ2Mi0wMTIxMmFjY2Q1NWQiLCJleHAiOjE1MTkzMTM5NDIsImJsb2ciOiJodHRwczovL2xvbmdmZWl6aGVuZy5naXRodWIuaW8vIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjFlMjI1YzE5LTE5NDMtNGNjMi1iYTdjLTM1MzdmZDA1M2E4MyIsImNsaWVudF9pZCI6Im1lcnJ5eW91In0.lKHgXd2HSPCp2cK6S-ZvLUwXRjnXEX9wryDWV4CmSGw\",\"expires_in\":7199,\"scope\":\"all\",\"blog\":\"https://longfeizheng.github.io/\",\"jti\":\"33951495-0c8f-4d95-bd62-01212accd55d\"}"
The effect is as follows:
Code download
Download from my github, https://github.com/longfeizheng/security-oauth2