我正在使用Angular 6
,并在{strong> OAuth2 实现中使用Django REST Framework
的API端点。
我是我的Angular应用程序,正在存储access_token
,refresh_token
,expires_in
,generate_time
,user_valid
,token_type
用户使用其username
和password
在 localStorage
由于expires_in
的使用率非常低,长达几分钟,因此即使用户在页面上处于活动状态,access_token也会过期。因此,我需要使用保存的access_token
重新生成refresh_token
。
我有 auth.interceptor.ts 为每个请求添加了access_token
以授权该请求。
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {AuthService} from './auth.service';
import {Injectable} from '@angular/core';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
public Auth: AuthService
) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('interceptor');
request = request.clone({
setHeaders: {
Authorization: `${this.Auth.tokenType()} ${this.Auth.accessToken()}`
}
});
return next.handle(request);
}
}
和 auth-guard.service.ts 来保护受保护的URL,并检查令牌是否已过期,如果令牌已过期,则重新生成访问令牌。
import { Injectable } from '@angular/core';
import {CanActivate, Router} from '@angular/router';
import {AuthService} from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuardService implements CanActivate {
constructor(
public Auth: AuthService,
public router: Router
) { }
/**
* this method is used to check if user is authenticated or not
* if user is not authenticated, is redirected to login page
*/
canActivate(): boolean {
// if userValid is true and user is not authenticated,
// probably access token has been expired,
// refresh token
if (this.Auth.userValid()) {
if (!this.Auth.isAuthenticated()) {
this.Auth.refreshAuthToken();
}
}
// if user is not authenticated,
// redirect to login page
if (!this.Auth.isAuthenticated()) {
this.router.navigate(['auth/login']);
}
return true;
}
}
有一个 auth.service.ts 来检查用户是否经过身份验证和有效,并且有 token.service.ts 来管理令牌并保存在 localStorage 并从存储中检索。
import { Injectable } from '@angular/core';
import {HttpClient, HttpRequest} from '@angular/common/http';
import {ResourceProviderService} from '../resource-provider.service';
import {Observable} from 'rxjs';
import {AuthToken} from './auth.model';
import {TokenService} from './token.service';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(
private resource: ResourceProviderService,
private http: HttpClient,
private token: TokenService
) { }
/**
* returns access token from token service
*/
public accessToken(): string {
return this.token.accessToken();
}
/**
* Get authentication token credentials like access token, token secret, expires in
* from the server for the authenticated user
*/
getAuthToken(username: any, password: any): Observable<AuthToken> {
const authFormData = new FormData();
authFormData.append('grant_type', this.resource.grant_type);
authFormData.append('client_id', this.resource.client_key);
authFormData.append('client_secret', this.resource.client_secret);
authFormData.append('scope', this.resource.scope);
authFormData.append('username', username);
authFormData.append('password', password);
return this.http.post<AuthToken>(this.resource.url + '/auth/token/', authFormData);
}
/**
* refresh token of the user using the refresh token stored
*/
refreshAuthToken() {
console.log('refresh token');
const authFormData = new FormData();
authFormData.append('grant_type', this.resource.grant_type_refresh_token);
authFormData.append('client_id', this.resource.client_key);
authFormData.append('client_secret', this.resource.client_secret);
authFormData.append('refresh_token', this.token.refreshToken());
this.http.post<AuthToken>(this.resource.url + '/auth/token/', authFormData).subscribe(
response => {
console.log('setting credentials');
this.token.setCredentials(response);
}, error => {
console.log(error.status, error.error.error);
console.log(error);
}
);
}
/**
* Method set credentials using token service and
* stores in local storage of the browser
*/
setCredentials(response: AuthToken): void {
this.token.setCredentials(response);
}
/**
* Method is called to logout the user
*/
clearCredentials(): void {
this.token.clearCredentials();
}
isAuthenticated(): boolean {
// Check whether the token is expired and return
// true or false
return !this.token.isTokenExpired();
}
userValid(): boolean {
return this.token.userValid();
}
tokenType(): string {
return this.token.tokenType();
}
}
access_token
过期时,isAuthenticated()
返回false,并从 auth-guard 服务调用this.Auth.refreshAuthToken()
。
但是它首先重定向到登录页面,最后一次调用refreshAuthToken()
,刷新令牌后网络请求中断。
1。即使刷新访问令牌后,它也不会重定向回页面。
2.为什么要加载登录页面?我想在令牌过期时以静默方式刷新访问令牌。
答案 0 :(得分:3)
您应该重写canActivate
方法以解决refreshAuthToken
方法的异步行为:
canActivate(): Observable<boolean> | boolean {
if (!this.Auth.userValid()) {
return this.cannotActivate();
} else if (this.Auth.isAuthenticated()) {
return true;
} else {
return this.Auth.refreshAuthToken().pipe(
map(() => this.Auth.isAuthenticated() || this.cannotActivate())
);
}
}
cannotActivate(): boolean {
this.router.navigate(['auth/login']);
return false;
}
但是要使此方法起作用,您还应该从refreshAuthToken
方法中返回一个Observable:
refreshAuthToken(): Observable<any> {
//...
return this.http.post<AuthToken>(this.resource.url + '/auth/token/', authFormData).pipe(
tap((response) => this.token.setCredentials(response))
)
}
答案 1 :(得分:2)
这是因为,您的auth防护正在从auth服务调用刷新令牌方法,而该服务又发出了异步HTTP请求,因此api防护中的canActivate方法在api调用完成之前会返回true,因为isAuthenticated()仍然为false。
我认为您需要从refreshToken方法返回一个订阅,并在auth Guard中进行订阅。订阅完成后,您可以返回true和false。但是在此之前,我们需要检查canActivate是否将等待订阅完成。
问候 阿比(Abhay)