首页 文章

Angular重定向到登录页面

提问于
浏览
102

我来自Asp.Net MVC世界,用户试图访问未授权的页面会自动重定向到登录页面 .

我试图在Angular上重现这种行为 . 我来到@CanActivate装饰器,但它导致组件根本没有渲染,没有重定向 .

我的问题如下:

  • Angular是否提供了实现此行为的方法?

  • 若然,怎么样?这是一个好习惯吗?

  • 如果没有,在Angular中处理用户授权的最佳做法是什么?

7 回答

  • 3

    Usage with the final router

    随着新路由器的推出,保护路线变得更加容易 . 您必须定义一个作为服务的防护,并将其添加到路由中 .

    import { Injectable } from '@angular/core';
    import { CanActivate } from '@angular/router';
    import { UserService } from '../../auth';
    
    @Injectable()
    export class LoggedInGuard implements CanActivate {
      constructor(user: UserService) {
        this._user = user;
      }
    
      canActivate() {
        return this._user.isLoggedIn();
      }
    }
    

    现在将 LoggedInGuard 传递给路径,并将其添加到模块的 providers 数组中 .

    import { LoginComponent } from './components/login.component';
    import { HomeComponent } from './components/home.component';
    import { LoggedInGuard } from './guards/loggedin.guard';
    
    const routes = [
        { path: '', component: HomeComponent, canActivate: [LoggedInGuard] },
        { path: 'login', component: LoginComponent },
    ];
    

    模块声明:

    @NgModule({
      declarations: [AppComponent, HomeComponent, LoginComponent]
      imports: [HttpModule, BrowserModule, RouterModule.forRoot(routes)],
      providers: [UserService, LoggedInGuard],
      bootstrap: [AppComponent]
    })
    class AppModule {}
    

    关于它如何与最终版本一起使用的详细博客文章:https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

    Usage with the deprecated router

    更强大的解决方案是扩展 RouterOutlet 并在激活路由时检查用户是否已登录 . 这样您就不必将指令复制并粘贴到每个组件 . 加上基于子组件的重定向可能会产生误导 .

    @Directive({
      selector: 'router-outlet'
    })
    export class LoggedInRouterOutlet extends RouterOutlet {
      publicRoutes: Array;
      private parentRouter: Router;
      private userService: UserService;
    
      constructor(
        _elementRef: ElementRef, _loader: DynamicComponentLoader,
        _parentRouter: Router, @Attribute('name') nameAttr: string,
        userService: UserService
      ) {
        super(_elementRef, _loader, _parentRouter, nameAttr);
    
        this.parentRouter = _parentRouter;
        this.userService = userService;
        this.publicRoutes = [
          '', 'login', 'signup'
        ];
      }
    
      activate(instruction: ComponentInstruction) {
        if (this._canActivate(instruction.urlPath)) {
          return super.activate(instruction);
        }
    
        this.parentRouter.navigate(['Login']);
      }
    
      _canActivate(url) {
        return this.publicRoutes.indexOf(url) !== -1 || this.userService.isLoggedIn()
      }
    }
    

    无论用户是否登录,_549085都代表业务逻辑所在的位置 . 您可以使用构造函数中的DI轻松添加它 .

    当用户导航到您网站上的新网址时,将使用当前指令调用activate方法 . 从中你可以 grab 网址并决定是否允许 . 如果不只是重定向到登录页面 .

    使其工作的最后一件事仍然是将其传递给我们的主要组件而不是内置组件 .

    @Component({
      selector: 'app',
      directives: [LoggedInRouterOutlet],
      template: template
    })
    @RouteConfig(...)
    export class AppComponent { }
    

    此解决方案不能与 @CanActive 生命周期装饰器一起使用,因为如果传递给它的函数解析为false,则不会调用 RouterOutlet 的activate方法 .

    还写了一篇关于它的详细博客文章:https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

  • 82

    Update: 我在Github上发布了一个完整的骨架Angular 2 project with OAuth2 integration,它显示了下面提到的指令 .

    一种方法是使用 directive . 与Angular 2 components (基本上是新的HTML标记(带有相关代码))不同,您插入到页面中,属性指令是您放入标记中的属性,会导致某些行为发生 . Docs here .

    您的自定义属性的存在会导致您将指令放入的组件(或HTML元素)发生 . 请考虑我用于当前Angular2 / OAuth2应用程序的此指令:

    import {Directive, OnDestroy} from 'angular2/core';
    import {AuthService} from '../services/auth.service';
    import {ROUTER_DIRECTIVES, Router, Location} from "angular2/router";
    
    @Directive({
        selector: '[protected]'
    })
    export class ProtectedDirective implements OnDestroy {
        private sub:any = null;
    
        constructor(private authService:AuthService, private router:Router, private location:Location) {
            if (!authService.isAuthenticated()) {
                this.location.replaceState('/'); // clears browser history so they can't navigate with back button
                this.router.navigate(['PublicPage']);
            }
    
            this.sub = this.authService.subscribe((val) => {
                if (!val.authenticated) {
                    this.location.replaceState('/'); // clears browser history so they can't navigate with back button
                    this.router.navigate(['LoggedoutPage']); // tells them they've been logged out (somehow)
                }
            });
        }
    
        ngOnDestroy() {
            if (this.sub != null) {
                this.sub.unsubscribe();
            }
        }
    }
    

    这使用我编写的身份验证服务来确定用户是否已经登录并且 also subscribes 到身份验证事件,以便在用户注销或超时时可以将用户踢出 .

    你可以做同样的事情 . 您'd create a directive like mine that checks for the presence of a necessary cookie or other state information that indicates that the user is authenticated. If they don' t有您正在寻找的那些标志,将用户重定向到您的主公共页面(就像我这样)或您的OAuth2服务器(或其他) . 您可以将该指令属性放在需要保护的任何组件上 . 在这种情况下,它可能被称为 protected ,就像我上面粘贴的指令一样 .

    <members-only-info [protected]></members-only-info>
    

    然后,您可能希望将用户导航/重定向到应用程序中的登录视图,并在那里处理身份验证 . 您'd have to change the current route to the one you wanted to do that. So in that case you' d使用依赖注入在您的指令的 constructor() 函数中获取Router object然后使用 navigate() 方法将用户发送到您的登录页面(如上例所示) .

    这假设您有一系列路径控制 <router-outlet> 标记,看起来像这样,也许:

    @RouteConfig([
        {path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true},
        {path: '/public', name: 'PublicPage', component: PublicPageComponent},
        {path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent}
    ])
    

    相反,如果您需要将用户重定向到 external URL(例如OAuth2服务器),那么您的指令应该执行以下操作:

    window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope
    
  • 56

    请参阅此代码,auth.ts文件

    import { CanActivate } from '@angular/router';
    import { Injectable } from '@angular/core';
    import {  } from 'angular-2-local-storage';
    import { Router } from '@angular/router';
    
    @Injectable()
    export class AuthGuard implements CanActivate {
    constructor(public localStorageService:LocalStorageService, private router: Router){}
    canActivate() {
    // Imaginary method that is supposed to validate an auth token
    // and return a boolean
    var logInStatus         =   this.localStorageService.get('logInStatus');
    if(logInStatus == 1){
        console.log('****** log in status 1*****')
        return true;
    }else{
        console.log('****** log in status not 1 *****')
        this.router.navigate(['/']);
        return false;
    }
    
    
    }
    
    }
    // *****And the app.routes.ts file is as follow ******//
          import {  Routes  } from '@angular/router';
          import {  HomePageComponent   } from './home-page/home- page.component';
          import {  WatchComponent  } from './watch/watch.component';
          import {  TeachersPageComponent   } from './teachers-page/teachers-page.component';
          import {  UserDashboardComponent  } from './user-dashboard/user- dashboard.component';
          import {  FormOneComponent    } from './form-one/form-one.component';
          import {  FormTwoComponent    } from './form-two/form-two.component';
          import {  AuthGuard   } from './authguard';
          import {  LoginDetailsComponent } from './login-details/login-details.component';
          import {  TransactionResolver } from './trans.resolver'
          export const routes:Routes    =   [
        { path:'',              component:HomePageComponent                                                 },
        { path:'watch',         component:WatchComponent                                                },
        { path:'teachers',      component:TeachersPageComponent                                         },
        { path:'dashboard',     component:UserDashboardComponent,       canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
        { path:'formone',       component:FormOneComponent,                 canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
        { path:'formtwo',       component:FormTwoComponent,                 canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
        { path:'login-details', component:LoginDetailsComponent,            canActivate: [AuthGuard]    },
    
    ];
    
  • 48

    这是使用Angular 4的更新示例

    Routes with home route protected by AuthGuard

    import { Routes, RouterModule } from '@angular/router';
    
    import { LoginComponent } from './login/index';
    import { HomeComponent } from './home/index';
    import { AuthGuard } from './_guards/index';
    
    const appRoutes: Routes = [
        { path: 'login', component: LoginComponent },
    
        // home route protected by auth guard
        { path: '', component: HomeComponent, canActivate: [AuthGuard] },
    
        // otherwise redirect to home
        { path: '**', redirectTo: '' }
    ];
    
    export const routing = RouterModule.forRoot(appRoutes);
    

    AuthGuard redirects to login page if user isn't logged in

    更新以将查询参数中的原始URL传递到登录页面

    import { Injectable } from '@angular/core';
    import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
    
    @Injectable()
    export class AuthGuard implements CanActivate {
    
        constructor(private router: Router) { }
    
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
            if (localStorage.getItem('currentUser')) {
                // logged in so return true
                return true;
            }
    
            // not logged in so redirect to login page with the return url
            this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
            return false;
        }
    }
    

    有关完整示例和工作演示,您可以查看this post

  • 91

    1. Create a guard as seen below. 2. Install ngx-cookie-service to get cookies returned by external SSO. 3. Create ssoPath in environment.ts (SSO Login redirection). 4. Get the state.url and use encodeURIComponent.

    import { Injectable } from '@angular/core';
    import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from 
      '@angular/router';
    import { CookieService } from 'ngx-cookie-service';
    import { environment } from '../../../environments/environment.prod';
    
    @Injectable()
    export class AuthGuardService implements CanActivate {
      private returnUrl: string;
      constructor(private _router: Router, private cookie: CookieService) {}
    
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
        if (this.cookie.get('MasterSignOn')) {
          return true;
        } else {
          let uri = window.location.origin + '/#' + state.url;
          this.returnUrl = encodeURIComponent(uri);      
          window.location.href = environment.ssoPath +  this.returnUrl ;   
          return false;      
        }
      }
    }
    
  • 2

    按照上面的精彩答案,我还想 CanActivateChild :守卫儿童路线 . 它可用于将 guard 添加到有助于ACL等情况的子路径中

    它是这样的

    src / app / auth-guard.service.ts(摘录)

    import { Injectable }       from '@angular/core';
    import {
      CanActivate, Router,
      ActivatedRouteSnapshot,
      RouterStateSnapshot,
      CanActivateChild
    }                           from '@angular/router';
    import { AuthService }      from './auth.service';
    
    @Injectable()
    export class AuthGuard implements CanActivate, CanActivateChild {
      constructor(private authService: AuthService, private router:     Router) {}
    
      canActivate(route: ActivatedRouteSnapshot, state:    RouterStateSnapshot): boolean {
        let url: string = state.url;
        return this.checkLogin(url);
      }
    
      canActivateChild(route: ActivatedRouteSnapshot, state:  RouterStateSnapshot): boolean {
        return this.canActivate(route, state);
      }
    
    /* . . . */
    }
    

    src / app / admin / admin-routing.module.ts(摘录)

    const adminRoutes: Routes = [
      {
        path: 'admin',
        component: AdminComponent,
        canActivate: [AuthGuard],
        children: [
          {
            path: '',
            canActivateChild: [AuthGuard],
            children: [
              { path: 'crises', component: ManageCrisesComponent },
              { path: 'heroes', component: ManageHeroesComponent },
              { path: '', component: AdminDashboardComponent }
            ]
          }
        ]
      }
    ];
    
    @NgModule({
      imports: [
        RouterModule.forChild(adminRoutes)
      ],
      exports: [
        RouterModule
      ]
    })
    export class AdminRoutingModule {}
    

    这取自https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

  • 0

    请不要覆盖路由器插座!这是最新路由器版本(3.0测试版)的噩梦 .

    而是使用接口CanActivate和CanDeactivate,并在路由定义中将类设置为canActivate / canDeactivate .

    像那样:

    { path: '', component: Component, canActivate: [AuthGuard] },
    

    类:

    @Injectable()
    export class AuthGuard implements CanActivate {
    
        constructor(protected router: Router, protected authService: AuthService)
        {
    
        }
    
        canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
    
            if (state.url !== '/login' && !this.authService.isAuthenticated()) {
                this.router.navigate(['/login']);
                return false;
            }
    
            return true;
        }
    }
    

    另见:https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

相关问题