用refresh token获取access token在springboot框架中的实例

本文提供了一个在SpringBoot应用中使用refresh token获取access token的实例。通过结合Spring Security的defaultTokenService和jdbc数据库存储tokens,配置client secret及访问控制,实现了token的管理。如果不需要缓存,可以不使用AmcJdbcTokenStore。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

用refresh token获取access token在springboot框架中的实例

springboot

上代码

package com.wensheng.zcc.sso.config;

import com.wensheng.zcc.sso.service.UserService;
import java.util.Arrays;
import java.util.Collection;
import javax.annotation.Resource;
import javax.sql.DataSource;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.support.SqlLobValue;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.transaction.annotation.Transactional;

@Configuration
@EnableAuthorizationServer
@Slf4j
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

//    private int accessTokenValidSeconds = 3600;
    private int accessTokenValidSeconds = 150;

    private int refreshTokenValidSeconds = 2592000;

    @Value("${spring.security.oauth2.client.registration.amc-admin.client-id}")
    private String amcAdminClientId;


    @Value("${spring.security.oauth2.client.registration.amc-admin.secret}")
    private String amcAdminSecret;


    @Value("${spring.security.oauth2.client.registration.amc-admin.scopes}")
    private String amcAdminScopes;

    @Value("${spring.security.oauth2.client.registration.amc-admin.authorizedGrantTypes}")
    private String amcAdminAuthorizedGrantTypes;

    @Value("${spring.security.oauth2.client.registration.amc-admin.redirectUris}")
    private String amcAdminRedirectUris;



    @Value("${spring.security.oauth2.client.registration.amc-client-thirdpart.client-id}")
    private String amcWechatClientId;

    @Value("${spring.security.oauth2.client.registration.amc-client-thirdpart.secret}")
    private String amcWechatSecret;


    @Value("${spring.security.oauth2.client.registration.amc-client-thirdpart.scopes}")
    private String amcWechatScopes;

    @Value("${spring.security.oauth2.client.registration.amc-client-thirdpart.authorizedGrantTypes}")
    private String amcWechatAuthorizedGrantTypes;

    @Value("${spring.security.oauth2.client.registration.amc-client-thirdpart.redirectUris}")
    private String amcWechatRedirectUris;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    DataSource dataSource;


    @Autowired
    UserService userService;

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("sampleClientId").authorizedGrantTypes("implicit").scopes("read", "write", "foo", "bar").autoApprove(false).accessTokenValiditySeconds(3600).redirectUris("http://localhost:8083/","http://localhost:8086/")

                .and().withClient("testImplicitClientId").authorizedGrantTypes("implicit").scopes("read", "write", "foo", "bar").autoApprove(true).redirectUris("xxx")
//            .and().withClient(amcWechatClientId).authorizedGrantTypes(amcWechatAuthorizedGrantTypes.replace(" ","").split(",")).scopes(amcWechatScopes.replace(" ", "").split(",")).autoApprove(true).redirectUris(amcWechatRedirectUris.split(","))
                .and().withClient(amcAdminClientId).secret(passwordEncoder.encode(amcAdminSecret))
            .authorizedGrantTypes(amcAdminAuthorizedGrantTypes.replace(" ","").split(",")).scopes(amcAdminScopes.replace(" ","").split(
                ","))
            .autoApprove(false).redirectUris(amcAdminRedirectUris.split(","))
            .accessTokenValiditySeconds(accessTokenValidSeconds).refreshTokenValiditySeconds(refreshTokenValidSeconds)
        ;

    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setReuseRefreshToken(false);
        addUserDetailsService(defaultTokenServices, userService);
        final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValidSeconds);
        defaultTokenServices.setRefreshTokenValiditySeconds(refreshTokenValidSeconds);
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        return defaultTokenServices;
    }

    private void addUserDetailsService(DefaultTokenServices tokenServices, UserService userService) {
        if (userService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
                userService));
            tokenServices
                .setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
        }
    }



    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        endpoints.tokenServices(tokenServices()).tokenEnhancer(tokenEnhancerChain).authenticationManager(authenticationManager);
//        endpoints.pathMapping("oauth/token", "oauth/token");

    }

    @Bean
    public TokenStore tokenStore() {
        JdbcTokenStore jdbcTokenStore =  new AmcJdbcTokenStore(dataSource);
        return jdbcTokenStore;
//        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("YOUR—KEY—PASSWORD");
        // final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
        // converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

    @Bean
    public TokenEnhancer wechatTokenEnhancer(){ return new WechatTokenEnhancer(); }

    @CacheConfig(cacheNames = {"TOKEN"})
    class AmcJdbcTokenStore extends JdbcTokenStore{

        public AmcJdbcTokenStore(DataSource dataSource) {
            super(dataSource);
        }

        @Override
        @Cacheable
        public Collection<OAuth2AccessToken> findTokensByClientId(String clientId){
            log.info("query caches with clientId:{}", clientId);
            return super.findTokensByClientId(clientId);
        }

        @Transactional
        @CacheEvict
        public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
            super.removeAccessTokenUsingRefreshToken(refreshToken);
        }

        @Transactional
        @CacheEvict
        public void removeAccessTokenUsingRefreshToken(String refreshToken) {
            super.removeAccessTokenUsingRefreshToken(refreshToken);
        }
        @Transactional
        @CacheEvict
        public void removeAccessToken(OAuth2AccessToken token) {
            super.removeAccessToken(token);
        }
        @Transactional
        @CacheEvict
        public void removeAccessToken(String tokenValue) {
            super.removeAccessToken(tokenValue);
        }
        @Transactional
        @CacheEvict
        public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
            super.storeAccessToken(token, authentication);
        }

    }


}

