這一篇對新手來說可能會有點難,但沒辦法,身分驗證的東西簡單不起來
我這邊已經把過程相對簡化了,留下的語法算是必要,且用很單純的寫法去寫,希望大家能吸收的進去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')
清掉就可以了
範例檔:下載