Угловой редирект на страницу входа
Я пришел из Asp.Net мир MVC, где пользователи пытаются получить доступ к странице, они не авторизованы автоматически перенаправляются на страницу входа.
Я пытаюсь воспроизвести это поведение на угловой. Я пришел через @ CanActivate decorator, но это приводит к тому, что компонент вообще не отображается, без перенаправления.
мой вопрос заключается в следующем:
- обеспечивает ли Angular способ достижения такого поведения?
- если да, то как? Это хорошая практика?
- если нет, то что было бы лучшей практикой для обработки авторизации пользователя в Angular?
7 ответов:
обновление: я опубликовал полный скелет угловой 2 проект с интеграцией OAuth2 на Github, который показывает директиву, упомянутую ниже в действии.
один из способов сделать это с помощью
directive
. В Отличие От Угловой 2components
, которые в основном являются новыми тегами HTML (со связанным кодом), которые вы вставляете на свою страницу, атрибутивная директива-это атрибут, который вы помещаете в тег, который вызывает некоторое поведение. Docs здесь.наличие пользовательского атрибута приводит к тому, что происходит с компонентом (или элементом 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(); } } }
это делает использование службы аутентификации я написал, чтобы определить, является ли пользователь уже вошел в систему и согласен к событию аутентификации, чтобы он мог выгнать пользователя, если он или она выходит из системы или тайм-аут.
вы могли бы сделать то же самое. Вы бы создали директиву, подобную моей, которая проверяет наличие необходимого файла cookie или другой информации о состоянии, которая указывает, что пользователь аутентифицирован. Если у них нет тех флагов, которые вы ищете, перенаправьте пользователя на свою главную общедоступную страницу (например, я) или ваш сервер OAuth2 (или что-то еще). Вы бы поместили этот атрибут директивы на любой компонент, который должен быть защищен. В этом случае его можно было бы назвать
protected
как в директиве, которую я вставил выше.<members-only-info [protected]></members-only-info>
затем вы хотите перейти / перенаправить пользователя в представление входа в приложение и обработать аутентификацию там. Вам придется изменить текущий маршрут на тот, который вы хотите сделать. Так что в этом случае вы бы использовали инъекцию зависимостей, чтобы получить маршрутизатор объект в функции, а затем использовать
navigate()
способ отправки пользователя на страницу входа (как в моем примере выше.)это предполагает, что у вас есть ряд маршрутов, где-то контролирующих a
<router-outlet>
тег, который выглядит примерно так, возможно:@RouteConfig([ {path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true}, {path: '/public', name: 'PublicPage', component: PublicPageComponent}, {path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent} ])
если вместо этого вам нужно было перенаправить пользователя на внешний 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
вот обновленный пример использования Angular 4
маршруты с домашним маршрутом защищены 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 перенаправляет на страницу входа, если пользователь не вошел в систему
обновлено для передачи исходного 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; } }
для полного примера и рабочей демонстрации вы можете проверить этот пост
использование с конечным маршрутизатором
С введением нового маршрутизатора стало легче охранять маршруты. Вы должны определить охрану, которая действует как служба, и добавить ее в маршрут.
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
использование с устаревшим маршрутизатором
более надежным решением является расширение
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() } }
The
UserService
обозначает место где бизнес логика находится ли пользователь вошел в систему или нет. Вы можете легко добавить его с помощью DI в конструкторе.когда пользователь переходит на новый url-адрес на вашем веб-сайте, метод activate вызывается с текущей инструкцией. Из него вы можете захватить url-адрес и решить, разрешено ли это или нет. Если не просто перенаправить на страницу входа.
последнее, что остается, чтобы заставить его работать, это передать его в наш основной компонент вместо встроенного один.
@Component({ selector: 'app', directives: [LoggedInRouterOutlet], template: template }) @RouteConfig(...) export class AppComponent { }
такое решение не может быть использовано с
@CanActive
lifecycle decorator, потому что если переданная ему функция разрешает false, метод activateRouterOutlet
не назовешь.также написал подробный пост в блоге об этом: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492
пожалуйста, не перегружайте роутер выход! Это кошмар с последней версией маршрутизатора (3.0 beta).
вместо этого используйте интерфейсы 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
после удивительных ответов выше я также хотел бы
CanActivateChild
: охрана дочерних маршрутов. Он может быть использован для добавленияguard
для детей маршруты полезны для таких случаев, как ACLsэто
src / app / auth-guard.услуга.ТС (отрывок)
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/приложение/админ/администратор-маршрутизации.модуль.ТС (отрывок)
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
обратитесь к этому коду, автор.файл 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] }, ];
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; } } }