在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
,自己手寫(xiě)一個(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解碼也需要自己手寫(xiě),稍微麻煩一點(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-02AngularJS 實(shí)現(xiàn)彈性盒子布局的方法
本文給大家?guī)?lái)一段簡(jiǎn)短代碼實(shí)現(xiàn)angularjs彈性布局效果,非常實(shí)用,對(duì)angularjs彈出布局知識(shí)感興趣的朋友可以參考下2016-08-08AngularJS的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-09angularjs 表單密碼驗(yàn)證自定義指令實(shí)現(xiàn)代碼
這篇文章主要介紹了angularjs 表單密碼驗(yàn)證自定義指令實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-10-10