一、跨域認證遇到的問題

由於多終端的出現,很多的站點透過

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. 案例圖設計

SpringBoot 2.x 使用 JWT(JSON Web Token)

三、程式碼演示案例

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