Angular- Sample về OAuth2 với Implicit Flow

Khởi tạo dự án ban đầu

Đầu tiên, cần cài đặt node 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



Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *