首页 > 解决方案 > Angular 6:刷新令牌未按预期工作

问题描述

我正在使用Angular 6和使用 OAuth2 实现的Django REST FrameworkAPI端点

我是我的 Angular 应用程序,我正在存储access_token, refresh_token, expires_in, generate_time, user_valid,token_type而用户使用他的usernameand password, 在localStorage中登录

由于在expires_in几分钟内非常低,因此即使用户在页面上处于活动状态,access_token 也会过期。因此,我需要access_token使用 saved重新生成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. 为什么会加载登录页面?我想在令牌过期时静默刷新访问令牌。

标签: angularangular6

解决方案


你应该重写你的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))
  )
}

推荐阅读