角度6:刷新令牌无法正常工作

时间:2018-08-04 05:27:37

标签: angular angular6

我正在使用Angular 6,并在{strong> OAuth2 实现中使用Django REST Framework的API端点。

我是我的Angular应用程序,正在存储access_tokenrefresh_tokenexpires_ingenerate_timeuser_validtoken_type用户使用其usernamepassword 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.为什么要加载登录页面?我想在令牌过期时以静默方式刷新访问令牌。

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)