나는 Asp.Net MVC 세계에서 왔는데, 사용자가 인증되지 않은 페이지에 액세스하려고하면 자동으로 로그인 페이지로 리디렉션됩니다.
Angular에서이 동작을 재현하려고합니다. @CanActivate 데코레이터를 건너 왔지만 구성 요소가 전혀 렌더링되지 않고 리디렉션이 발생하지 않습니다.
내 질문은 다음과 같습니다.
- Angular는이 동작을 달성하는 방법을 제공합니까?
- 그렇다면 어떻게? 좋은 습관입니까?
- 그렇지 않다면 Angular에서 사용자 인증을 처리하는 가장 좋은 방법은 무엇입니까?
답변
업데이트 : 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
답변
다음은 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;
}
}
전체 예제와 작업 데모를 보려면 이 게시물을 확인하십시오.
답변
최종 라우터와 함께 사용
새로운 라우터의 도입으로 경로를 보호하는 것이 더 쉬워졌습니다. 서비스 역할을하는 가드를 정의하고 경로에 추가해야합니다.
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을 가져와 허용 여부를 결정할 수 있습니다. 로그인 페이지로 리디렉션하지 않는 경우.
작동하도록 마지막으로 남은 것은 기본 구성 요소 대신 기본 구성 요소에 전달하는 것입니다.
@Component({
selector: 'app',
directives: [LoggedInRouterOutlet],
template: template
})
@RouteConfig(...)
export class AppComponent { }
이 솔루션은 @CanActive
수명주기 데코레이터 와 함께 사용할 수 없습니다. 전달 된 함수가 false로 확인되면의 activate 메서드 RouterOutlet
가 호출되지 않기 때문입니다.
또한 그것에 대한 자세한 블로그 게시물을 작성했습니다 : 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
어린이 경로를 보호하고 싶습니다 . 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 에서 가져온 것입니다.
답변
이 코드, 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;
}
}
}