Token登陸驗(yàn)證機(jī)制的原理及實(shí)現(xiàn)
session簡介
做過Web開發(fā)的程序員應(yīng)該對Session都比較熟悉,Session是一塊保存在服務(wù)器端的內(nèi)存空間,一般用于保存用戶的會(huì)話信息。

用戶通過用戶名和密碼登陸成功之后,服務(wù)器端程序會(huì)在服務(wù)器端開辟一塊Session內(nèi)存空間并將用戶的信息存入這塊空間,同時(shí)服務(wù)器會(huì)在cookie中寫入一個(gè)Session_id的值,這個(gè)值用于標(biāo)識(shí)這個(gè)內(nèi)存空間。
下次用戶再來訪問的話會(huì)帶著這個(gè)cookie中的session_id,服務(wù)器拿著這個(gè)id去尋找對應(yīng)的session,如果session中已經(jīng)有了這個(gè)用戶的登陸信息,則說明用戶已經(jīng)登陸過了。
使用Session保持會(huì)話信息使用起來非常簡單,技術(shù)也非常成熟。但是也存在下面的幾個(gè)問題:
- 服務(wù)器壓力大:通常Session是存儲(chǔ)在內(nèi)存中的,每個(gè)用戶通過認(rèn)證之后都會(huì)將session數(shù)據(jù)保存在服務(wù)器的內(nèi)存中,而當(dāng)用戶量增大時(shí),服務(wù)器的壓力增大。
- Session共享:現(xiàn)在很多應(yīng)用都是分布式集群,需要我們做額外的操作進(jìn)行Session共享;
- CSRF跨站偽造請求攻擊:Session機(jī)制是基于瀏覽器端的cookie的,cookie如果被截獲,用戶就會(huì)很容易受到跨站請求偽造的攻擊。
基于token的認(rèn)證
基于token的認(rèn)證機(jī)制將認(rèn)證信息返回給客戶端并存儲(chǔ)。下次訪問其他頁面,需要從客戶端傳遞認(rèn)證信息回服務(wù)端。簡單的流程如下:
- 客戶端使用用戶名跟密碼請求登錄;
- 服務(wù)端收到請求,去驗(yàn)證用戶名與密碼;
- 驗(yàn)證成功后,服務(wù)端會(huì)簽發(fā)一個(gè) Token,再把這個(gè) Token 發(fā)送給客戶端;
- 客戶端收到 Token 以后可以把它存儲(chǔ)起來,比如放在 Cookie 里或者 Local Storage 里;
- 客戶端每次向服務(wù)端請求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token;
- 服務(wù)端收到請求,然后去驗(yàn)證客戶端請求里面帶著的 Token,如果驗(yàn)證成功,就向客戶端返回請求的數(shù)據(jù);
基于token的驗(yàn)證機(jī)制,有以下的優(yōu)點(diǎn):
- 支持跨域訪問,將token置于請求頭中,而cookie是不支持跨域訪問的;
- 無狀態(tài)化,服務(wù)端無需存儲(chǔ)token,只需要驗(yàn)證token信息是否正確即可,而session需要在服務(wù)端存儲(chǔ),一般是通過cookie中的sessionID在服務(wù)端查找對應(yīng)的session;
- 無需綁定到一個(gè)特殊的身份驗(yàn)證方案(傳統(tǒng)的用戶名密碼登陸),只需要生成的token是符合我們預(yù)期設(shè)定的即可;
- 更適用于移動(dòng)端(Android,iOS,小程序等等),像這種原生平臺(tái)不支持cookie,比如說微信小程序,每一次請求都是一次會(huì)話,當(dāng)然我們可以每次去手動(dòng)為他添加cookie,詳情請查看博主另一篇博客;
- 避免CSRF跨站偽造攻擊,還是因?yàn)椴灰蕾嘽ookie;
缺點(diǎn)的話一個(gè)就是相比較于傳統(tǒng)的session登陸機(jī)制實(shí)現(xiàn)起來略微復(fù)雜一點(diǎn),另外一個(gè)比較大的缺點(diǎn)是由于服務(wù)器不保存 token,因此無法在使用過程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說,一旦 token 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
退出登陸的話,只要前端清除token信息即可。
基于JWT的token認(rèn)證實(shí)現(xiàn)
JWT(JSON Web Token)就是基于token認(rèn)證的代表,這邊就用JWT為列來介紹基于token的認(rèn)證機(jī)制。
需要引入JWT的依賴
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.2</version> </dependency>
生成token和驗(yàn)證token的工具類如下:
public class JWTTokenUtil {
//設(shè)置過期時(shí)間
private static final long EXPIRE_DATE=30*60*100000;
//token秘鑰
private static final String TOKEN_SECRET = "ZCfasfhuaUUHufguGuwu2020BQWE";
public static String token (String username,String password){
String token = "";
try {
//過期時(shí)間
Date date = new Date(System.currentTimeMillis()+EXPIRE_DATE);
//秘鑰及加密算法
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//設(shè)置頭部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//攜帶username,password信息,生成簽名
token = JWT.create()
.withHeader(header)
.withClaim("username",username)
.withClaim("password",password).withExpiresAt(date)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
public static boolean verify(String token){
/**
* @desc 驗(yàn)證token,通過返回true
* @params [token]需要校驗(yàn)的串
**/
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public static void main(String[] args) {
String username ="name1";
String password = "pw1";
//注意,一般不會(huì)把密碼等私密信息放在payload中,這邊只是舉個(gè)列子
String token = token(username,password);
System.out.println(token);
boolean b = verify(token);
System.out.println(b);
}
}
執(zhí)行結(jié)果如下:
Connected to the target VM, address: '127.0.0.1:11838', transport: 'socket' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXNzd29yZCI6IjEyMyIsImV4cCI6MTU5NzM5Nzc0OCwidXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.LI5S_nX-YcqtExI9UtKiP8FPqpQW_ccaws2coLzyOS0 true
關(guān)于DecodedJWT這個(gè)類,大家可以重點(diǎn)看下,里面包含了解碼后的用戶信息。
JWT的使用說明
客戶端收到服務(wù)器返回的 JWT,可以儲(chǔ)存在 Cookie 里面,也可以儲(chǔ)存在 localStorage。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求的頭信息Authorization字段里面。
Authorization: Bearer <token>
另一種做法是,跨域的時(shí)候,JWT 就放在 POST 請求的數(shù)據(jù)體里面。
JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對用戶進(jìn)行認(rèn)證。
為了減少盜用,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸。(或者是對JWT在前后端之間進(jìn)行加密之后在傳輸)
關(guān)于JWT的一個(gè)問題
上面生成JWT token的過程關(guān)鍵點(diǎn)就是密鑰,假如這個(gè)密鑰泄露了,那是不是就可以偽造token了。
還有就是生產(chǎn)環(huán)境的密鑰值,開發(fā)的程序員大概率是知道的,怎么防止程序要監(jiān)守自盜,偽造token值呢?希望有經(jīng)驗(yàn)的大佬指教下。
//token秘鑰 private static final String TOKEN_SECRET = "ZCfasfhuaUUHufguGuwu2020BQWE";
關(guān)于上面的問題,@仙湖碼農(nóng) 給出了一個(gè)簡單易懂的方案~
jwt 來生成token,還有一個(gè)玩法,用戶登錄時(shí),生成token的 SecretKey 是一個(gè)隨機(jī)數(shù),也就是說每個(gè)用戶,每次登錄時(shí)jwt SecretKey 是隨機(jī)數(shù),并保存到緩存,key是登錄賬戶,(當(dāng)然了,分布式緩存的話,就用Redis,sqlserver緩存等等),總之,客戶端訪問接口是,header 要帶登錄賬戶,和token,服務(wù)端拿到登錄賬號,到緩存去撈相應(yīng)的SecretKey ,然后再進(jìn)行token校驗(yàn)。可以防偽造token了(這個(gè)方案在一定程度上能防止偽造,但是不能防止token泄露被劫持)。
獲取refresh token的方法#
ASP.NET Core應(yīng)用JWT進(jìn)行用戶認(rèn)證及Token的刷新方案
參考#
Java登錄功能實(shí)現(xiàn)token生成與驗(yàn)證
PHP實(shí)現(xiàn)JWT的Token登錄認(rèn)證
以上所述是小編給大家介紹的Token登陸驗(yàn)證機(jī)制的原理及實(shí)現(xiàn),希望對大家有所幫助。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
詳解Java中ByteArray字節(jié)數(shù)組的輸入輸出流的用法
ByteArrayInputStream和ByteArrayOutputStream分別集成自InputStream和OutputStream這兩個(gè)輸入和輸出流,這里我們就來詳解Java中ByteArray字節(jié)數(shù)組的輸入輸出流的用法,需要的朋友可以參考下2016-06-06
Java項(xiàng)目開發(fā)中實(shí)現(xiàn)分頁的三種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java項(xiàng)目開發(fā)中實(shí)現(xiàn)分頁的三種方式,通過這一篇文章可以很快的學(xué)會(huì)java分頁功能,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
spring boot開發(fā)遇到坑之spring-boot-starter-web配置文件使用教程
Spring Boot支持容器的自動(dòng)配置,默認(rèn)是Tomcat,當(dāng)然我們也是可以進(jìn)行修改的。這篇文章給大家介紹了spring boot開發(fā)遇到坑之spring-boot-starter-web配置文件使用教程,需要的朋友參考下吧2018-01-01
@Valid和@Validated注解校驗(yàn)以及異常處理方式
在Javaweb開發(fā)中,防止數(shù)據(jù)庫惡意攻擊是至關(guān)重要的,盡管前端校驗(yàn)可以起到一定的篩選作用,但通過工具如postman直接對后端發(fā)起請求的情況仍然需要后端進(jìn)行嚴(yán)格的數(shù)據(jù)校驗(yàn),Java生態(tài)下,@Valid注解配合SpringBoot提供了一個(gè)便捷高效的后端數(shù)據(jù)校驗(yàn)方案2024-11-11
最全面的JVM優(yōu)化經(jīng)驗(yàn)總結(jié)
這篇文章主要介紹了最全面的JVM優(yōu)化經(jīng)驗(yàn)總結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06
springboot?vue測試列表遞歸查詢子節(jié)點(diǎn)下的接口功能實(shí)現(xiàn)
這篇文章主要為大家介紹了springboot?vue測試列表遞歸查詢子節(jié)點(diǎn)下的接口功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Java框架Struts2實(shí)現(xiàn)圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了Java框架Struts2實(shí)現(xiàn)圖片上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-08-08
Java中system.exit(0) 和 system.exit(1)區(qū)別
本文主要介紹了Java中system.exit(0) 和 system.exit(1)區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05

