一、跨域認證遇到的問題
由於多終端的出現,很多的站點透過
web api restful
的形式對外提供服務,採用了前後端分離模式進行開發,因而在身份驗證的方式上可能與傳統的基於
cookie
的
Session Id
的做法有所不同,除了面臨跨域提交
cookie
的問題外,更重要的是,有些終端可能根本不支援
cookie
。
JWT(JSON Web Token)
是一種身份驗證及授權方案,簡單的說就是呼叫端呼叫
api
時,附帶上一個由
api
端頒發的
token
,以此來驗證呼叫者的授權資訊。
一般流程是下面這樣:
1
。
使用者向伺服器傳送使用者名稱和密碼
。
2
。
伺服器驗證通過後
,
在當前對話
(
session
)
裡面儲存相關資料
,
比如使用者角色
、
登入時間等等
。
3
。
伺服器向用戶返回一個
session_id
,
寫入使用者的
Cookie
。
4
。
使用者隨後的每一次請求
,
都會透過
Cookie
,
將
session_id
傳回伺服器
。
5
。
伺服器收到
session_id
,
找到前期儲存的資料
,
由此得知使用者的身份
。
這種模式的問題在於擴充套件性不好。單機沒有問題,如果是伺服器叢集、跨域的服務導向架構或者使用者禁用了
cookie
,就不行了。
二、解決方案
1. 單機和分散式應用下登入校驗,session 共享
單機和多節點
tomcat
應用登入檢驗
①、單機
tomcat
應用登入,
sesssion
儲存在瀏覽器和應用伺服器會話之間,使用者登入成功後,服務端會保證一個
session
,也會給客戶端一個
sessionId
,客戶端會把
sessionId
儲存在
cookie
中,使用者每次請求都會攜帶這個
sessionId
。
②、多節點
tomcat
應用登入,開啟
session
資料共享後,每臺伺服器都能夠讀取
session
。缺點是每個
session
都是佔用記憶體和資源的,每個伺服器節點都需要同步使用者的資料,即一個數據需要儲存多份到每個伺服器,當用戶量到達百萬、千萬級別的時,佔用資源就嚴重,使用者體驗特別不好!!
分散式應用中
session
共享
①、真實的應用不可能單節點部署,所以就有個多節點登入
session
共享的問題需要解決。
tomcat
支援
session
共享,但是有廣播風暴;使用者量大的時候,佔用資源就嚴重,不推薦
②、
Reids
叢集,儲存登陸的
token
,向外提供服務介面,
Redis
可設定過期時間(服務端使用
UUID
生成隨機
64
位或者
128
位
token
,放入
Redis
中,然後返回給客戶端並存儲)。
③、使用者第一次登入成功時,需要先自行生成
token
,然後將
token
返回到瀏覽器並存儲在
cookie
中, 並在
Redis
伺服器上以
token
為
key
,使用者資訊作為
value
儲存。後續使用者再操作,可以透過
HttpServletRequest
物件直接讀取
cookie
中的
token
,並在
Redis
中取得相對應的使用者資料進行比較(使用者每次訪問都攜帶此
token
,服務端去
Redis
中校驗是否有此使用者即可)。
④、 缺點:必須部署
Redis
,每次必須訪問
Redis
,
IO
開銷特別大。
2. 最終解決方案:使用 JWT 實現 Token 認證
JWT 的原理
伺服器認證以後,生成一個 JSON 物件發回給使用者,以後使用者與服務端通訊的時候,都要發回這個 JSON 物件。伺服器完全只靠這個物件認定使用者身份。為了防止使用者篡改資料,伺服器在生成這個物件的時候,會加上簽名。也就是說伺服器就不儲存任何 session 資料了,即伺服器變成無狀態了,從而比較容易實現擴充套件。
java 簡單來說,就是透過一定規範來生成 token,然後可以透過解密演算法逆向解密 token,這樣就可以獲取使用者資訊
優點和缺點
優點:生產的 token 可以包含基本資訊,比如 id、使用者暱稱、頭像等資訊,避免再次查庫;儲存在客戶端,不佔用服務端的記憶體資源
缺點:token 是經過 base64 編碼,所以可以解碼,因此 token 加密前的物件不應該包含敏感資訊(如使用者許可權,密碼等)
JWT 格式組成:頭部+負載+簽名 ( header + payload + signature )
頭部:主要是描述簽名演算法。
負載:主要描述是加密物件的資訊,如使用者的 id 等,也可以加些規範裡面的東西,如 iss 簽發者,exp 過期時間,sub 面向的使用者。
簽名:主要是把前面兩部分進行加密,防止別人拿到 token 進行base 解密後篡改 token。
3. 案例圖設計
三、程式碼演示案例
pom。xml 檔案引入依賴和實體類
<!——
依賴可以減少實體類
getter
/
setter等方法書寫
——>
<
dependency
>
<
groupId
>
org
。
projectlombok
groupId
>
<
artifactId
>
lombok
artifactId
>
<
optional
>
true
optional
>
dependency
>
<!——
JWT相關
——>
<
dependency
>
<
groupId
>
io
。
jsonwebtoken
groupId
>
<
artifactId
>
jjwt
artifactId
>
<
version
>
0
。
7
。
0
version
>
dependency
>
====================================================================================
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public
class
User
implements
Serializable
{
private
Integer
id
;
private
String
openid
;
private
String
name
;
private
String
headImg
;
private
String
phone
;
private
String
sign
;
private
Integer
sex
;
private
String
city
;
private
Date
createTime
;
}
生成 JWT 工具類
public
class
JwtUtil
{
// 主題
public
static
final
String
SUBJECT
=
“RookieLi”
;
// 秘鑰
public
static
final
String
SECRETKEY
=
“Rookie666”
;
// 過期時間
public
static
final
long
EXPIRE
=
1000
*
60
*
60
*
24
*
7
;
//過期時間,毫秒,一週
// 生成 JWT
public
static
String
geneJsonWebToken
(
User
user
)
{
if
(
user
==
null
||
user
。
getId
()
==
null
||
user
。
getName
()
==
null
||
user
。
getHeadImg
()
==
null
)
{
return
null
;
}
String
token
=
Jwts
。
builder
()
。
setSubject
(
SUBJECT
)
。
claim
(
“id”
,
user
。
getId
())
。
claim
(
“name”
,
user
。
getName
())
。
claim
(
“img”
,
user
。
getHeadImg
())
。
setIssuedAt
(
new
Date
())
。
setExpiration
(
new
Date
(
System
。
currentTimeMillis
()
+
EXPIRE
))
。
signWith
(
SignatureAlgorithm
。
HS256
,
SECRETKEY
)。
compact
();
return
token
;
}
// 校驗 JWT
public
static
Claims
checkJWT
(
String
token
)
{
try
{
final
Claims
claims
=
Jwts
。
parser
()。
setSigningKey
(
SECRETKEY
)。
parseClaimsJws
(
token
)。
getBody
();
return
claims
;
}
catch
(
Exception
e
)
{
e
。
printStackTrace
();
}
return
null
;
}
}
測試 JWT 工具類
public
class
JwtUtilTest
{
@Test
public
void
testGeneJwt
(){
User
user
=
new
User
();
user
。
setId
(
999
);
user
。
setHeadImg
(
“I‘m busy”
);
user
。
setName
(
“Rookie”
);
String
token
=
JwtUtil
。
geneJsonWebToken
(
user
);
System
。
out
。
println
(
token
);
}
@Test
public
void
testCheck
(){
// 下面此 token 字串是上面的結果生成的,每次不一樣,不是寫死的
String
token
=
“eyJhbGciOiJIUzI1NiJ9。eyJzdWIiOiJSb29raWVMaSIsImlkIjo5OTks
Im5hbWUiOiJSb29raWUiLCJpbWciOiJJJ20gYnVzeSIsImlhdCI6MTU2NzMxNjk4NywiZXhwI
joxNTY3OTIxNzg3fQ。FJh41VwVh2gh5-_cOG0SOgoO3dR_ZcK9VWNNskWqKl0”
;
Claims
claims
=
JwtUtil
。
checkJWT
(
token
);
if
(
claims
!=
null
){
String
name
=
(
String
)
claims
。
get
(
“name”
);
String
img
=
(
String
)
claims
。
get
(
“img”
);
int
id
=(
Integer
)
claims
。
get
(
“id”
);
System
。
out
。
println
(
name
);
System
。
out
。
println
(
img
);
System
。
out
。
println
(
id
);
}
else
{
System
。
out
。
println
(
“非法token”
);
}
}
}
參考部落格:
http://www。
ruanyifeng。com/blog/201
8/07/json_web_token-tutorial。html
https://www。
cnblogs。com/jpfss/p/109
29458。html