Pengalihan sudut ke halaman login

122

Saya berasal dari dunia MVC Asp.Net di mana pengguna mencoba mengakses halaman yang tidak mereka otorisasi secara otomatis diarahkan ke halaman login.

Saya mencoba mereproduksi perilaku ini di Angular. Saya menemukan dekorator @CanActivate, tetapi menghasilkan komponen yang tidak merender sama sekali, tidak ada pengalihan.

Pertanyaan saya adalah sebagai berikut:

  • Apakah Angular menyediakan cara untuk mencapai perilaku ini?
  • Jika ya, bagaimana caranya? Apakah ini praktik yang baik?
  • Jika tidak, apa praktik terbaik untuk menangani otorisasi pengguna di Angular?
Amaury
sumber
Saya menambahkan arahan aktual yang menunjukkan cara melakukan hal-hal auth, jika Anda mau melihatnya.
Michael Oryl
Jawaban ini mungkin berguna: stackoverflow.com/a/59008239/7059557
AmirReza-Farahlagha

Jawaban:

86

Pembaruan: Saya telah menerbitkan proyek kerangka penuh Angular 2 dengan integrasi OAuth2 di Github yang menunjukkan arahan yang disebutkan di bawah ini dalam tindakan.

Salah satu cara untuk melakukannya adalah melalui penggunaan a directive. Tidak seperti Angular 2 components, yang pada dasarnya adalah tag HTML baru (dengan kode terkait) yang Anda sisipkan ke halaman Anda, direktif atributif adalah atribut yang Anda masukkan ke dalam tag yang menyebabkan beberapa perilaku terjadi. Dokumen di sini .

Kehadiran atribut khusus Anda menyebabkan hal-hal terjadi pada komponen (atau elemen HTML) yang Anda tempatkan arahannya. Pertimbangkan arahan yang saya gunakan untuk aplikasi Angular2 / OAuth2 saya saat ini:

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

Ini memanfaatkan layanan Otentikasi yang saya tulis untuk menentukan apakah pengguna sudah masuk atau tidak dan juga berlangganan ke acara otentikasi sehingga dapat menendang pengguna keluar jika dia logout atau waktu keluar.

Anda bisa melakukan hal yang sama. Anda akan membuat arahan seperti milik saya yang memeriksa keberadaan cookie yang diperlukan atau informasi status lainnya yang menunjukkan bahwa pengguna diautentikasi. Jika mereka tidak memiliki flag yang Anda cari, arahkan pengguna ke halaman publik utama Anda (seperti yang saya lakukan) atau server OAuth2 Anda (atau apa pun). Anda akan meletakkan atribut direktif itu pada setiap komponen yang perlu dilindungi. Dalam hal ini, mungkin disebut protectedseperti dalam petunjuk yang saya tempel di atas.

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

Kemudian Anda ingin menavigasi / mengarahkan pengguna ke tampilan login dalam aplikasi Anda, dan menangani otentikasi di sana. Anda harus mengubah rute saat ini ke yang Anda inginkan. Jadi dalam hal ini Anda akan menggunakan injeksi ketergantungan untuk mendapatkan objek Router dalam constructor()fungsi direktif Anda dan kemudian menggunakan navigate()metode untuk mengirim pengguna ke halaman login Anda (seperti dalam contoh saya di atas).

Ini mengasumsikan bahwa Anda memiliki serangkaian rute di suatu tempat yang mengontrol <router-outlet>tag yang terlihat seperti ini, mungkin:

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

Jika, sebaliknya, Anda perlu mengarahkan pengguna ke URL eksternal , seperti server OAuth2 Anda, maka Anda akan meminta arahan Anda melakukan sesuatu seperti berikut:

window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope
Michael Oryl
sumber
4
Berhasil! Terima kasih! Saya juga menemukan metode lain di sini - github.com/auth0/angular2-authentication-sample/blob/master/src/… Saya tidak bisa mengatakan metode mana yang lebih baik, tetapi mungkin seseorang akan merasakan manfaatnya juga.
Sergey
3
Terima kasih ! Saya juga menambahkan rute baru yang berisi parameter / protected /: returnUrl, returnUrl menjadi location.path () yang dicegat di ngOnInit dari direktif. Ini memungkinkan untuk menavigasi pengguna setelah login ke url yang awalnya diminta.
Amaury
1
Lihat jawaban di bawah untuk solusi sederhana. Apa pun yang umum ini (alihkan jika tidak diautentikasi) harus memiliki solusi sederhana dengan jawaban kalimat tunggal.
Rick O'Shea
7
Catatan: Jawaban ini membahas versi beta atau kandidat rilis dari Angular 2 dan tidak lagi berlaku untuk final Angular 2.
jbandi
1
Ada solusi yang jauh lebih baik untuk ini sekarang menggunakan Penjaga Angular
mwilson
116

Berikut adalah contoh yang diperbarui menggunakan Angular 4 (juga kompatibel dengan Angular 5 - 8)

Rute dengan rute asal dilindungi oleh 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 mengalihkan ke halaman login jika pengguna tidak login

Diperbarui untuk meneruskan url asli dalam parameter kueri ke halaman login

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

Untuk contoh lengkap dan demo kerja Anda dapat melihat posting ini

