認証されていないページにアクセスしようとしているユーザーが自動的にログインページにリダイレクトされるAsp.Net MVCの世界から来ました。
Angularでこの動作を再現しようとしています。 @CanActivateデコレータに出会いましたが、コンポーネントがまったくレンダリングされず、リダイレクトされません。
私の質問は次のとおりです。
更新:完全なスケルトンを公開しました OAuth2統合を使用したAngular 2プロジェクト Githubで、以下で説明するディレクティブを実際に表示します。
それを行う1つの方法は、directive
を使用することです。 Angular 2 components
は、基本的にページに挿入する新しいHTMLタグ(関連付けられたコード付き)です。属性ディレクティブは、何らかの動作を引き起こすタグに配置する属性です。起こる。 ここのドキュメント 。
カスタム属性が存在すると、ディレクティブを配置したコンポーネント(または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>
次に、ユーザーをアプリ内のログインビューに移動/リダイレクトし、そこで認証を処理します。現在のルートを、目的のルートに変更する必要があります。その場合、依存関係の注入を使用して、ディレクティブのconstructor()
関数で Routerオブジェクト を取得し、navigate()
メソッドを使用してユーザーをログインページに送信します(上記の私の例のように)。
これは、おそらく次のような<router-outlet>
タグを制御する一連のルートがあることを前提としています。
@RouteConfig([
{path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true},
{path: '/public', name: 'PublicPage', component: PublicPageComponent},
{path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent}
])
代わりに、OAuth2サーバーなどのexternalURLにユーザーをリダイレクトする必要がある場合、ディレクティブに次のようなことをさせます:
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()
}
}
UserService
は、ユーザーがログインしているかどうかにかかわらず、ビジネスロジックが存在する場所を表します。あなたはコンストラクタの中でDIでそれを簡単に追加することができます。
ユーザーがあなたのウェブサイト上の新しいURLにナビゲートすると、activateメソッドが現在の命令で呼び出されます。そこからあなたはURLをつかみ、それが許可されているかどうかを決めることができます。ログインページにリダイレクトしないだけの場合。
それをうまく機能させるために残っている最後のことの1つは、それをビルトインの代わりに私たちのメインコンポーネントに渡すことです。
@Component({
selector: 'app',
directives: [LoggedInRouterOutlet],
template: template
})
@RouteConfig(...)
export class AppComponent { }
渡された関数がfalseを解決すると、RouterOutlet
のactivateメソッドが呼び出されないため、このソリューションを@CanActive
ライフサイクルデコレータと一緒に使用することはできません。
また、それについての詳細なブログ記事を書いた: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492
ルーターアウトレットを上書きしないでください。これは最新のルーターリリース(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 も参照してください。
上記の素晴らしい答えに続いて、私はCanActivateChild
:子ルートの保護もしたいと思います。 ACLのような場合に有用な子ルートにguard
を追加するのに使用できます。
こんなふうになります
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
このコードを参照してください、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] },
];
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;
}
}
}