在Angular中使用JWT認(rèn)證方法示例
本文介紹了在Angular中使用JWT認(rèn)證方法示例,分享給大家,具體如下:

項(xiàng)目地址: grading-system
基于session的認(rèn)證和基于token的認(rèn)證的方式已經(jīng)被廣泛使用。在session認(rèn)證中,服務(wù)端會(huì)存儲(chǔ)一份用戶登錄信息,這份登錄信息會(huì)在響應(yīng)時(shí)傳遞給瀏覽器并保存為Cookie,在下次請(qǐng)求時(shí),會(huì)帶上這份登錄信息,這樣就能識(shí)別請(qǐng)求來(lái)自哪個(gè)用戶。
在基于session的認(rèn)證中,每個(gè)用戶都要生成一份session,這份session通常保存在內(nèi)存中,隨著用戶量的增加,服務(wù)端的開(kāi)銷會(huì)增大,而且對(duì)分布式應(yīng)用不是很友好。
在token認(rèn)證中,服務(wù)端不需要保留用戶認(rèn)證信息。當(dāng)用戶登錄時(shí),服務(wù)器驗(yàn)證用戶信息后會(huì)返回一個(gè)token,這個(gè)token存儲(chǔ)在客戶端,并且在每次請(qǐng)求的請(qǐng)求頭中都帶上這個(gè)token,這樣服務(wù)端驗(yàn)證token后就可以返回?cái)?shù)據(jù)。
JWT(JSON Web Token)是一個(gè)開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且獨(dú)立的方式,可以在各方之間作為JSON對(duì)象安全地傳輸信息。 此信息可以通過(guò)數(shù)字簽名進(jìn)行驗(yàn)證和信任。特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景。
JWT 是什么,為何要使用 JWT?
JWT 是 JSON Web Tokens 的簡(jiǎn)稱,對(duì)于這個(gè)問(wèn)題最精簡(jiǎn)的回答是,JWT 具有簡(jiǎn)便、緊湊、安全的特點(diǎn),具體來(lái)看:
簡(jiǎn)便:只要用戶登陸后,使用 JWT 認(rèn)證僅需要添加一個(gè) http header 認(rèn)證信息,這可以用一個(gè)函數(shù)簡(jiǎn)單實(shí)現(xiàn),我們會(huì)在后面的例子中看到這一點(diǎn)。
緊湊:JWT token 是一個(gè) base 64 編碼的字符串,包含若干頭部信息及一些必要的數(shù)據(jù),非常簡(jiǎn)單。簽名后的 JWT 字符串通常不超過(guò) 200 字節(jié)。
安全:JWT 可以使用 RSA 或 HMAC 加密算法進(jìn)行加密,確保 token 有效且防止篡改。
總之你可以有一種安全有效的方式來(lái)認(rèn)證用戶,并且對(duì)所有 api 調(diào)用都進(jìn)行認(rèn)證,而不需要解析復(fù)雜的數(shù)據(jù)結(jié)構(gòu)或者實(shí)現(xiàn)自己的加密算法。
JWT的構(gòu)成
JWT由 . 分隔的三個(gè)部分組成,它們是:
- 頭部(Header)
- 荷載(Playload)
- 簽名(Signature)
也就是說(shuō),JWT只是一個(gè)具有以下格式的字符串:
header.payload.signature
頭部
頭部通常由兩部分組成:令牌的類型(即JWT)以及正在使用的散列算法,例如HMAC SHA256或RSA。
header.payload.signature
然后,對(duì)這個(gè)JSON進(jìn)行Base64編碼,形成JWT的第一部分。
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9
荷載
JWT的第二部分是荷載,其中包含聲明。 聲明是關(guān)于實(shí)體(通常是用戶)和其他數(shù)據(jù)的聲明。聲明有三種:注冊(cè)的聲明、公開(kāi)的聲明和私有的聲明。
JWT規(guī)范定義了七個(gè)在標(biāo)準(zhǔn)中注冊(cè)的聲明名稱,它們是:
- iss: JWT簽發(fā)者
- sub: JWT所面向的用戶
- aud:接收J(rèn)WT的一方
- exp:JWT的過(guò)期時(shí)間,這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間
- nbf:定義在什么時(shí)間之前,該JWT都是不可用的.
- iat: JWT的簽發(fā)時(shí)間
- jti: JWT的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性token,從而回避重放攻擊。
對(duì)于特定情況,可以使用公共的聲明名稱。 這些包括:
- auth_time:身份驗(yàn)證發(fā)生的時(shí)間
- acr:認(rèn)證上下文類的引用
- nonce:用于將客戶端會(huì)話與ID Token關(guān)聯(lián)的值
最后,還有私有的聲明名稱,可以使用它們來(lái)傳達(dá)與身份相關(guān)的信息,例如姓名或部門。
由于公共和私人的聲明未注冊(cè),請(qǐng)注意避免名稱沖突。
比如,我們定義一個(gè)palyload:
{
"sub": "1234567890",
"name": "tc9011",
"admin": true,
"exp": 1441594722
}
然后將其進(jìn)行base64加密,得到JWT的第二部分:
ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAidGM5MDExIiwKICAiYWRtaW4iOiB0cnVlLAogICJleHAiOiAxNDQxNTk0NzIyCn0=
簽名
簽名由base64編碼后的頭、base64編碼后的荷載和secret組成。
例如,將上面的兩個(gè)編碼后的字符串都用句號(hào) . 連接在一起(頭部在前),就形成了:
ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAidGM5MDExIiwKICAiYWRtaW4iOiB0cnVlLAogICJleHAiOiAxNDQxNTk0NzIyCn0=
然后,將上面拼接完的字符串用secret作為秘鑰進(jìn)行HS256加密。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
使用JWT
一般在會(huì)在請(qǐng)求頭中加入 Authorization ,并加上 Bearer 進(jìn)行標(biāo)注:

