SpringSecurityOAuth使用JWT Token
⒈JWT?
JWT(Json Web Token),是Json的一个开放的Token标准。
1,自包含,SpringSecurityOAuth的默认Token是UUID的一个随机的无意义的字符串,并不包含任何信息,信息是被单独存放的,我们还需要通过这个令牌从单独存放信息的存储那里获取信息,所以说SpringSecurityOAuth的默认Token比较依赖于存储,一旦存储信息的存储那里出现了故障,那么这个令牌就毫无用处了。而JWT Token中是包含有意义的信息的,当我们的系统拿到这个令牌以后,我们可以直接解析这个令牌来获取有用的信息。
2,密签,我们可以对发出去的令牌进行签名(指令牌被人篡改我们可以获知)
3,可扩展,Token中的信息我们可以根据业务需求进行扩展。
⒉如何在SpringSecurityOAuth中使用JWT Token替换掉默认的UUID Token
1.
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-security</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-data-redis</artifactId> 8 </dependency> 9 <dependency> 10 <groupId>org.springframework.boot</groupId> 11 <artifactId>spring-boot-starter-web</artifactId> 12 </dependency> 13 <dependency> 14 <groupId>org.springframework.security.oauth</groupId> 15 <artifactId>spring-security-oauth2</artifactId> 16 <version>2.3.5.RELEASE</version> 17 </dependency> 18 <dependency> 19 <groupId>commons-collections</groupId> 20 <artifactId>commons-collections</artifactId> 21 <version>3.2.2</version> 22 </dependency> 23 <dependency> 24 <groupId>org.springframework.boot</groupId> 25 <artifactId>spring-boot-starter-test</artifactId> 26 <scope>test</scope> 27 </dependency> 28 <dependency> 29 <groupId>org.springframework.security</groupId> 30 <artifactId>spring-security-test</artifactId> 31 <scope>test</scope> 32 </dependency>
2.
1 package cn.coreqi.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Conditional; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.data.redis.connection.RedisConnectionFactory; 8 import org.springframework.security.oauth2.provider.token.TokenStore; 9 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 10 import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 11 import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; 12 13 @Configuration 14 public class TokenStoreConfig { 15 16 @Autowired 17 private RedisConnectionFactory redisConnectionFactory; 18 19 /** 20 * TokenStore 负责令牌的存取 21 * @return 22 */ 23 // @Bean 24 // public TokenStore redisTokenStore(){ 25 // return new RedisTokenStore(redisConnectionFactory); 26 // } 27 28 @Configuration 29 public static class JwtTokenConfig { 30 /** 31 * Token生成中的处理 32 * @return 33 */ 34 @Bean 35 public JwtAccessTokenConverter jwtAccessTokenConverter(){ 36 JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); 37 accessTokenConverter.setSigningKey("fanqi"); //Token签名用的密钥 38 //发出去的令牌需要密钥签名,验令牌的时候也需要令牌来验签,如果他人获知了我们的密钥 39 //就可以用我们的密钥来签发我们的JWT令牌,JWT唯一的安全性就是密钥 40 //别人用我们的密钥来签发我们的JWT令牌就可以随意进入我们的系统 41 return accessTokenConverter; 42 } 43 44 @Bean 45 public TokenStore jwtTokenStore(){ 46 return new JwtTokenStore(jwtAccessTokenConverter()); 47 } 48 } 49 }
3.
1 package cn.coreqi.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.beans.factory.annotation.Qualifier; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.data.redis.connection.RedisConnectionFactory; 8 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 10 import org.springframework.security.core.userdetails.UserDetailsService; 11 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 12 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 13 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 14 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 15 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 16 import org.springframework.security.oauth2.provider.token.TokenStore; 17 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 18 import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; 19 20 @Configuration 21 @EnableAuthorizationServer //开启认证服务器 22 public class CoreqiAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 23 24 @Autowired 25 //@Qualifier("authenticationManagerBean") 26 private AuthenticationManager authenticationManager; 27 28 @Autowired 29 private UserDetailsService userDetailsService; 30 31 32 @Autowired 33 private TokenStore jwtTokenStore; 34 35 @Autowired(required = false) 36 private JwtAccessTokenConverter jwtAccessTokenConverter; 37 38 // @Autowired 39 // private AuthenticationConfiguration authenticationConfiguration; 40 41 /** 42 * 针对端点的配置 43 * @param authorizationServerEndpointsConfigurer 44 * @throws Exception 45 */ 46 @Override 47 public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception { 48 //authorizationServerEndpointsConfigurer.authenticationManager(authenticationConfiguration.getAuthenticationManager()); 49 authorizationServerEndpointsConfigurer.tokenStore(jwtTokenStore) //使用JwtToken 50 .authenticationManager(authenticationManager) 51 .userDetailsService(userDetailsService); 52 if(jwtAccessTokenConverter != null){ 53 authorizationServerEndpointsConfigurer.accessTokenConverter(jwtAccessTokenConverter); 54 } 55 } 56 57 /** 58 * 第三方应用客户端的有关配置 59 * @param clientDetailsServiceConfigurer 60 * @throws Exception 61 */ 62 @Override 63 public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception { 64 clientDetailsServiceConfigurer.inMemory() 65 .withClient("coreqi") //client_id 66 .secret("coreqiSecret") //client_id的密码 67 .accessTokenValiditySeconds(7200) //令牌的有效时间(单位秒) 68 .redirectUris("https://www.baidu.com") 69 .scopes("all","read","write") //所支持的权限有那些 70 .authorities("COREQI_READ") 71 .authorizedGrantTypes("authorization_code","password"); //针对当前client所支持的授权模式 72 } 73 74 /** 75 * 针对安全性有关的配置 76 * @param security 77 * @throws Exception 78 */ 79 @Override 80 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 81 super.configure(security); 82 } 83 }
⒊如何扩展Jwt Token中的信息
1 package cn.coreqi.jwt; 2 3 import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; 4 import org.springframework.security.oauth2.common.OAuth2AccessToken; 5 import org.springframework.security.oauth2.provider.OAuth2Authentication; 6 import org.springframework.security.oauth2.provider.token.TokenEnhancer; 7 8 import java.util.HashMap; 9 import java.util.Map; 10 11 public class CoreqiJwtTokenEnhancer implements TokenEnhancer { 12 13 @Override 14 public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { 15 Map<String,Object> info = new HashMap<>(); //要往accessToken中存放的信息 16 info.put("company","fanqi"); 17 ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info); //设置附加信息 18 return accessToken; 19 } 20 }
1 package cn.coreqi.config; 2 3 import cn.coreqi.jwt.CoreqiJwtTokenEnhancer; 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Conditional; 7 import org.springframework.context.annotation.Configuration; 8 import org.springframework.data.redis.connection.RedisConnectionFactory; 9 import org.springframework.security.oauth2.provider.token.TokenEnhancer; 10 import org.springframework.security.oauth2.provider.token.TokenStore; 11 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 12 import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; 13 import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; 14 15 @Configuration 16 public class TokenStoreConfig { 17 18 @Autowired 19 private RedisConnectionFactory redisConnectionFactory; 20 21 /** 22 * TokenStore 负责令牌的存取 23 * @return 24 */ 25 // @Bean 26 // public TokenStore redisTokenStore(){ 27 // return new RedisTokenStore(redisConnectionFactory); 28 // } 29 30 @Configuration 31 public static class JwtTokenConfig { 32 /** 33 * Token生成中的处理 34 * @return 35 */ 36 @Bean 37 public JwtAccessTokenConverter jwtAccessTokenConverter(){ 38 JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); 39 accessTokenConverter.setSigningKey("fanqi"); //Token签名用的密钥 40 //发出去的令牌需要密钥签名,验令牌的时候也需要令牌来验签,如果他人获知了我们的密钥 41 //就可以用我们的密钥来签发我们的JWT令牌,JWT唯一的安全性就是密钥 42 //别人用我们的密钥来签发我们的JWT令牌就可以随意进入我们的系统 43 return accessTokenConverter; 44 } 45 46 @Bean 47 public TokenStore jwtTokenStore(){ 48 return new JwtTokenStore(jwtAccessTokenConverter()); 49 } 50 51 @Bean 52 public TokenEnhancer jwtTokenEnhancer(){ 53 return new CoreqiJwtTokenEnhancer(); 54 } 55 } 56 }
1 package cn.coreqi.config; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.beans.factory.annotation.Qualifier; 5 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.data.redis.connection.RedisConnectionFactory; 8 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 10 import org.springframework.security.core.userdetails.UserDetailsService; 11 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; 12 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; 13 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 14 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; 15 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; 16 import org.springframework.security.oauth2.provider.token.TokenEnhancer; 17 import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; 18 import org.springframework.security.oauth2.provider.token.TokenStore; 19 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; 20 import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 @Configuration 26 @EnableAuthorizationServer //开启认证服务器 27 public class CoreqiAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { 28 29 @Autowired 30 //@Qualifier("authenticationManagerBean") 31 private AuthenticationManager authenticationManager; 32 33 @Autowired 34 private UserDetailsService userDetailsService; 35 36 37 @Autowired 38 private TokenStore jwtTokenStore; 39 40 @Autowired(required = false) 41 private JwtAccessTokenConverter jwtAccessTokenConverter; 42 43 @Autowired(required = false) 44 private TokenEnhancer jwtTokenEnhancer; 45 46 // @Autowired 47 // private AuthenticationConfiguration authenticationConfiguration; 48 49 /** 50 * 针对端点的配置 51 * @param authorizationServerEndpointsConfigurer 52 * @throws Exception 53 */ 54 @Override 55 public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception { 56 //authorizationServerEndpointsConfigurer.authenticationManager(authenticationConfiguration.getAuthenticationManager()); 57 authorizationServerEndpointsConfigurer.tokenStore(jwtTokenStore) //使用JwtToken 58 .authenticationManager(authenticationManager) 59 .userDetailsService(userDetailsService); 60 if(jwtAccessTokenConverter != null && jwtTokenEnhancer != null){ 61 TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); //Token增强器 62 List<TokenEnhancer> enhancers = new ArrayList<>(); 63 enhancers.add(jwtTokenEnhancer); 64 enhancers.add(jwtAccessTokenConverter); 65 enhancerChain.setTokenEnhancers(enhancers); 66 authorizationServerEndpointsConfigurer.tokenEnhancer(enhancerChain) 67 .accessTokenConverter(jwtAccessTokenConverter); 68 } 69 } 70 71 /** 72 * 第三方应用客户端的有关配置 73 * @param clientDetailsServiceConfigurer 74 * @throws Exception 75 */ 76 @Override 77 public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception { 78 clientDetailsServiceConfigurer.inMemory() 79 .withClient("coreqi") //client_id 80 .secret("coreqiSecret") //client_id的密码 81 .accessTokenValiditySeconds(7200) //令牌的有效时间(单位秒) 82 .redirectUris("https://www.baidu.com") 83 .scopes("all","read","write") //所支持的权限有那些 84 .authorities("COREQI_READ") 85 .authorizedGrantTypes("authorization_code","password"); //针对当前client所支持的授权模式 86 } 87 88 /** 89 * 针对安全性有关的配置 90 * @param security 91 * @throws Exception 92 */ 93 @Override 94 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 95 super.configure(security); 96 } 97 }
⒋控制器如何识别Token中扩展的信息
1 <dependency> 2 <groupId>io.jsonwebtoken</groupId> 3 <artifactId>jjwt</artifactId> 4 <version>0.9.1</version> 5 </dependency>
1 package cn.coreqi.controller; 2 3 4 import io.jsonwebtoken.Claims; 5 import io.jsonwebtoken.Jwts; 6 import org.apache.commons.lang.StringUtils; 7 import org.springframework.web.bind.annotation.GetMapping; 8 import org.springframework.web.bind.annotation.RestController; 9 10 import javax.servlet.http.HttpServletRequest; 11 import java.io.UnsupportedEncodingException; 12 import java.net.Authenticator; 13 14 @RestController 15 public class UserController { 16 17 @GetMapping("/user/me") 18 public Object getCurrentUser(Authenticator user , HttpServletRequest request) throws UnsupportedEncodingException { 19 String header = request.getHeader("Authorization"); 20 String token = StringUtils.substringAfter(header,"bearer "); 21 Claims claims = Jwts.parser().setSigningKey("fanqi".getBytes("UTF-8")).parseClaimsJws(token).getBody(); 22 String company = (String) claims.get("company"); 23 System.out.println(company); 24 return user; 25 } 26 }