You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JHipster(Angular4/Spring)应用OAuth Token过期时间续期配置咨询

解决JHipster Angular4/Spring应用Token请求续期问题

嘿,我来给你梳理下这个问题的解决方案——首先得明确:JHipster默认用的JWT Token是无状态且不可修改的,已发放的token过期时间没法事后更改,因为这个时间是生成时就嵌入到token里的。所以我们得换个思路,通过refresh token自动续期+前端请求拦截的方式,实现“每次请求时自动延长有效时长”的效果。

下面是具体的配置步骤,分后端和前端两部分:

1. 后端Spring Security OAuth2配置调整

自定义Token服务,延长Refresh Token有效期

如果你的应用用数据库存储token(JdbcTokenStore),需要重写默认的Token服务,在刷新token时更新refresh token的过期时间:

  • 创建自定义DefaultTokenServices子类:
public class CustomTokenServices extends DefaultTokenServices {
    private JdbcTemplate jdbcTemplate;

    // 注入JdbcTemplate
    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
        // 先调用父类方法生成新的access token
        OAuth2AccessToken accessToken = super.refreshAccessToken(refreshTokenValue, tokenRequest);
        OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
        
        // 更新数据库中refresh_token的过期时间,延长有效期
        if (refreshToken instanceof DefaultOAuth2RefreshToken) {
            String refreshTokenId = ((DefaultOAuth2RefreshToken) refreshToken).getValue();
            jdbcTemplate.update(
                "UPDATE oauth_refresh_token SET expiration = ? WHERE token_id = ?",
                new Date(System.currentTimeMillis() + getRefreshTokenValiditySeconds() * 1000),
                refreshTokenId
            );
        }
        return accessToken;
    }
}
  • 在AuthorizationServerConfigurerAdapter配置类中替换默认的token服务:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    // 注入必要的bean:tokenStore、clientDetailsService、jdbcTemplate等
    
    @Bean
    public TokenServices tokenServices() {
        CustomTokenServices tokenServices = new CustomTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService());
        tokenServices.setAccessTokenValiditySeconds(1800); // 保持你的原有设置
        tokenServices.setRefreshTokenValiditySeconds(1800); // 可以考虑设长一点,比如3600
        tokenServices.setJdbcTemplate(jdbcTemplate);
        return tokenServices;
    }

    // 允许复用refresh token(默认是true,但明确设置更稳妥)
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .tokenStore(tokenStore())
            .tokenServices(tokenServices())
            .reuseRefreshTokens(true);
    }
}

检查OAuth客户端配置

确保oauth_client_details表中的authorized_grant_types字段包含refresh_token,否则无法使用刷新token的功能。如果没有,直接更新该字段:

UPDATE oauth_client_details SET authorized_grant_types = 'password,refresh_token' WHERE client_id = 'your-client-id';

2. 前端Angular4拦截器配置

前端需要在每次发起API请求时,检查当前access token的剩余有效期,如果快过期(比如剩余时间小于60秒),就自动调用refresh token接口获取新的access token,避免用户被强制登出:

  • 创建HTTP拦截器:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError, timer } from 'rxjs';
import { catchError, switchMap, finalize } from 'rxjs/operators';
import { AuthService } from './auth.service';

@Injectable()
export class TokenRefreshInterceptor implements HttpInterceptor {
    private isRefreshing = false;

    constructor(private authService: AuthService) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // 跳过登录和刷新token的请求,避免循环调用
        if (request.url.includes('/oauth/token')) {
            return next.handle(request);
        }

        const token = this.authService.getAccessToken();
        const expiresAt = this.authService.getExpiresAt();
        
        // 检查token是否即将过期(这里设为剩余60秒时触发刷新)
        if (token && expiresAt && (expiresAt.getTime() - Date.now() < 60000)) {
            if (!this.isRefreshing) {
                this.isRefreshing = true;
                return this.authService.refreshToken().pipe(
                    switchMap((newTokenData) => {
                        // 更新请求头中的access token
                        const updatedRequest = request.clone({
                            setHeaders: {
                                Authorization: `Bearer ${newTokenData.access_token}`
                            }
                        });
                        return next.handle(updatedRequest);
                    }),
                    catchError((error) => {
                        // 刷新失败,直接登出用户
                        this.authService.logout();
                        return throwError(error);
                    }),
                    finalize(() => {
                        this.isRefreshing = false;
                    })
                );
            } else {
                // 如果正在刷新token,等待刷新完成后再处理当前请求
                return timer(100).pipe(switchMap(() => this.intercept(request, next)));
            }
        }

        // token有效期足够,直接放行请求
        return next.handle(request);
    }
}
  • 在AuthService中实现refreshToken方法:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class AuthService {
    private accessTokenKey = 'access_token';
    private refreshTokenKey = 'refresh_token';
    private expiresAtKey = 'expires_at';

    constructor(private http: HttpClient) {}

    getAccessToken(): string {
        return localStorage.getItem(this.accessTokenKey);
    }

    getRefreshToken(): string {
        return localStorage.getItem(this.refreshTokenKey);
    }

    getExpiresAt(): Date {
        const expiresAtStr = localStorage.getItem(this.expiresAtKey);
        return expiresAtStr ? new Date(parseInt(expiresAtStr, 10)) : null;
    }

    refreshToken() {
        const refreshToken = this.getRefreshToken();
        const body = new URLSearchParams();
        body.set('grant_type', 'refresh_token');
        body.set('refresh_token', refreshToken);
        body.set('client_id', 'your-client-id'); // 替换为你的OAuth客户端ID
        body.set('client_secret', 'your-client-secret'); // 替换为你的OAuth客户端密钥

        return this.http.post('/oauth/token', body.toString(), {
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        }).pipe(
            tap((response: any) => {
                // 更新本地存储的token和过期时间
                localStorage.setItem(this.accessTokenKey, response.access_token);
                localStorage.setItem(this.refreshTokenKey, response.refresh_token);
                localStorage.setItem(this.expiresAtKey, (Date.now() + response.expires_in * 1000).toString());
            })
        );
    }

    logout() {
        localStorage.removeItem(this.accessTokenKey);
        localStorage.removeItem(this.refreshTokenKey);
        localStorage.removeItem(this.expiresAtKey);
        // 跳转到登录页
        window.location.href = '/login';
    }
}
  • 将拦截器注册到Angular模块中:
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TokenRefreshInterceptor } from './token-refresh.interceptor';

@NgModule({
    // ... 其他模块配置
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: TokenRefreshInterceptor,
            multi: true
        }
    ]
})
export class AppModule {}

3. 额外注意事项

  • 如果你用的是JHipster默认的JWT Token(无数据库存储),那么refresh token的有效期同样是生成时固定的,这种情况下建议把refresh_token_validity设得比access_token_validity长一些,比如3600秒,让用户有足够的时间触发刷新。
  • 测试时可以故意缩短access_token_validity(比如设为60秒),验证拦截器是否会自动刷新token,避免登出。

内容的提问来源于stack exchange,提问作者Lewis Godgiven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:14:17