Jason
sumber
6
Saya memiliki T tindak lanjut, bukankah jika menetapkan nilai sewenang-wenang currentUserdi dalam localStoragemasih dapat mengakses rute yang dilindungi? misalnya. localStorage.setItem('currentUser', 'dddddd')?
jsd
2
Ini akan melewati keamanan sisi klien. Tetapi itu juga akan menghapus token yang akan diperlukan untuk transaksi sisi server, jadi tidak ada data berguna yang dapat diekstraksi dari aplikasi.
Matt Meng
55

Penggunaan dengan router terakhir

Dengan diperkenalkannya router baru, menjadi lebih mudah untuk menjaga rute. Anda harus menentukan penjaga, yang bertindak sebagai layanan, dan menambahkannya ke rute.

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

Sekarang teruskan LoggedInGuardke rute dan tambahkan juga ke providerslarik modul.

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

Deklarasi modul:

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

Posting blog mendetail tentang cara kerjanya dengan rilis final: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Penggunaan dengan router yang tidak digunakan lagi

Solusi yang lebih kuat adalah memperpanjang RouterOutletdan saat mengaktifkan rute, periksa apakah pengguna login. Dengan cara ini Anda tidak perlu menyalin dan menempelkan perintah Anda ke setiap komponen. Selain itu, pengalihan berdasarkan subkomponen bisa menyesatkan.

@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 UserServicesingkatan tempat di mana logika bisnis Anda berada apakah pengguna masuk log atau tidak. Anda dapat menambahkannya dengan mudah dengan DI di konstruktor.

Saat pengguna menavigasi ke url baru di situs web Anda, metode pengaktifan dipanggil dengan Instruksi saat ini. Dari situ Anda dapat mengambil url dan memutuskan apakah diizinkan atau tidak. Jika tidak langsung saja ke halaman login.

Satu hal terakhir yang tersisa untuk membuatnya berfungsi, adalah meneruskannya ke komponen utama kami, bukan ke komponen bawaan.

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

Solusi ini tidak dapat digunakan dengan @CanActivedekorator siklus hidup, karena jika fungsi yang diteruskan kepadanya menyelesaikan kesalahan, metode pengaktifan RouterOutlettidak akan dipanggil.

Juga menulis posting blog rinci tentang itu: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

Blacksonic
sumber
2
Juga menulis posting blog yang lebih rinci tentang itu medium.com/@blacksonic86/…
Blacksonic
Halo @BlackMy. Baru mulai menggali ng2. Saya mengikuti saran Anda tetapi akhirnya mendapatkan kesalahan ini selama 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". Tidak dapat menemukan apa yang harus diubah di konstruktor ("_parentRounter") untuk menyingkirkan pesan ini. Ada pemikiran?
leovrf
deklarasi tersebut disalin dari objek yang dibangun di RouterOutlet yang mendasari untuk memiliki tanda tangan yang sama sebagai kelas diperpanjang, saya akan menonaktifkan aturan tslint khusus untuk baris ini
Blacksonic
Saya menemukan referensi tentang mgechev style-guide (cari "Lebih suka masukan daripada @Attribute parameter decorator"). Mengubah baris menjadi _parentRouter: Router, @Input() nameAttr: string,dan tslint tidak lagi menimbulkan kesalahan. Juga menggantikan impor "Atribut" menjadi "Input" dari inti sudut. Semoga ini membantu.
leovrf
1
Ada masalah dengan 2.0.0-rc.1 karena RouterOutlet tidak diekspor dan tidak ada kemungkinan untuk memperpanjangnya
mkuligowski
53

Tolong, jangan menimpa Outlet Router! Ini mimpi buruk dengan rilis router terbaru (3.0 beta).

Sebagai gantinya gunakan antarmuka CanActivate dan CanDeactivate dan setel kelas sebagai canActivate / canDeactivate dalam definisi rute Anda.

Seperti itu:

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

Kelas:

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

Lihat juga: https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

Nilz11
sumber
2
Bagus, jawaban @ Blacksonic berfungsi sempurna untuk saya dengan router yang sudah usang. Saya harus banyak melakukan refaktorisasi setelah memutakhirkan ke router baru. Solusi Anda adalah yang saya butuhkan!
evandongen
Saya tidak bisa mengaktifkan canActivate untuk bekerja di app.component saya. Saya ingin mengalihkan pengguna jika tidak diautentikasi. Ini adalah versi router yang saya miliki (Jika saya perlu memperbaruinya, bagaimana cara melakukannya menggunakan baris perintah git bash?) Versi yang saya miliki: "@ angular / router": "2.0.0-rc.1"
AngularM
dapatkah saya menggunakan kelas yang sama (AuthGuard) untuk melindungi rute komponen lain?
tsiro
4

Mengikuti jawaban luar biasa di atas, saya juga ingin CanActivateChild: menjaga rute anak. Dapat digunakan untuk menambahkan guardke rute turunan yang berguna untuk kasus seperti ACL

Ini berjalan seperti ini

src / app / auth-guard.service.ts (kutipan)

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 (kutipan)

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

Ini diambil dari https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

Thabung
sumber
2

Lihat kode ini, file 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]    },

]; 
sojan
sumber
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;      
    }
  }
}
M. Laida
sumber