6-8.Angular 入門教學 - JWT認證登入並使用HTTP_INTERCEPTORS加上Authorization標頭

Angular 入門教學


這一篇對新手來說可能會有點難,但沒辦法,身分驗證的東西簡單不起來

我這邊已經把過程相對簡化了,留下的語法算是必要,且用很單純的寫法去寫,希望大家能吸收的進去XD

這邊預設的登入帳號為todo密碼為todo

首先我這邊先做了一個登入用的login.model.ts

export interface LoginPost {
    AccountName: string;
    Password: string;
}

接著製作登入的service,login.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { LoginPost } from '../@models/login.model';

@Injectable({
  providedIn: 'root'
})
export class LoginService {
  private url = '/api/login6_8';

  constructor(private http: HttpClient) { }

  JWT登入(value: LoginPost) {
    return this.http.post(this.url + '/jwtLogin', value);
  }
}

我這支api有套上固定格式Data、Status、Message

{
  "Data": "No",
  "Status": 0,
  "Message": "帳號或密碼錯誤"
}

這是我個人的習慣,方便我前端可以輕鬆掌握狀況,等等也會說明

那我這個login的api,當密碼錯誤data會出no,然後status會是0,message會顯示訊息

當正確的時候就會data吐出jwt,然後status是1如下

{
  "Data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRvZG8iLCJGdWxsTmFtZSI6InRvZG8iLCJuYW1laWQiOiIxNzFmNzE4Yi02ZmY2LTQ1NmEtYjM0My0wYzkyNDhkNmViM2EiLCJleHAiOjE2NjcyODM3MTgsImlzcyI6InRvZG8udGFsbGxrYWkuY29tIiwiYXVkIjoia2FpIn0.an7_MiIolU6oauIJ088KDepxsxDyBTd6bZfOzmaIlss",
  "Status": 1,
  "Message": null
}

因此我們的login.component.ts可以先這樣寫

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoginPost } from '../@models/login.model';
import { LoginService } from '../@services/login.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
  loginValue: LoginPost = {
    AccountName: '',
    Password: ''
  }

  constructor(private router: Router, private loginService: LoginService) { }

  ngOnInit(): void {
  }

  login() {
    this.loginService.JWT登入(this.loginValue).subscribe((data: any) => {
      if (data.Status === 1) {
        localStorage.setItem('jwt', data.Data);
        this.router.navigateByUrl('/manage/home');
      }else{
        alert(data.Message)
      
}
    });
  }

}

login.component.html

<div class="mzone">
  <div class="titleZone">
    TODO系統
  </div>
  <div>帳號:<input type="text" [(ngModel)]="loginValue.AccountName" class="form-control" placeholder="帳號"></div>
  <div>密碼:<input type="text" [(ngModel)]="loginValue.Password" class="form-control" placeholder="密碼"></div>
  <button class="btn btn-success text-white mm" (click)="login()" type="submit">
    登入
  </button>
</div>

這邊就是如果data.Status === 1代表登入通過,就記下jwt內容,然後轉跳至內頁

如果不是則跳出錯誤訊息

接著我這邊寫了一支todo6_8的api,這支是需要登入後才能使用,也就是說要傳給他正確的jwt才能夠使用

所以我們可以將TodoApiService上面的網址改成private url = '/api/todo6_8';

這時候我們的todo就沒辦法正常讀,因為缺少jwt的Authorization標頭,我內容會回應下圖訊息

我們必須在每次送出要求api時,加上jwt的Authorization標頭就能夠順利讀取

所以我們在修改todo-api.service.ts,在裡面的要求都加上標頭嗎?可以這樣做,但不建議,因為有更好的辦法

就是使用Angular提供的Http攔截器HTTP_INTERCEPTORS,這是在做什麼用的呢?

顧名思義,這個可以在你發出http要求的時候,中途統一做一些事

那我們這邊要做什麼事呢?就是在每個http要求中,都加上jwt的Authorization標頭

如此就不用在每個HttpClient都自己一個一個加上標頭了

那我們這邊先建立一個interceptor.service.ts

import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {

  constructor(private injector: Injector, private router: Router) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const jwt = localStorage.getItem('jwt');

    if (jwt) {
      req = req.clone({
        headers: req.headers.set('Authorization', 'bearer ' + jwt)
      });
    }

    return next.handle(req).pipe(
      map(event => {
        if (event instanceof HttpResponse) {
          switch (event.body.Status) {
            case 1: {
              event = this.success(event);
              break;
            }
            case 0: {
              event = this.error(event);
              break;
            }
            case -1: {
              event = this.error(event);
              this.router.navigate(['/login']);
              break;
            }
          }
        }
        return event;
      })
    );
  }

  private success(event: any): any {
    if (event.body.Data) {
      return event.clone({ body: event.body.Data });
    } else {
      return event.clone({ body: true });
    }

  }

  private error(event: any): any {
    alert(event.body.Message);

    return event.clone({ body: false });
  }

}

上半部的地方

const jwt = localStorage.getItem('jwt');

if (jwt) {
  req = req.clone({
    headers: req.headers.set('Authorization', 'bearer ' + jwt)
  });
}

簡單來說就是先取出我們登入時存的jwt字串,如果有的話,就在這個http要求上加上一個Authorization的jwt標頭,之後就送出

return next.handle(req);

那要求回來的時候,我還有做一些事情,那這邊就是我個人習慣,並非必要,而且要前後端的人講好

f(event instanceof HttpResponse)這邊我先判斷是不是回應的事件後,針對回應的內容做初步篩選

這邊我後端的寫法就是Status=1為正確、0為錯誤、-1為未登入

而當錯誤和未登入的時候我都會帶Message的內容,所以我這邊就會順便印出來,然後將body值改寫成false,留給之後的程式做判斷

如果是1代表都正確,我就將body值改寫成data裡面的值,之後我在元件裡取到的內容就只會剩下data的值,而不會有Status跟Message

因為這兩個值到了元件的時候已經不需要了,我在這個攔截器中已經使用完了

再次重申,這是要前後端的人講好才能這樣寫,那我這邊是我個人習慣這樣處理比較順,並非必要做法

攔截器寫完了,我們要到app.module.ts做註冊

@NgModule({
  declarations: [
    AppComponent,
    NotfoundComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    AppRoutingModule,
    ProcessBarModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: InterceptorService,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

接著我們再回到login.component.ts做一些修改


login() {
  this.loginService.JWT登入(this.loginValue).subscribe((data: any) => {
    if (data) {
      localStorage.setItem('jwt', data);
      this.router.navigateByUrl('/manage/home');
    }
  });
}

這邊的data如果是錯的,已經會在攔截器被我改成false,所以我可以用if來判斷data是否為正確就知道是否登入成功

todo的程式我們完全不用做更動,但我們此時已經做好了基本的驗證功能了

我們可以試一試程式是否可以正常讀取到資料了

當然這時候就沒問題了

那登出的部分我們就在點登出的時候把localStorage.removeItem('jwt')清掉就可以了

 

範例檔:下載




Copyright © 凱哥寫程式 2022 | Powered by TalllKai ❤