【问题标题】:How to add a mobile access token in Angular 8 application?如何在 Angular 8 应用程序中添加移动访问令牌?
【发布时间】:2019-11-04 01:05:17
【问题描述】:

我已将我的应用程序部署到 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中完成以上所有代码实现,我不确定这样做是否正确。

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

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

【问题讨论】:

  • 可以更多地描述您的应用程序流程。基本上它将有一个登录表单。如果您设法登录到系统,您将拥有一个令牌,并将其存储在我怀疑的 localStorage 中。并且在每个后续,您将检查该令牌并将其一起发送到每个 http 请求的标头中?将用户重定向到 401 页面的代码在哪里,这在你的保护范围内吗?
  • @trungk18 是的,重定向到 401 是错误拦截器保护。关于流程-当我登录时,我有一个主页,从主页上,我有一个打开子应用程序的链接。当子应用程序即将加载时,我调用身份验证服务将消息发布到父窗口(即主页) - 从这个父窗口,我应该得到一个 access_token。
  • 那么当你开始从主应用导航到子应用时出现了问题?你检查了令牌,这仍然是有效的吗?在我的应用程序中,我通过将令牌存储在 localStore 中来执行相同的操作,并且我有一个 AuthService 来管理所有这些。但是当你得到 401 时,有一件事是肯定你应该将用户重定向到登录页面,而不是 401。有很多用例我们希望强制用户再次登录并使所有令牌无效。
  • 很多子应用只有一个登录入口。主页团队说要发送一个名为“askForToken”的 postMessage,以便他们将 access_token 发送给我,我必须执行 window.addEventListener 才能进入 event.data.access_token。
  • 您能否确认以上所有代码应该只写在身份验证服务中还是应该在我的应用程序中的其他地方提到?

标签: javascript angular security oauth access-token


【解决方案1】:

源代码和演示:

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"]);
    }
}

我希望你能从中得到灵感。

【讨论】:

  • 感谢您的回复,我能够部分理解,但我需要了解如何传播它的适当流程。由于我是初学者,所以请你给我一个演示,以便我能完全理解。感谢您的热情支持。请尽可能分享演示,我可以等待
  • 我现在正在准备。希望今天能给你。
  • 我添加了github repo,你可以下载并在你的机器上运行
  • 不客气。如果您有任何问题,请在此处告诉我,或者只是在 repo 本身上创建一个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-25
  • 2012-04-12
  • 2020-06-29
  • 2016-03-27
  • 2019-03-08
  • 2012-03-19
  • 1970-01-01
相关资源
最近更新 更多