로그인 페이지로 각도 리디렉션


122

나는 Asp.Net MVC 세계에서 왔는데, 사용자가 인증되지 않은 페이지에 액세스하려고하면 자동으로 로그인 페이지로 리디렉션됩니다.

Angular에서이 동작을 재현하려고합니다. @CanActivate 데코레이터를 건너 왔지만 구성 요소가 전혀 렌더링되지 않고 리디렉션이 발생하지 않습니다.

내 질문은 다음과 같습니다.

  • Angular는이 동작을 달성하는 방법을 제공합니까?
  • 그렇다면 어떻게? 좋은 습관입니까?
  • 그렇지 않다면 Angular에서 사용자 인증을 처리하는 가장 좋은 방법은 무엇입니까?

보고 싶다면 인증 작업을 수행하는 방법을 보여주는 실제 지시문을 추가했습니다.
Michael Oryl 2015

이 답변은 유용 할 수 있습니다. stackoverflow.com/a/59008239/7059557
AmirReza-Farahlagha

답변:


86

업데이트 : Github에 OAuth2 통합 이 포함 된 전체 스켈레톤 Angular 2 프로젝트를 게시했습니다.이 프로젝트는 아래에 언급 된 지침을 실제로 보여줍니다.

이를 수행하는 한 가지 방법은 directive. components페이지에 삽입하는 기본적으로 새로운 HTML 태그 (관련 코드 포함) 인 Angular 2와 달리 속성 지시문은 일부 동작을 발생시키는 태그에 넣는 속성입니다. 여기에 문서가 있습니다 .

사용자 정의 속성이 있으면 지시문을 배치 한 구성 요소 (또는 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();
        }
    }
}

이것은 내가 작성한 인증 서비스를 사용하여 사용자가 이미 로그인했는지 여부를 확인하고 사용자가 로그 아웃하거나 시간이 초과되면 사용자를 추방 할 수 있도록 인증 이벤트에 가입 합니다.

당신도 같은 일을 할 수 있습니다. 필요한 쿠키 또는 사용자가 인증되었음을 나타내는 기타 상태 정보가 있는지 확인하는 지시어를 만들 것입니다. 찾고있는 플래그가없는 경우 사용자를 기본 공개 페이지 (예 : 나처럼) 또는 OAuth2 서버 (또는 기타)로 리디렉션합니다. 보호해야하는 구성 요소에 해당 지시어 속성을 배치합니다. 이 경우 protected위에 붙여 넣은 지시문처럼 호출 될 수 있습니다 .

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

그런 다음 사용자를 앱 내의 로그인보기로 이동 / 리디렉션하고 거기에서 인증을 처리 할 수 ​​있습니다. 현재 경로를 원하는 경로로 변경해야합니다. 따라서이 경우 종속성 주입을 사용 하여 지시문의 함수 에서 Router 개체constructor()가져온 다음 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 서버와 같은 외부 URL 로 리디렉션 해야하는 경우 지시문이 다음과 같은 작업을 수행하도록합니다.

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

4
효과가있다! 감사! 나는 또한 여기에서 다른 방법을 찾았습니다 -github.com/auth0/angular2-authentication-sample/blob/master/src/… 어떤 방법이 더 나은지 말할 수는 없지만 누군가도 유용하다고 생각할 것입니다.
Sergey

3
감사합니다 ! 또한 / protected / : returnUrl 매개 변수를 포함하는 새 경로를 추가했습니다. returnUrl은 지시문의 ngOnInit에서 가로채는 location.path ()입니다. 이렇게하면 원래 프롬프트 된 URL에 로그인 한 후 사용자를 탐색 할 수 있습니다.
Amaury

1
간단한 솔루션은 아래 답변을 참조하십시오. 이러한 공통적 인 것 (인증되지 않은 경우 리디렉션)은 단일 문장 답변으로 간단한 솔루션을 가져야합니다.
Rick O'Shea

7
참고 :이 답변은 Angular 2의 베타 또는 릴리스 후보 버전을 다루며 더 이상 Angular 2 final에 적용되지 않습니다.
jbandi

1
이 현재 사용 각도 감시에 훨씬 더 나은 솔루션이있다
mwilson

116

다음은 Angular 4를 사용하는 업데이트 된 예제 입니다 (Angular 5-8 과도 호환 됨).

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;
    }
}