这里面利用spring security框架里面defaultTokenService结合jdbc数据库作为token的datasource,自己定义一些client secret和访问控制。 AmcJdbcTokenStore只是想用缓存试试, 没有缓存必要的话可以不需要重载jdbcTokenStore

这里插入图片描述
就这样完成refreshtoken来获取accessToken

<think>嗯,用户问的是JWT中accessrefresh token的生成和管理,需要给出实例。首先,我得回忆一下JWT的基本结构,access tokenrefresh token的作用区别。access token通常用于认证,有较短的有效期,而refresh token用来获取新的access token,有效期较长。 接下来,生成这两个token的方法,应该都需要使用密钥,可能access用更短的过期时间,比如15分钟,refresh用几天或者更久。然后,管理方面需要考虑如何存储,比如access token存在客户端内存,refresh token可能存在数据库或者HTTP-only的cookie里,防止XSS攻击。 然后,用户可能需要具体的例子,比如用Node.js和Express,jsonwebtoken库来生成,用Redis或者数据库来管理refresh token。要注意安全性的问题,比如密钥的保护,token的撤销机制,可能还需要处理黑名单的情况。 另外,用户可能没有明说但需要考虑的是如何在实际应用中处理token的刷新流程,比如提供一个/auth/refresh的端点,接收refresh token来颁发新的access token。同时,需要处理refresh token的过期和存储,确保每次使用后可以更新或者删除旧的token,防止重复使用。 还要注意在响应中返回新的access token,并让客户端处理替换旧的。可能还要提到如何验证token的签名,确保不被篡改。可能需要代码示例来展示生成、签名、验证的整个过程,以及如何用中间件来保护路由。 最后,应该提醒用户安全最佳实践,比如HTTPS、密钥管理、refresh token的存储方式,避免常见的安全漏洞。可能还需要提到关于token泄露后的应对措施,比如及时撤销refresh token。</think>在JWT(JSON Web Token)中,**Access Token** 和 **Refresh Token** 是两种常见的令牌机制,以下是它们的生成、管理及实际应用示例: --- ### **1. 核心区别** - **Access Token** 用于访问受保护资源,有效期短(如 15 分钟),需频繁更换。 - **Refresh Token** 用于获取新的 Access Token,有效期长(如 7 天),存储更安全。 --- ### **2. 生成示例(Node.js + `jsonwebtoken`)** ```javascript const jwt = require('jsonwebtoken'); const SECRET_KEY = 'your-secret-key'; // 实际应使用环境变量存储 // 生成 Access Token(15分钟过期) const accessToken = jwt.sign( { userId: '123', role: 'user' }, SECRET_KEY, { expiresIn: '15m' } ); // 生成 Refresh Token(7天过期,单独密钥更安全) const refreshToken = jwt.sign( { userId: '123' }, SECRET_KEY + 'refresh', // 使用不同密钥 { expiresIn: '7d' } ); ``` --- ### **3. 管理流程** #### **(1) 颁发令牌(登录接口)** ```javascript app.post('/login', (req, res) => { // 验证用户身份后... res.json({ accessToken, refreshToken }); }); ``` #### **(2) 使用 Access Token 访问资源** ```javascript // 中间件验证 Access Token const authenticate = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; jwt.verify(token, SECRET_KEY, (err, decoded) => { if (err) return res.sendStatus(403); // Forbidden req.user = decoded; next(); }); }; app.get('/protected', authenticate, (req, res) => { res.send('Access granted'); }); ``` #### **(3) 使用 Refresh Token 更新 Access Token** ```javascript app.post('/refresh', (req, res) => { const { refreshToken } = req.body; // 验证 Refresh Token 有效性(可结合数据库或Redis校验是否被撤销) jwt.verify(refreshToken, SECRET_KEY + 'refresh', (err, decoded) => { if (err) return res.sendStatus(403); // 生成新 Access Token const newAccessToken = jwt.sign( { userId: decoded.userId }, SECRET_KEY, { expiresIn: '15m' } ); res.json({ accessToken: newAccessToken }); }); }); ``` --- ### **4. 安全最佳实践** 1. **存储方式** - Access Token:客户端内存(如 Vue/React 状态),**避免 localStorage**(防XSS)。 - Refresh Token:通过 HTTP-only Cookie 或安全存储服务(如移动端 Keychain)。 2. **黑名单机制** 使用 Redis 或数据库记录已注销但未过期的 Token,验证时检查是否在黑名单中。 3. **密钥管理** - 使用强随机密钥(如 `crypto.randomBytes(64).toString('hex')`)。 - 通过环境变量或密钥管理服务(如 AWS KMS)存储密钥。 4. **Token 撤销** 提供 `/logout` 接口,将 Refresh Token 标记为失效: ```javascript // 伪代码:将 Refresh Token 存入Redis黑名单 redisClient.set(refreshToken, 'revoked', 'EX', 7 * 86400); // 设置7天过期 ``` --- ### **5. 流程图解** ``` 用户登录 → 颁发 Access + Refresh Token │ ├─ Access Token 过期 → 使用 Refresh Token 获取Access Token │ └─ Refresh Token 过期 → 重新登录 ``` 通过合理设计 Token 生命周期和安全策略,可以在保证用户体验的同时提升系统安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值