Chuyển hướng góc tới trang đăng nhập


122

Tôi đến từ thế giới Asp.Net MVC nơi người dùng cố gắng truy cập vào một trang mà họ không được phép sẽ tự động được chuyển hướng đến trang đăng nhập.

Tôi đang cố gắng tái tạo hành vi này trên Angular. Tôi đã đi qua trình trang trí @CanActivate, nhưng nó dẫn đến thành phần không hiển thị chút nào, không chuyển hướng.

Câu hỏi của tôi là như sau:

  • Angular có cung cấp cách để đạt được hành vi này không?
  • Nếu vậy, làm thế nào? Nó có phải là một thực hành tốt?
  • Nếu không, thực tiễn tốt nhất để xử lý ủy quyền người dùng trong Angular là gì?

Tôi đã thêm một chỉ thị thực tế cho thấy cách thực hiện nội dung auth, nếu bạn quan tâm.
Michael Oryl

Câu trả lời này có thể hữu ích: stackoverflow.com/a/59008239/7059557
AmirReza-Farahlagha

Câu trả lời:


86

Cập nhật: Tôi đã xuất bản dự án Angular 2 hoàn chỉnh với tích hợp OAuth2 trên Github cho thấy lệnh được đề cập bên dưới đang hoạt động.

Một cách để làm điều đó sẽ là sử dụng a directive. Không giống như Angular 2 components, về cơ bản là các thẻ HTML mới (với mã được liên kết) mà bạn chèn vào trang của mình, chỉ thị thuộc tính là một thuộc tính mà bạn đặt trong thẻ khiến một số hành vi xảy ra. Tài liệu ở đây .

Sự hiện diện của thuộc tính tùy chỉnh của bạn khiến mọi thứ xảy ra với thành phần (hoặc phần tử HTML) mà bạn đã đặt chỉ thị vào. Hãy xem xét chỉ thị này mà tôi sử dụng cho ứng dụng Angular2 / OAuth2 hiện tại của mình:

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

Điều này sử dụng dịch vụ Xác thực mà tôi đã viết để xác định xem người dùng đã đăng nhập hay chưa và cũng đăng ký sự kiện xác thực để nó có thể loại bỏ người dùng nếu họ đăng xuất hoặc hết thời gian.

Bạn có thể làm điều tương tự. Bạn sẽ tạo một chỉ thị như của tôi để kiểm tra sự hiện diện của cookie cần thiết hoặc thông tin trạng thái khác cho biết rằng người dùng đã được xác thực. Nếu họ không có những cờ mà bạn đang tìm kiếm, hãy chuyển hướng người dùng đến trang công khai chính của bạn (như tôi làm) hoặc máy chủ OAuth2 của bạn (hoặc bất cứ thứ gì). Bạn sẽ đặt thuộc tính chỉ thị đó trên bất kỳ thành phần nào cần được bảo vệ. Trong trường hợp này, nó có thể được gọi protectednhư trong chỉ thị mà tôi đã dán ở trên.

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

Sau đó, bạn muốn điều hướng / chuyển hướng người dùng đến chế độ xem đăng nhập trong ứng dụng của bạn và xử lý xác thực ở đó. Bạn sẽ phải thay đổi tuyến đường hiện tại thành tuyến đường bạn muốn làm điều đó. Vì vậy, trong trường hợp đó, bạn sẽ sử dụng phương thức tiêm phụ thuộc để lấy một đối tượng Bộ định tuyến trong constructor()chức năng chỉ thị của bạn và sau đó sử dụngnavigate() phương thức để đưa người dùng đến trang đăng nhập của bạn (như trong ví dụ của tôi ở trên).

Điều này giả định rằng bạn có một loạt các tuyến ở đâu đó kiểm soát một <router-outlet>thẻ trông giống như sau, có lẽ:

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

Thay vào đó, nếu bạn cần chuyển hướng người dùng đến một URL bên ngoài , chẳng hạn như máy chủ OAuth2 của bạn, thì bạn sẽ có chỉ thị của mình thực hiện một số việc như sau:

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
Nó hoạt động! cảm ơn! Tôi cũng tìm thấy một phương pháp khác ở đây - github.com/auth0/angular2-authentication-sample/blob/master/src/… Tôi không thể nói phương pháp nào tốt hơn, nhưng có thể ai đó cũng sẽ thấy nó hữu ích.
Sergey

3
Cảm ơn bạn ! Tôi cũng đã thêm một tuyến mới chứa tham số / protected /: returnUrl, returnUrl là location.path () bị chặn tại ngOnInit của chỉ thị. Điều này cho phép điều hướng người dùng sau khi đăng nhập vào url được nhắc ban đầu.
Amaury

1
Xem câu trả lời dưới đây để có một giải pháp đơn giản. Bất kỳ điều gì phổ biến này (chuyển hướng nếu không được xác thực) nên có một giải pháp đơn giản với một câu trả lời duy nhất.
Rick O'Shea

7
Lưu ý: Câu trả lời này đề cập đến phiên bản beta hoặc phiên bản ứng viên phát hành của Angular 2 và không còn áp dụng cho Angular 2 cuối cùng.
jbandi

1
Có một giải pháp tốt hơn nhiều để sử dụng này ngay bây giờ kiễu góc Guards
mwilson

116

Đây là một ví dụ cập nhật sử dụng Angular 4 (cũng tương thích với Angular 5-8)

Các tuyến đường có tuyến nhà được AuthGuard bảo vệ

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 chuyển hướng đến trang đăng nhập nếu người dùng chưa đăng nhập

Cập nhật để chuyển url gốc trong tham số truy vấn đến trang đăng nhập

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

Để biết ví dụ đầy đủ và bản demo hoạt động, bạn có thể xem bài đăng này


