Angular 6 JWT 身份验证

Posted

技术标签:

【中文标题】Angular 6 JWT 身份验证【英文标题】:Angular 6 JWT authentication 【发布时间】:2019-03-15 07:17:36 【问题描述】:

我正在使用 JWT 令牌保护我的 Angular 6 应用程序。我有一个身份验证服务来保护 UI 元素:

export class AuthService 

  constructor(
    private http: HttpClient,
    private router: Router,
    public jwtHelper: JwtHelperService,
  ) 

  isAuthenticated(): boolean 
    return !this.isTokenExpired;
  

然后我可以在我的组件中调用它:

<span class="page-nav" *ngIf="!authService.isAuthenticated()">
    <a mat-button routerLink="/login">
        Log In
    </a>
</span>

我的路线也有一个身份验证守卫:

export class AuthGuard implements CanActivate 
  constructor(private authService: AuthService, private router: Router) 

  canActivate(): boolean 
    if (!this.authService.isAuthenticated()) 
      this.router.navigate(['/login']);
      return false;
    
    return true;
  

这一切都很好,除非令牌变得无效,然后当前页面不会路由到login

例如,如果我登录,获得一个有效的 JWT 令牌,我可以导航到一个受保护的页面,并出现受保护的 UI 元素(导航等)。如果我随后通过 chrome 开发工具从本地存储中删除 JWT 令牌,然后单击导航,受保护的 UI 元素按预期消失,但受保护的页面仍然存在,不会调用路由身份验证保护,并且仅路由到 login导航到其他页面时。

我尝试在isAuthenticated() authService 方法中进行路由,但这会导致无限循环。如何在调用 AuthGuard 之外自动路由到 login

编辑

路由:

const routes: Routes = [
   path: '', redirectTo: '/products', pathMatch: 'full' ,
  
    path: 'products',
    component: ProductsComponent,
    canActivate: [AuthGuard],
    children: [
       path: ':id', component: ProductComponent, canActivate: [AuthGuard] ,
    ],
  ,
  
    path: 'search',
    component: SearchComponent,
    canActivate: [AuthGuard],
  ,
   path: 'login', component: LoginComponent ,
];

@NgModule(
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
)
export class AppRoutingModule 

编辑:我意识到这是预期的行为,因为 @GangadharJannu mentions,路由器不知道 UI 元素调用了 isAuthenticated() 方法,现在被隐藏了正在返回false,直到调用 AuthService 刷新方法并执行相同的检查。我很想知道当 UI 元素调用 isAuthenticated() 方法时让路由器知道导航到 login 的最佳方法?

编辑:我的工作解决方案:

 import  map  from 'rxjs/operators';
 import  interval  from 'rxjs';
 import  JwtHelperService  from '@auth0/angular-jwt';

 ...

 public validateToken(): void 
    interval(1000).pipe(
      map(() => 
        const isExpired: boolean = this.jwtHelper.isTokenExpired(
          localStorage.getItem('jwt_token'),
        );
        if (isExpired) 
          this.logout();
        
      ),
    );
  

【问题讨论】:

我们可以看到路由设置吗? 可能是因为this.isTokenExpired没有正确设置。 @NimaHakimi 我已经添加了路由。 @Ploppy 不,不是这样,应用程序有一个刷新计时器,每分钟更新一次 JWT 令牌,它还会检查当前令牌并在无效时导航到登录。此外,它适用于 UI 元素,只是不会被路由到 /login 的任何东西访问。 哪一个是您的受保护组件? 【参考方案1】:

我们有父级空状态只是为了保护我们应用程序的受保护部分,您可能想尝试一下。尝试类似这样的路由配置:

const routes: Routes = [ 
    path: '', 
    canActivateChild: [AuthGuard], 
    runGuardsAndResolvers: 'always',
    children: [
        path: 'products',
        component: ProductsComponent,
        children: [
           path: ':id', component: ProductComponent ,
        ],
      , 
        path: 'search',
        component: SearchComponent
    ]
  ,
   path: '', redirectTo: '/products', pathMatch: 'full' ,
   path: 'login', component: LoginComponent ,
];

注意runGuardsAndResolvers: 'always' - 它告诉 Angular 始终运行守卫和解析器,即使在导航到相同状态时也是如此。

编辑

所以基本上你想观察本地存储并对变化做出反应,所以当令牌从存储中删除或过期时,你想立即导航到登录组件。一些想法:

您可以将过期检查的频率提高到 1 秒,这不会造成太大影响,因为目前您正在检查令牌是否在每个渲染周期都过期 您可以在每次刷新令牌时设置一个定时可观察对象,以便它在过期后一秒触发并导航到登录 要观察本地存储本身的变化,您可以搜索一些具有此功能的库 (like this one) 或直接使用 native storage change event 如果您不关心用户手动删除令牌时的用例,那么您可以在您的应用从本地存储中删除令牌时导航到登录 (question/answers)

【讨论】:

谢谢@Ludevik,但我仍然有同样的行为。问题是它根本没有路由。单击导航只是检查身份验证的状态,如果它无效,则不会对 routerLink 进行操作。 所以您在点击导航时有一些自定义检查?当用户不再经过身份验证时,它什么也不做? 导航按钮上有*ngIf="authService.isAuthenticated()"。它们在不再经过身份验证时消失,但页面不会路由到login,直到刷新令牌,这可能是一分钟后,或者我明确地导航到其他地方。 @bordeltabernacle。您正在经历的是预期的行为。 Angular 路由器不知道您何时删除密钥。您的导航消散器有角度的原因是为模板中的表达式分配绑定 谢谢@GangadharJannu 是的,我认为这是预期的行为,我很想知道在这种情况下如何让路由器路由到login

以上是关于Angular 6 JWT 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Angular 和 Laravel 身份验证 JWT?

使用 JWT 对 Angular Universal 进行身份验证

Symfony 3 + JWT + angular 2 + 身份验证(选项方法)

使用 Angular 和 spring 进行 Jwt 身份验证

Angular 2 和 JWT 身份验证 (Auth0)

angular2 与 Slim 框架 jwt 身份验证