Ooooon

灯光照亮着我

【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解析

posted @   Ooooon  阅读(345)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示