Khởi tạo dự án ban đầu
Đầu tiên, cần cài đặt node và npm – vì Angular CLI là một npm tool.
Tiếp theo, khởi tạo angular project
ng new oauthApp
Sample này chúng ta sẽ làm cho 2 module: password flow và implicit flow
Password Flow
Trường hợp sử dụng của chúng ta rất đơn giản, sau khi người dùng cung cấp thông tin đăng nhập của họ, máy khách front-end sẽ sử dụng chúng để lấy Mã truy cập (Access Token) từ Máy chủ ủy quyền.
#App Service
Bắt đầu với AppService- đặt tại app.service.ts
– chứa logic cho các tương tác máy chủ:
- getAccessToken(): để lấy Mã truy cập được cung cấp thông tin xác thực của người dùng.
- saveToken(): để lưu mã thông báo truy cập của chúng tôi trong cookie bằng cách sử dụng thư viện ng2-cookie.
- getResource(): để lấy một đối tượng Foo từ máy chủ bằng cách sử dụng ID của nó.
- checkCredentials(): để kiểm tra xem người dùng đã đăng nhập hay chưa.
- logout(): để xóa cookie mã thông báo truy cập và đăng xuất người dùng.
export class Foo { constructor( public id: number, public name: string) { } } @Injectable() export class AppService { constructor( private _router: Router, private _http: Http){} obtainAccessToken(loginData){ let params = new URLSearchParams(); params.append('username',loginData.username); params.append('password',loginData.password); params.append('grant_type','password'); params.append('client_id','fooClientIdPassword'); let headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Basic '+btoa("fooClientIdPassword:secret")}); let options = new RequestOptions({ headers: headers }); this._http.post('http://localhost:8081/spring-security-oauth-server/oauth/token', params.toString(), options) .map(res => res.json()) .subscribe( data => this.saveToken(data), err => alert('Invalid Credentials')); } saveToken(token){ var expireDate = new Date().getTime() + (1000 * token.expires_in); Cookie.set("access_token", token.access_token, expireDate); this._router.navigate(['/']); } getResource(resourceUrl) : Observable<Foo>{ var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Bearer '+Cookie.get('access_token')}); var options = new RequestOptions({ headers: headers }); return this._http.get(resourceUrl, options) .map((res:Response) => res.json()) .catch((error:any) => Observable.throw(error.json().error || 'Server error')); } checkCredentials(){ if (!Cookie.check('access_token')){ this._router.navigate(['/login']); } } logout() { Cookie.delete('access_token'); this._router.navigate(['/login']); } }
Lưu ý rằng:
- Để nhận được Access Token, chúng ta gửi một POST tới endpoint “/oauth/token”
- Chúng ta đamg sử dụng Client credentials (thông tin đăng nhập khách hàng) và Basic Auth để đạt được endpoint này.
- Sau đó, chúng ta sẽ gửi User credentials cùng với Client Id và Grant type parameters URL được mã hóa.
- Sau khi chúng ta có được Access Token – chúng ta lưu trữ nó vào trong cookie
- Việc lưu trữ cookie đặc biệt quan trọng ở đây, vì chúng ta chỉ sử dụng cookie cho mục đích lưu trữ và không trực tiếp thúc đẩy quá trình xác thực. Điều này giúp bảo vệ chống lại kiểu tấn công và lỗ hổng bảo mật theo yêu cầu chéo trang web (CSRF).
Login Component
Tiếp theo, LoginComponent chịu trách nhiệm về biểu mẫu đăng nhập:
@Component({ selector: 'login-form', providers: [AppService], template: `<h1>Login</h1> <input type="text" [(ngModel)]="loginData.username" /> <input type="password" [(ngModel)]="loginData.password"/> <button (click)="login()" type="submit">Login</button>` }) export class LoginComponent { public loginData = {username: "", password: ""}; constructor(private _service:AppService) {} login() { this._service.obtainAccessToken(this.loginData); }
Home Component
Tiếp theo, HomeComponent chịu trách nhiệm hiển thị và điều khiển Trang chủ
@Component({ selector: 'home-header', providers: [AppService], template: `<span>Welcome !!</span> <a (click)="logout()" href="#">Logout</a> <foo-details></foo-details>` }) export class HomeComponent { constructor( private _service:AppService){} ngOnInit(){ this._service.checkCredentials(); } logout() { this._service.logout(); } }
Foo Component
Cuối cùng, FooComponent- hiển thị chi tiết Foo
@Component({ selector: 'foo-details', providers: [AppService], template: `<h1>Foo Details</h1> <label>ID</label> <span>{{foo.id}}</span> <label>Name</label> <span>{{foo.name}}</span> <button (click)="getFoo()" type="submit">New Foo</button>` }) export class FooComponent { public foo = new Foo(1,'sample foo'); private foosUrl = 'http://localhost:8082/spring-security-oauth-resource/foos/'; constructor(private _service:AppService) {} getFoo(){ this._service.getResource(this.foosUrl+this.foo.id) .subscribe( data => this.foo = data, error => this.foo.name = 'Error'); } }
App Component
AppComponent hoạt động như một root component
@Component({ selector: 'app-root', template: `<router-outlet></router-outlet>` }) export class AppComponent {}
AppModule
AppModule nơi bao gồm tất cả các components, services và routes
@NgModule({ declarations: [ AppComponent, HomeComponent, LoginComponent, FooComponent ], imports: [ BrowserModule, FormsModule, HttpModule, RouterModule.forRoot([ { path: '', component: HomeComponent }, { path: 'login', component: LoginComponent }]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Implicit Flow
App Service
Tương tự, chúng ta sẽ bắt đầu với dịch vụ của mình, nhưng lần này chúng ta sẽ sử dụng thư viện angle-oauth2-oidc thay vì tự lấy access token
@Injectable() export class AppService { constructor( private _router: Router, private _http: Http, private oauthService: OAuthService){ this.oauthService.loginUrl = 'http://localhost:8081/spring-security-oauth-server/oauth/authorize'; this.oauthService.redirectUri = 'http://localhost:8086/'; this.oauthService.clientId = "sampleClientId"; this.oauthService.scope = "read write foo bar"; this.oauthService.setStorage(sessionStorage); this.oauthService.tryLogin({}); } obtainAccessToken(){ this.oauthService.initImplicitFlow(); } getResource(resourceUrl) : Observable<Foo>{ var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Bearer '+this.oauthService.getAccessToken()}); var options = new RequestOptions({ headers: headers }); return this._http.get(resourceUrl, options) .map((res:Response) => res.json()) .catch((error:any) => Observable.throw(error.json().error || 'Server error')); } isLoggedIn(){ if (this.oauthService.getAccessToken() === null){ return false; } return true; } logout() { this.oauthService.logOut(); location.reload(); } }
Lưu ý rằng sau khi lấy được Access Token, chúng ta sẽ sử dụng nó thông qua Authorization header bất cứ khi nào chúng ta sử dụng tài nguyên được bảo vệ từ bên trong Resource Server.
Home Component
HomeComponent để xử lý Trang chủ
@Component({ selector: 'home-header', providers: [AppService], template: ` <button *ngIf="!isLoggedIn" (click)="login()" type="submit">Login</button> <div *ngIf="isLoggedIn"> <span>Welcome !!</span> <a (click)="logout()" href="#">Logout</a> <br/> <foo-details></foo-details> </div>` }) export class HomeComponent { public isLoggedIn = false; constructor( private _service:AppService){} ngOnInit(){ this.isLoggedIn = this._service.isLoggedIn(); } login() { this._service.obtainAccessToken(); } logout() { this._service.logout(); } }
Foo Component
FooComponent – giống như bên mô-đun password flow.
App Module
Cuối cùng, AppModule
@NgModule({ declarations: [ AppComponent, HomeComponent, FooComponent ], imports: [ BrowserModule, FormsModule, HttpModule, OAuthModule.forRoot(), RouterModule.forRoot([ { path: '', component: HomeComponent }]) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Run Front End
Để chạy app, chúng ta cần điều hướng đến thư mục ứng dụng Angular và
npm start