java - 使用来自 Spring Boot 后端的 Angular 11 拦截器刷新 JWT 过期令牌
问题描述
我正在开发一个 Spring Boot + Angular 项目,其中用户从 Angular 前端登录到 Spring Boot 上的身份验证 API,该 API 返回一个 JWT 令牌。我还在 Angular 上设置了一个拦截器,它为所有请求附加了带有 JWT 令牌的 Authorization 标头。
我正在寻找一种拦截角度请求的方法,以便当 JWT 令牌过期后 Spring Boot 引发 401 错误时,Angular 前端将尝试使用过期的 JWT 和新的“isRefreshToken”标头集联系新的 refreshtoken 端点为 true 以接收新的 JWT。
这是我当前的 AuthService
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}
login(username: string, password: string) {
return this.http
.post<iUser>('http://localhost:8080/authenticate', { username, password }).pipe(
tap(res => this.setSession(res)),
shareReplay()
)
}
refreshToken(){
return this.http.post<iUser>('http://localhost:8080/refreshtoken', {responseType: 'text' as 'json'}).pipe(
tap(res => this.setSession(res)),
shareReplay()
)
}
private setSession(authResult) {
let tokenInfo = this.getDecodedAccessToken(authResult.token);
const expiresAt = moment(tokenInfo.exp);
localStorage.setItem('id_token', authResult.token);
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
localStorage.setItem('userId', tokenInfo.userId);
}
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('userId');
}
public isLoggedIn() {
return moment().isBefore(this.getExpiration());
}
isLoggedOut() {
return !this.isLoggedIn();
}
getExpiration() {
const expiration = localStorage.getItem('expires_at');
const expiresAt = JSON.parse(expiration);
return moment.unix(expiresAt);
}
getDecodedAccessToken(token: string): any {
try{
return jwt_decode(token);
}
catch(Error){
return null;
}
}
}
虽然这是我正在使用的拦截器:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router, private authService: AuthService){}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let url = req.url.includes('localhost');
const idToken = localStorage.getItem('id_token');
if (idToken && url) {
const cloned = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + idToken),
});
console.log(cloned);
return next.handle(cloned);
} else {
return next.handle(req);
}
}
}
解决方案
我可以向您建议另一种执行此操作的方法,我最近使用该方法在令牌过期时注销用户。先分享一下我的做法:
loginUser(email: string, password: string) {
const authData: AuthData = { email: email, password: password };
this.http.post<{ token: string, expiresIn: number }>('http://localhost:3000/api/users/login', authData).subscribe( response => {
const token = response.token;
this.token = token;
if(token) {
const expiresInDuration = response.expiresIn;
this.tokenTimer = setTimeout(() => {
this.logout();
}, expiresInDuration*1000);
this.isAuthenticated = true;
this.authStatusListener.next(true);
const now = new Date();
const expirationDate = new Date(now.getTime() + (expiresInDuration * 1000));
this.saveAuthData(token, expirationDate);
this.router.navigate(['']);
}
});
}
logout() {
this.token = null;
this.isAuthenticated = false;
this.authStatusListener.next(false);
clearTimeout(this.tokenTimer);
this.clearAuthData();
this.router.navigate(['']);
}
private saveAuthData(token: string, expirationDate: Date) {
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate.toISOString());
}
所以我在这里所做的是我收到了一个expireIn
以秒为单位的令牌过期的值。然后我设置了一个timeout
回调方法,当到达该时间时将调用该方法。在这里,我已在您的情况下注销,您可以调用刷新令牌的 API 并根据您的要求进行所需的更改。所以回到这一点,我已经设置了与当前登录日期时间和持续时间相关的到期日期和expiresIn
时间。还添加了一个authStatusListener
Subject
将监听身份验证状态直到它过期的功能。
在拦截器/守卫中没有额外的事情可做令牌过期
要控制标题中的注销按钮,只需执行以下操作:
userIsAuthenticated = false;
private authListenerSubs: Subscription;
constructor(private authService: AuthService) { }
onLogout() {
this.authService.logout();
}
ngOnInit() {
this.authListenerSubs = this.authService.getAuthStatusListener().subscribe(isAuthenticated => {
this.userIsAuthenticated = isAuthenticated;
});
}
ngOnDestroy() {
this.authListenerSubs.unsubscribe();
}
userIsAuthenticated
在ngIf
HTML 中使用。
对于一个真实的场景,你可以借助这个github repo。
推荐阅读
- amazon-web-services - 如何将仪表板正文中的 ARN 替换为不包含 accountId 的内容?
- grafana - 如何创建基于其他变量的反应性 Grafana 变量,所有这些变量都在仪表板设置中定义?
- sass - WebStorm 中未解析的函数 sass math.div()
- neural-network - 如何在实践中使用图卷积网络模型(节点分类)?
- c# - 注册的自定义协议在浏览器中未被识别为链接
- reactjs - AntD Form - 手动调用 onChange 事件
- c++ - C++派生类如何占用基类空间
- javascript - Vue/Laravel WebSockets 监听多个事件
- r - 如何使用 terra 包将栅格堆栈中提取的值添加到 Spatial 对象的 data.frame?
- c++ - 在内联匿名命名空间中声明的全局命名空间中定义模板函数时未定义的引用