전체 예제와 작업 데모를 보려면 이 게시물을 확인하십시오.


6
후속 질문이 있습니다. 임의의 값을 currentUser으로 설정 localStorage하면 보호 된 경로에 액세스 할 수 있습니까? 예. localStorage.setItem('currentUser', 'dddddd')?
JSD

2
클라이언트 측 보안을 우회합니다. 그러나 서버 측 트랜잭션에 필요한 토큰도 지워 지므로 응용 프로그램에서 유용한 데이터를 추출 할 수 없습니다.
Matt Meng

55

최종 라우터와 함께 사용

새로운 라우터의 도입으로 경로를 보호하는 것이 더 쉬워졌습니다. 서비스 역할을하는 가드를 정의하고 경로에 추가해야합니다.

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();
  }
}

이제를 경로에 전달 하고 모듈 LoggedInGuardproviders배열 에도 추가합니다 .

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을 가져와 허용 여부를 결정할 수 있습니다. 로그인 페이지로 리디렉션하지 않는 경우.

작동하도록 마지막으로 남은 것은 기본 구성 요소 대신 기본 구성 요소에 전달하는 것입니다.

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

이 솔루션은 @CanActive수명주기 데코레이터 와 함께 사용할 수 없습니다. 전달 된 함수가 false로 확인되면의 activate 메서드 RouterOutlet가 호출되지 않기 때문입니다.

또한 그것에 대한 자세한 블로그 게시물을 작성했습니다 : https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492


2
또한 그것에 대해 더 자세히 블로그 포스트를 작성 medium.com/@blacksonic86/...
Blacksonic

안녕하세요 @Blacksonic. 방금 ng2를 파기 시작했습니다. 귀하의 제안을 따랐지만 gulp-tslint 중에이 오류가 발생했습니다 Failed to lint <classname>.router-outlet.ts[15,28]. In the constructor of class "LoggedInRouterOutlet", the parameter "nameAttr" uses the @Attribute decorator, which is considered as a bad practice. Please, consider construction of type "@Input() nameAttr: string". .이 메시지를 제거하기 위해 생성자 ( "_parentRounter")에서 무엇을 변경해야하는지 파악할 수 없습니다. 이견있는 사람?
leovrf

선언은 기본 내장 객체 RouterOutlet에서 복사되어 확장 클래스와 동일한 서명을 갖습니다.이 라인에 대한 특정 tslint 규칙을 비활성화합니다
Blacksonic

mgechev 스타일 가이드 에 대한 참조를 찾았습니다 ( "@Attribute 매개 변수 데코레이터보다 입력 선호"찾기). 줄을로 변경 _parentRouter: Router, @Input() nameAttr: string,하고 tslint가 더 이상 오류를 발생시키지 않습니다. 또한 "속성"가져 오기를 각도 코어에서 "입력"으로 대체했습니다. 도움이 되었기를 바랍니다.
leovrf

1
RouterOutlet이 내보내지지 않고 확장 가능성이 없기 때문에 2.0.0-rc.1에 문제가 있습니다
mkuligowski

53

라우터 아울렛을 무시하지 마십시오! 최신 라우터 릴리스 (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


2
좋은 것, @Blacksonic의 대답은 더 이상 사용되지 않는 라우터로 완벽하게 작동했습니다. 새 라우터로 업그레이드 한 후 리팩토링을 많이해야했습니다. 귀하의 솔루션은 내가 필요한 것입니다!
evandongen

내 app.component에서 작동하도록 canActivate를 얻을 수 없습니다. 인증되지 않은 경우 사용자를 리디렉션하려고합니다. 이것은 내가 가지고있는 라우터의 버전입니다. (업데이트가 필요한 경우 git bash 명령 줄을 사용하여 어떻게합니까?) 내가 가지고있는 버전 : "@ angular / router": "2.0.0-rc.1"
AngularM

동일한 클래스 (AuthGuard)를 사용하여 다른 구성 요소 경로를 보호 할 수 있습니까?
tsiro

4

위의 멋진 답변에 따라 나는 또한 CanActivateChild어린이 경로를 보호하고 싶습니다 . guardACL과 같은 경우에 유용한 하위 경로 를 추가 하는 데 사용할 수 있습니다.

이렇게 간다

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 에서 가져온 것입니다.


2

이 코드, 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

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;      
    }
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.