首页 > 解决方案 > 如何在 Angular 8 应用程序中添加移动访问令牌?

问题描述

我已将我的应用程序部署到 UAT,但我无法在移动设备中运行我的应用程序,因为它直接进入拒绝访问(401)页面。我认为这是由于访问令牌问题。

我主要有 2 个拦截器来处理我的应用程序。1.错误拦截器 - 当有任何路由错误或未经授权的错误时进行处理。2. jwt - 分配令牌,内部调用身份验证服务来获取令牌。

下面是我的服务文件,我试图通过发送我准备接收令牌的postMessage在会话或本地或窗口事件中获取访问令牌。

身份验证服务.ts

public getToken() {
    let accessToken = null;
     const auth = JSON.parse(sessionStorage.getItem('auth'));
    if (auth) {
        accessToken = auth.access_token;
    } elseif (accessToken == null || accessToken === undefined) {
        accessToken = localStorage.getItem('access_token');
    }

 window.addEventListener('message', function(event){
      // this event should have all the necessary tokens
 }, false);

 // once my page is loaded to indicate that I am ready to receive the message from server side.
  parent.postMessage({ askForToken:"true"}, "*");

  return accessToken;
}

我正在发送 parent.postMessage 以使 window.addEventListener 检索数据,但事件未按预期发送令牌。

我在 authentication.service.ts 中执行上述所有这些代码实现,我不确定这是否是正确的方法。

谁能建议我正确的方法来实现此代码并适当地接收令牌?

请纠正我,因为我是第一次尝试使用令牌进行部署。

标签: javascriptangularsecurityoauthaccess-token

解决方案


源代码和演示:

https://github.com/trungk18/angular-authentication-demo-with-lazy-loading

https://stackblitz.com/edit/angular-authentication-demo-with-lazy-loading

当我们第一次运行时,我将添加该部分以延迟加载所有其他模块。这意味着只有登录页面将首次加载。登录后,将加载下一个模块。它将为我们节省大量带宽。


有一个用户,我将在登录成功后将整个对象存储到 localStorage 中。

流程可能是这样的。

  1. 打开应用程序

  2. AuthGuard将被触发。如果localStorage中存在带有令牌的用户对象,则激活路由。如果没有,则路由回登录页面。

  3. 在路由被激活并开始对服务器进行 API 调用后,JWTInterceptor将被触发以在每个后续请求中发送令牌。

  4. ErrorInterceptor检查是否有 401,然后从localStorage中删除用户并重新加载页面。这种处理更多的是针对某人尝试使用不同的令牌或对象手动更新 localStorage 的用例。如果令牌是正确的,并且没有来自服务器的修饰符,则它不应该发生。


模型

export const ConstValue = { 
    ReturnUrl: "returnUrl",
    CurrentUser: "currentUser",    
}

export const ConstRoutingValue = {
    Login: "login"
}

export interface AICreateUser {
    firstName: string;
    lastName: string;
    email: string;
    password: string;    
    roleIds: string[]
}

export interface PartnerUser extends AICreateUser {
    id: string;    
    createdAt: string;    
    token: string;    
    featureSet: string[]
}

export interface AuthDto {
    email: string;
    password: string;
}

身份验证服务

export class AuthService {
    private _currentUserSubject: BehaviorSubject<User>;
    public currentUser: Observable<User>;

    public get currentUserVal(): User {
        return this._currentUserSubject.value;
    }

    get currentUserToken() {
        return this.currentUserVal.token;
    }

    constructor(private http: HttpClient) {
        this._currentUserSubject = new BehaviorSubject<User>(this.getUserFromLocalStorage());
        this.currentUser = this._currentUserSubject.asObservable();
    }

    login(username, password) {
        return this.http.post<any>(`/users/authenticate`, { username, password })
            .pipe(map(user => {
                // store user details and jwt token in local storage to keep user logged in between page refreshes
                localStorage.setItem(ConstValue.CurrentUser, JSON.stringify(user));
                this._currentUserSubject.next(user);
                return user;
            }));
    }

    logout() {
        // remove user from local storage and set current user to null
        localStorage.removeItem(ConstValue.CurrentUser);
        this._currentUserSubject.next(null);
    }

    private getUserFromLocalStorage(): User {
        try {
          return JSON.parse(localStorage.getItem(ConstValue.CurrentUser)!);
        } catch (error) {
          return null!;
        }
      }
}

并且有一个 JwtInterceptor 将令牌附加到每个请求的标头中

export class JwtInterceptor implements HttpInterceptor {
  constructor(private authenticationService: AuthService) {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    let currentUser = this.authenticationService.currentUserVal;
    if (currentUser && currentUser.token) {
      request = request.clone({
        setHeaders: {
          Authorization: `Bearer ${currentUser.token}`
        }
      });
    }

    return next.handle(request);
  }
}

export const JWTInterceptorProvider = {
  provide: HTTP_INTERCEPTORS,
  useClass: JwtInterceptor,
  multi: true
};

ErrorInterceptor 检查是否有 401 然后从 localStorage 中删除用户重新加载页面。

export class ErrorInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthService) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(request).pipe(catchError(err => {
            if (err.status === 401) {
                // auto logout if 401 response returned from api
                this.authenticationService.logout();
                location.reload(true);
            }
            
            const error = err.error.message || err.statusText;
            return throwError(error);
        }))
    }
}

export const ErrorInterceptorProvider = { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }

还有一个 AuthGuard 可确保您在激活路由之前拥有令牌。配置 Angular 路由器时,必须包含在所有路由中,登录页面除外。

export class AuthGuard implements CanActivate {
    constructor(
        private router: Router,
        private authenticationService: AuthService
    ) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const currentUserToken = this.authenticationService.currentUserToken;
        if (currentUserToken) {
            return true;
        }

        // not logged in so redirect to login page with the return url
        this.router.navigate(['/login'], { queryParams: { [ConstValue.ReturnUrl]: state.url }});
        return false;
    }
}

如果我想使用 User 对象,我将在 AuthService 中获取 currentUser 的 public observable。例如我想在列表中显示用户用户名

export class AppComponent {
    currentUser: User;

    constructor(
        private router: Router,
        private authenticationService: AuthService
    ) {
        this.authenticationService.currentUser.subscribe(
            x => (this.currentUser = x)
        );
    }

    logout() {
        this.authenticationService.logout();
        this.router.navigate(["/login"]);
    }
}

我希望你能从中得到想法。


推荐阅读