fetch('api/v1/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服務(wù)端會(huì)驗(yàn)證token,如果驗(yàn)證通過(guò)就會(huì)返回相應(yīng)的資源。
不過(guò)要注意,因?yàn)楹奢d是base64編碼,這種編碼可以對(duì)稱解密,所以在荷載中不應(yīng)該存放用戶的敏感信息,比如密碼。所以一般JWT用來(lái)向Web傳遞一些非敏感信息,例如用戶名、所屬部門等。
在Angular中使用JWT
這里我們以Angular6和koa2(使用TypeScript)為例,介紹一下如何在你的Angular應(yīng)用中使用JWT。
服務(wù)端
首先在jwt.io 官網(wǎng)上找到node的JWT的庫(kù): jsonwebtoken 。

可以看到官網(wǎng)把這個(gè)庫(kù)對(duì)標(biāo)準(zhǔn)注冊(cè)聲明字段的支持情況以及加密方式的支持情況都列出來(lái)了。除了這個(gè)庫(kù),還需要使用koa一個(gè)中間件: koa-jwt ,用來(lái)對(duì)HTTP請(qǐng)求進(jìn)行JWT認(rèn)證。你可以通過(guò)下面命令安裝這兩個(gè)庫(kù):
npm i koa-jwt jsonwebtoken --save
在 app.ts 中:
import * as jwt from 'koa-jwt';
app.use(jwt({
secret: Secret
}).unless({
path: [/\/register/, /\/login/, /\/groups/],
}));
這里的secret就是你自己定義的秘鑰, unless 方法用來(lái)排除一些不需要進(jìn)行JWT認(rèn)證的api。koa-jwt中間件需要放在路由中間件之前,這樣就可以對(duì)所有路由(除了 unless 中設(shè)置的路由外)進(jìn)行JWT的檢查。只有正確之后才能正確的訪問(wèn)。
除此之外,你還要自定義一個(gè)401錯(cuò)誤處理的中間件,如果沒(méi)有token,或者token失效,該中間件會(huì)給出對(duì)應(yīng)的錯(cuò)誤信息。如果沒(méi)有自定義中間件的話,會(huì)直接將 koa-jwt 暴露的錯(cuò)誤信息直接返回給用戶。
export const errorHandle = (ctx, next) => {
return next().catch((err) => {
if (err.status === 401) {
ctx.status = 401;
handleError({ctx, message: '登錄過(guò)期,請(qǐng)重新登錄', err: err.originalError ? err.originalError.message : err.message});
} else {
throw err;
}
});
};
然后把這個(gè)中間件放在koa-jwt之前:
app.use(errorHandle);
app.use(jwt({
secret: Secret
}).unless({
path: [/\/register/, /\/login/, /\/groups/],
}));
在用戶登陸時(shí)候,生成token,返回給客戶端:
// 生成 token 返回給客戶端
const token = jsonwebtoken.sign({
user: {
workNumber: user.workNumber,
realName: user.realName,
group: user.group,
role: user.role
},
// 設(shè)置 token 過(guò)期時(shí)間
exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24), // 1天
}, Secret);
handleSuccess({
ctx,
message: '登陸成功!',
response: {
token,
lifeTime: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // 1天
}
});
需要注意的是,在使用 jsonwebtoken.sign() 時(shí),需要傳入的 secret 參數(shù),這里的 secret 必須要與 前面設(shè)置 jwt() 中的 secret 一致。
客戶端
在Angular中,我們需要使用 @auth0/angular2-jwt 這個(gè)庫(kù)來(lái)幫助我們?cè)贏ngular中處理JWT:
npm install @auth0/angular-jwt --save
在 app.module.ts 中引入 JwtModule 這個(gè)模塊(注意,引入該模塊的同時(shí)也要引入 HttpClientModule 模塊):
import { JwtModule } from '@auth0/angular-jwt';
import { HttpClientModule } from '@angular/common/http';
export function tokenGetter(){
return localStorage.getItem('token');
}
@NgModule({
bootstrap: [AppComponent],
imports: [
// ...
HttpClientModule,
JwtModule.forRoot({
config: {
tokenGetter: tokenGetter,
whitelistedDomains: ['localhost:3001'],
blacklistedRoutes: ['localhost:3001/auth/']
}
})
]
})
export class AppModule {}
在 JwtModule 的 config 中:
tokenGetter :從localStorage中獲取token;
whitelistedDomains :允許發(fā)送認(rèn)證的請(qǐng)求的域名;
blacklistedRoutes :你不希望替換header中 Authorization 信息的api列表。
接著創(chuàng)建一個(gè)全局的 auth.service.ts 服務(wù),方便在登陸的時(shí)候獲取用戶相關(guān)信息及權(quán)限,這個(gè)服務(wù)中有個(gè) login 方法,用來(lái)處理登陸后返回的token信息,并把token存到LocalStorage中,這樣在token失效前,下次用戶登陸時(shí)就不需要輸入用戶名和密碼:
login(loginInfo: LoginInfo): Observable<boolean> {
return this.passportService.postLogin(loginInfo).pipe(map(
(res: LoginRes) => {
// 登陸成功后獲取token,并存到localStorage
this.storageService.setLocalStorage('token', res.token);
const decodedUser = this.decodeUserFromToken(res.token);
this.setCurrentUser(decodedUser);
this.msg.success('登錄成功!');
return this.loggedIn;
}
)
);
}
在這個(gè) login 方法中, decodeUserFromToken 封裝了 @auth0/angular2-jwt 中提供的 decodeToken 方法,注意 decodeToken 方法解析出來(lái)的只是服務(wù)端 jsonwebtoken.sign() 中的JSON對(duì)象,所以需要通過(guò) . 操作獲取 jsonwebtoken.sign() 中定義的 user :
decodeUserFromToken(token): User {
return this.jwtHelperService.decodeToken(token).user;
}
在這個(gè)服務(wù)中,定義了兩個(gè)變量 loggedIn 和 isAdmin ,用來(lái)標(biāo)識(shí)用戶是否登錄和其相應(yīng)的權(quán)限,方便在Angular路由中控制可以訪問(wèn)的視圖。
有登錄當(dāng)然就有登出,登出時(shí)只需把token從LocalStorage中移除,并把幾個(gè)變量重置即可:
logout(): void {
this.storageService.removeLocalStorage('token');
this.loggedIn = false;
this.isAdmin = false;
this.currentUser = new User();
}
AuthService 的完整代碼如下:
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { LoginInfo, LoginRes, User } from '../../views/passport/interfaces/passport';
import { PassportService } from '../../views/passport/services/passport.service';
import { StorageService } from '../storage/storage.service';
import { NzMessageService } from 'ng-zorro-antd';
@Injectable()
export class AuthService {
public loggedIn = false;
public isAdmin = false;
public currentUser: User = new User();
constructor(private jwtHelperService: JwtHelperService,
private router: Router,
private injector: Injector,
private passportService: PassportService,
private storageService: StorageService) {
const token = localStorage.getItem('token');
if (token) {
const decodedUser = this.decodeUserFromToken(token);
this.setCurrentUser(decodedUser);
}
}
get msg(): NzMessageService {
return this.injector.get(NzMessageService);
}
login(loginInfo: LoginInfo): Observable<boolean> {
return this.passportService.postLogin(loginInfo).pipe(map(
(res: LoginRes) => {
this.storageService.setLocalStorage('token', res.token);
const decodedUser = this.decodeUserFromToken(res.token);
this.setCurrentUser(decodedUser);
this.msg.success('登錄成功!');
return this.loggedIn;
}
)
);
}
logout(): void {
this.storageService.removeLocalStorage('token');
this.loggedIn = false;
this.isAdmin = false;
this.currentUser = new User();
}
decodeUserFromToken(token): User {
return this.jwtHelperService.decodeToken(token).user;
}
setCurrentUser(decodedUser): void {
this.loggedIn = true;
this.currentUser.workNumber = decodedUser.workNumber;
this.currentUser.realName = decodedUser.realName;
this.currentUser.group = decodedUser.group;
this.currentUser.role = decodedUser.role;
this.isAdmin = decodedUser.role > 10;
delete decodedUser.role;
}
}
至此,在你的Angular應(yīng)用中就引入了JWT認(rèn)證,當(dāng)然,你也可以不使用 @auth0/angular2-jwt ,自己手寫一個(gè)HTTP攔截器,手動(dòng)設(shè)置每次請(qǐng)求的header:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>,
next: HttpHandler): Observable<HttpEvent<any>> {
const token = localStorage.getItem("token");
if (token) {
const cloned = req.clone({
headers: req.headers.set("Authorization",
"Bearer " + token)
});
return next.handle(cloned);
}
else {
return next.handle(req);
}
}
}
不過(guò)這樣的話,token Base64解碼也需要自己手寫,稍微麻煩一點(diǎn)。
總結(jié)
JWT因?yàn)槭腔贘SON的,所以通用性很強(qiáng),很多語(yǔ)言已經(jīng)存在jwt相關(guān)的庫(kù)。不過(guò)使用JWT的時(shí)候需要注意以下幾點(diǎn):
- 保存好secret秘鑰,這個(gè)秘鑰只能在服務(wù)端存在
- 給token設(shè)置一個(gè)過(guò)期時(shí)間,因?yàn)橐坏﹖oken生成,它就永遠(yuǎn)有效,除非token密鑰被更改或過(guò)期
- 在payload中只能存儲(chǔ)一些業(yè)務(wù)邏輯所必要的非敏感信息
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- 利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼
- ASP.NET Core使用JWT認(rèn)證授權(quán)的方法
- 利用go-zero在Go中快速實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- ASP.NET Core學(xué)習(xí)之使用JWT認(rèn)證授權(quán)詳解
- 解析SpringSecurity+JWT認(rèn)證流程實(shí)現(xiàn)
- 詳解Django配置JWT認(rèn)證方式
- ASP.Net Core3.0中使用JWT認(rèn)證的實(shí)現(xiàn)
- Asp.Net Core基于JWT認(rèn)證的數(shù)據(jù)接口網(wǎng)關(guān)實(shí)例代碼
- php 后端實(shí)現(xiàn)JWT認(rèn)證方法示例
- swagger上傳文件并支持jwt認(rèn)證的實(shí)現(xiàn)方法
- Springboot WebFlux集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的示例
相關(guān)文章
AngularJS select加載數(shù)據(jù)選中默認(rèn)值的方法
下面小編就為大家分享一篇AngularJS select加載數(shù)據(jù)選中默認(rèn)值的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
AngularJS 實(shí)現(xiàn)彈性盒子布局的方法
本文給大家?guī)?lái)一段簡(jiǎn)短代碼實(shí)現(xiàn)angularjs彈性布局效果,非常實(shí)用,對(duì)angularjs彈出布局知識(shí)感興趣的朋友可以參考下2016-08-08
AngularJS的ng-repeat指令與scope繼承關(guān)系實(shí)例詳解
這篇文章主要介紹了AngularJS的ng-repeat指令與scope繼承關(guān)系,結(jié)合實(shí)例形式通過(guò)ng-repeat指令詳細(xì)分析了scope繼承關(guān)系,需要的朋友可以參考下2017-01-01
在AngularJs中設(shè)置請(qǐng)求頭信息(headers)的方法及不同方法的比較
在AngularJs中有三種方式可以設(shè)置請(qǐng)求頭信息,文中對(duì)每種方法給大家介紹的非常詳細(xì),選擇那種方式可以根據(jù)自己的需求,感興趣的朋友跟隨腳本之家小編一起看看吧2018-09-09
angularjs 表單密碼驗(yàn)證自定義指令實(shí)現(xiàn)代碼
這篇文章主要介紹了angularjs 表單密碼驗(yàn)證自定義指令實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-10-10

