【spring security】oauth认证授权-JWT存储token
1、JWT基本实现-对称加密
认证服务
@Configuration
@EnableAuthorizationServer //开启授权服务
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
/**
* 授权服务器端点的 非安全性配置(请求到 TokenEndpoint )
* 配置令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.tokenEnhancer(tokenConverter());
}
/**
* 授权服务器端点的 安全性配置(请求到 TokenEndpoint 之前)
* 配置令牌端点的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
/**
* 配置客户端详情
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(tokenConverter());
}
@Bean
public JwtAccessTokenConverter tokenConverter(){
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey("123456");//对称秘钥,资源服务器使用该秘钥来验证
return tokenConverter;
}
}
简单使用JWT的话改动很小,只需要提供JwtTokenStore的配置 和 JwtAccessTokenConverter(TokenEnhancer的实现)的配置。前面有说TokenStore是token的存储相关的操作,TokenEnhancer是token的增强器,可以对默认的token进行加工处理生成一个定制化的token。
JWT创建AccessToken的源码
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
......
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
// 这里如果没有配置TokenEnhancer,AccessToken就是上面生成的UUID。配置TokenEnhancer后会对UUID加工。
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
......
}
public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean {
......
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
String tokenId = result.getValue();
if (!info.containsKey(TOKEN_ID)) {
info.put(TOKEN_ID, tokenId);
}
else {
tokenId = (String) info.get(TOKEN_ID);
}
// 把原来生成的UUID当成一个附加信息
result.setAdditionalInformation(info);
// encode()生成新token
result.setValue(encode(result, authentication));
...... 稍微简化了一下
return result;
}
......
// 生成新token的方法
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
// 封装jwt需要加密的数据
content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication));
}
catch (Exception e) {
throw new IllegalStateException("Cannot convert access token to JSON", e);
}
// jwt加密编码
String token = JwtHelper.encode(content, signer).getEncoded();
return token;
}
......
}
jwt返回示例
资源服务
资源服务需要提供与认证服务一样的tokenStore配置
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
public static final String RESOURCE_ID = "userinfo";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123456"); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenStore(tokenStore())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/aaa/aaa").hasAuthority("ROLE_USER")
.antMatchers("/bbb/aaa").hasRole("123456")
.antMatchers("/**").access("#oauth2.hasScope('all')")
.anyRequest().authenticated()
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
}
2、JWT基本实现-非对称加密
认证服务
使用keytool生成rsa秘钥库
keytool -genkeypair -alias test-jwt -validity 3650 -keyalg RSA -keypass testTest -keystore test-jwt.jks -storepass testTest
更改配置
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("test-jwt.jks"),
"testTest".toCharArray());
return keyStoreKeyFactory.getKeyPair("test-jwt");
}
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(tokenConverter());
}
@Bean
public JwtAccessTokenConverter tokenConverter(){
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setKeyPair(keyPair());
// tokenConverter.setSigningKey("123456");
return tokenConverter;
}
资源服务
使用keytool命令在 秘钥库下生成公钥
keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
更改配置
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
String publicKey = ""; // 生成的公钥
converter.setVerifierKey(publicKey);
return converter;
}
3、token中扩展自定义用户数据
之前有整理过JWT生成的accesstoken是有包含用户信息,配置中已经暴露了解析token的接口,查看默认的用户信息数据。对于一些业务来说 扩展自定义的用户数据 很重要。方便各个服务通过自己解析token来获得 用户数据。
扩展自定义用户数据的方式,是在生成JWT token的增强器之前增加一个增强器,添加扩展信息。
自定义增强器,扩展用户数据
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
UserInfoDetail user = (UserInfoDetail) authentication.getPrincipal();
Map<String, Object> map = new LinkedHashMap<>();
map.put("nickname", user.getNickname());
map.put("mobile", user.getMobile());
map.put("id", user.getId());
token.setAdditionalInformation(map);
return token;
}
}
更改配置
@Configuration
@EnableAuthorizationServer //开启授权服务
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
/**
* 授权服务器端点的 非安全性配置(请求到 TokenEndpoint )
* 配置令牌(token)的访问端点和令牌服务(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenServices(tokenService());
}
/**
* 授权服务器端点的 安全性配置(请求到 TokenEndpoint 之前)
* 配置令牌端点的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
// .checkTokenAccess("isAnonymous() || hasAuthority('ROLE_USER')");
}
/**
* 配置客户端详情
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(tokenConverter());
}
@Bean
public JwtAccessTokenConverter tokenConverter(){
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey("123456");
return tokenConverter;
}
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);
service.setSupportRefreshToken(true);
service.setTokenStore(tokenStore());
service.setTokenEnhancer(customTokenEnhancer());
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(604800); // 刷新令牌默认有效期7天
return service;
}
@Bean
public TokenEnhancer customTokenEnhancer() {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), tokenConverter()));
return tokenEnhancerChain;
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
//设置授权码模式的授权码如何存取,暂时采用内存方式
return new InMemoryAuthorizationCodeServices();
}
}
通过tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer(), tokenConverter()));
设置了两个增强器进去,由于两个增强器是循环调用的,所以顺序不能乱,生成JWT token的在最后。
public class TokenEnhancerChain implements TokenEnhancer {
private List<TokenEnhancer> delegates = Collections.emptyList();
public void setTokenEnhancers(List<TokenEnhancer> delegates) {
this.delegates = delegates;
}
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OAuth2AccessToken result = accessToken;
for (TokenEnhancer enhancer : delegates) {
result = enhancer.enhance(result, authentication);
}
return result;
}
}
新token解析
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下