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
相关产品推荐
相关产品推荐