6
Tôi có một sau lên Q, được không nếu thiết lập một giá trị tùy ý để currentUserlocalStoragevẫn sẽ có thể truy cập các tuyến đường được bảo vệ? ví dụ. localStorage.setItem('currentUser', 'dddddd')?
jsd

2
Nó sẽ bỏ qua bảo mật phía máy khách. Nhưng nó cũng sẽ xóa mã thông báo cần thiết cho các giao dịch phía máy chủ, vì vậy không có dữ liệu hữu ích nào có thể được trích xuất từ ​​ứng dụng.
Matt Meng

55

Cách sử dụng với bộ định tuyến cuối cùng

Với sự ra đời của bộ định tuyến mới, việc bảo vệ các tuyến đường trở nên dễ dàng hơn. Bạn phải xác định một người bảo vệ, hoạt động như một dịch vụ và thêm nó vào tuyến đường.

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

Bây giờ chuyển LoggedInGuardtới tuyến đường và cũng thêm nó vào providersmảng của mô-đun.

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

Khai báo mô-đun:

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

Bài đăng trên blog chi tiết về cách nó hoạt động với bản phát hành cuối cùng: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Cách sử dụng với bộ định tuyến không dùng nữa

Một giải pháp mạnh mẽ hơn là mở rộng RouterOutletvà khi kích hoạt kiểm tra tuyến đường nếu người dùng đã đăng nhập. Bằng cách này, bạn không phải sao chép và dán chỉ thị của mình vào mọi thành phần. Cộng với việc chuyển hướng dựa trên một thành phần con có thể gây hiểu nhầm.

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

Các UserService tắt của nơi chứa logic nghiệp vụ của bạn cho dù người dùng có đăng nhập hay không. Bạn có thể thêm nó một cách dễ dàng với DI trong hàm tạo.

Khi người dùng điều hướng đến một url mới trên trang web của bạn, phương pháp kích hoạt được gọi với Hướng dẫn hiện tại. Từ đó, bạn có thể lấy url và quyết định xem nó có được phép hay không. Nếu không chỉ chuyển hướng đến trang đăng nhập.

Một điều cuối cùng còn lại để làm cho nó hoạt động, là chuyển nó đến thành phần chính của chúng ta thay vì được tích hợp sẵn.

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

Không thể sử dụng giải pháp này với @CanActivetrình trang trí vòng đời, bởi vì nếu hàm được truyền cho nó giải quyết sai, thì phương thức kích hoạt của hàm RouterOutletsẽ không được gọi.

Cũng đã viết một bài đăng blog chi tiết về nó: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492


2
Cũng đã viết một bài đăng blog chi tiết thêm về nó medium.com/@blacksonic86/...
Blacksonic

Xin chào @Blacksonic. Mới bắt đầu đào ng2. Tôi đã làm theo đề xuất của bạn nhưng cuối cùng vẫn gặp lỗi này trong quá trình 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". Không thể tìm ra những gì cần thay đổi trong hàm tạo ("_parentRounter") để loại bỏ thông báo này. Có suy nghĩ gì không?
leovrf

khai báo được sao chép từ đối tượng được xây dựng bên trong RouterOutlet để có cùng chữ ký với lớp mở rộng, tôi sẽ tắt quy tắc tslint cụ thể cho dòng này
Blacksonic

Tôi đã tìm thấy tài liệu tham khảo trên hướng dẫn kiểu mgechev (tìm "Ưu tiên đầu vào hơn trình trang trí tham số @Attribute"). Đã thay đổi dòng thành _parentRouter: Router, @Input() nameAttr: string,và tslint không còn gây ra lỗi nữa. Đồng thời thay thế nhập "Thuộc tính" thành "Đầu vào" từ lõi góc. Hi vọng điêu nay co ich.
leovrf

1
Có một vấn đề với 2.0.0-rc.1 vì RouterOutlet không xuất khẩu và không có khả năng mở rộng nó
mkuligowski

53

Vui lòng không ghi đè Router Outlet! Đó là một cơn ác mộng với bản phát hành bộ định tuyến mới nhất (3.0 beta).

Thay vào đó, hãy sử dụng các giao diện CanActivate và CanDeactivate và đặt lớp là canActivate / canDeactivate trong định nghĩa tuyến đường của bạn.

Như vậy:

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

Lớp học:

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

Xem thêm: https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard


2
Tuyệt vời, câu trả lời của @ Blacksonic đã hoạt động hoàn hảo với tôi với bộ định tuyến không dùng nữa. Tôi đã phải cấu trúc lại rất nhiều sau khi nâng cấp lên bộ định tuyến mới. Giải pháp của bạn chỉ là những gì tôi cần!
evandongen

Tôi không thể khiến canActivate hoạt động trong app.component của tôi. Tôi đang tìm cách chuyển hướng người dùng nếu không được xác thực. Đây là phiên bản của bộ định tuyến mà tôi có (Nếu tôi cần cập nhật nó, làm cách nào để thực hiện việc đó bằng dòng lệnh git bash?) Phiên bản tôi có: "@
angle

tôi có thể sử dụng cùng một lớp (AuthGuard) để bảo vệ tuyến thành phần khác không?
tsiro

4

Sau những câu trả lời tuyệt vời ở trên, tôi cũng muốn CanActivateChild: bảo vệ các tuyến đường dành cho trẻ em. Nó có thể được sử dụng để thêmguard vào các tuyến đường trẻ em hữu ích cho các trường hợp như ACL

Nó diễn ra như thế này

src / app / auth-Guard.service.ts (phần trích dẫn)

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 (phần trích dẫn)

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

Nội dung này được lấy từ https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard


2

Tham khảo mã này, tệp 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;      
    }
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.