SpringSecurityOAuth2 如何自定义token信息
作者:无小农
OAuth2默认的token返回最多只携带了5个参数(client_credentials模式只有4个 没有refresh_token)
下面是一个返回示例:
{ "access_token": "1e93bc23-32c8-428f-a126-8206265e17b2", "token_type": "bearer", "refresh_token": "0f083e06-be1b-411f-98b0-72be8f1da8af", "expires_in": 3599, "scope": "auth api" }
然后我们需要的token可能需要增加username等自定义参数:
{ "access_token": "1e93bc23-32c8-428f-a126-8206265e17b2", "token_type": "bearer", "refresh_token": "0f083e06-be1b-411f-98b0-72be8f1da8af", "expires_in": 3599, "scope": "auth api", "username":"username" }
具体实现自定义token步骤如下: 新建一个自定义token信息的新建自定义token返回MyTokenEnhancer实现TokenEnhancer接口重写enhance方法:
/** * @Description 自定义token返回值 * @Author wwz * @Date 2019/07/31 * @Param * @Return */ public class MyTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { User user = (User) authentication.getPrincipal(); final Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("username", user.getUsername()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
然后在认证服务配置AuthorizationServerEndpointsConfigurer中加上 MyTokenEnhancer。
这里划重点 因为我这里指定了了defaultTokenServices()所以得在这个方法里加上配置
还有,如果已经生成了一次没有自定义的token信息,需要去redis里删除掉该token才能再次测试结果,不然你的结果一直是错误的,因为token还没过期的话,是不会重新生成的。
Spring Security 使用JWT自定义Token
叙述
默认的token生成规则其实就是一个UUID,就是一个随机的字符串,然后存到redis中去,使用JWT的话,token中可以存放一些信息,我们服务端也不需要保存这个token, 服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证
使用JWT,在分布式系统中,很好地解决了单点登录问题,很容易解决了session共享的问题.但是是无法作废已颁布的令牌/不易应对数据过期,因为 token 并没有保存到服务端, 下面来看一下如何去配置JWT
配置JWT
TokenStoreConfig 这个类中要做一些修改,之前我们只在这个类里面配置了 redis 的存储,现在把 JWT 的配置也加上,如下:
@Configuration public class TokenStoreConfig { @Autowired private RedisConnectionFactory connectionFactory; @Bean @ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="redis") public TokenStore redisTokenStore(){ return new RedisTokenStore(connectionFactory); } @Configuration @ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="jwt", matchIfMissing = true) public static class JwtTokenConfig{ @Autowired private SecurityProperties securityProperties; @Bean public TokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ // token生成中的一些处理 JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(securityProperties.getOAuth2().getJwtTokenSignKey()); return converter; } } }
这里首先是一个内部的静态类 JwtTokenConfig 用来配置 JWT 的一些配置,第一个方法 jwtTokenStore() 就是配置token的存储,然后这里需要一个 JwtAccessTokenConverter 因为 TokenStore 只管 Token 的存储,生成规则还需要配置,所以 jwtAccessTokenConverter() 就是用来做一些 Token 的处理
这个类上有一个注解
@ConditionalOnProperty(prefix = "core.security.oAuth2", name="tokenStore", havingValue="jwt", matchIfMissing = true)
意思就是,在配置中有 core.security.oAuth2.tokenStore 这个配置,而且值是 jwt 的话,就生效,最后有一个 matchIfMissing = true ,这个表示, 如果配置中没有这个配置的话,也生效
上面的 redisTokenStore 也加了这个注解,但是没有 matchIfMissing 默认是 false, 总的配置就是如果在配置中没有指定哪种 tokenStore 的话,就默认的用 jwt ,如果想要使用 redis 存储的话,必须明确的指定 core.security.oAuth2.tokenStore: redis
最后认证服务的配置中还需要做一些修改
@Configuration @EnableAuthorizationServer public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired(required = false) private JwtAccessTokenConverter jwtAccessTokenConverter; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // ... 省略其他配置 // 只有配置使用JWT的时候才会生效 if (jwtAccessTokenConverter != null) { endpoints.accessTokenConverter(jwtAccessTokenConverter); } } // ... 省略其他代码 }
这里,就是给 endpoints 指定一下 JwtAccessTokenConverter 就可以了
测试
请求 Token 还是跟之前的方式一样的, 如下:
可以看到这里的token就是使用jwt了
在 jwt.io 中可以把刚刚生成的token解析一下,内容如下:
这个就是我们生成的 JWT 中包含的信息
TokenEnhancer 的使用
TokenEnhancer 是一个增强器,JWT 中是可以放一些我们自定义的信息的,如果要加入一些我们自己的信息的话,就得使用 TokenEnhancer
还是修改 TokenStoreConfig ,在 JwtTokenConfig 这个内部类中,加一个配置
@Bean @ConditionalOnBean(TokenEnhancer.class) public TokenEnhancer jwtTokenEnhancer(){ return new MyJwtTokenEnhancer(); }
然后 MyJwtTokenEnhancer 代码如下:
public class MyJwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> info = new HashMap<>(1); info.put("custom", "test"); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); return accessToken; } }
这里加了 @ConditionalOnBean,是必须存在一个TokenEnhancer 的时候,才被创建, 之前的 JwtAccessTokenConverter 也是一个 TokenEnhancer
最后,认证服务器配置类 MyAuthorizationServerConfig 中,需要修改一下
@Autowired(required = false) private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); endpoints.userDetailsService(userDetailsService); endpoints.tokenStore(tokenStore); // 只有配置使用JWT的时候才会生效 if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) { TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> enhancers = new ArrayList<>(); enhancers.add(jwtTokenEnhancer); enhancers.add(jwtAccessTokenConverter); enhancerChain.setTokenEnhancers(enhancers); endpoints.tokenEnhancer(enhancerChain) .accessTokenConverter(jwtAccessTokenConverter); } }
测试
获取token,然后解析结果如下:
自定义数据解析
这里 Spring 在解析JWT的时候会解析成一个 Authentication 对象,并不会解析我们上面设置的自定义字段,这个还需要我们自己去解析
demo 项目中增加一个 jwt 解析的依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
然后再 /user/me 这个接口中做解析,代码如下:
@GetMapping("/me") public Object me(Authentication authentication, HttpServletRequest request) throws UnsupportedEncodingException { // 从请求头中获取到token String jwtToken = StringUtils.substringAfter(request.getHeader("Authorization"), AUTHORIZATION_PREFIX); log.info("请求头中的token:{}", jwtToken); // 获取配置中的 jwtTokenSignKey String jwtTokenSignKey = securityProperties.getOAuth2().getJwtTokenSignKey(); Claims claims = Jwts.parser().setSigningKey(jwtTokenSignKey.getBytes("UTF-8")).parseClaimsJws(jwtToken).getBody(); return claims; }
效果如下:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。