一、Web應(yīng)用程序的身份驗(yàn)證
1、Session認(rèn)證
① 用戶向服務(wù)器發(fā)送用戶名和密碼
② 服務(wù)器驗(yàn)證通過后,在當(dāng)前對話(session)里面保存相關(guān)數(shù)據(jù),如用戶角色,登陸時(shí)間等
③ 服務(wù)器向用戶返回一個(gè)session_id,寫入用戶的Cookie
④ 用戶隨后的每一次請求,都會通過Cookie,將session_id傳回服務(wù)器
⑤ 服務(wù)器收到session_id,找到前期保存的數(shù)據(jù),由此得知用戶的身份
認(rèn)證流程:
?
????????Session認(rèn)證的方式擴(kuò)展性不好,如果是服務(wù)器集群,或者是跨域的服務(wù)導(dǎo)向架構(gòu),就要求session數(shù)據(jù)共享,以便每臺服務(wù)器都能夠讀取session,針對這問題有兩種解決方案:
????????① session數(shù)據(jù)持久化,寫入數(shù)據(jù)庫或別的持久層。優(yōu)點(diǎn)是架構(gòu)清晰,但工程量大
????????② 服務(wù)器不再保存session數(shù)據(jù),所有數(shù)據(jù)都保存在客戶端,每次請求都發(fā)回服務(wù)器。Token就是其中一個(gè)代表
2、Token認(rèn)證
Token是在服務(wù)端產(chǎn)生的一串字符串,是客戶端訪問資源接口(API)時(shí)所需要的資源憑證
????????① 客戶端使用用戶名和密碼請求登陸,服務(wù)端收到請求,去驗(yàn)證用戶名和密碼
????????② 驗(yàn)證成功后,服務(wù)端會簽發(fā)一個(gè)token并把這個(gè)token發(fā)送給客戶端
????????③ 客戶端收到token后,存儲起來,比如放在cookie或者localStorage里頭
????????④ 客戶端每次向服務(wù)端請求資源時(shí)需要帶上這個(gè)token
????????⑤ 服務(wù)端收到請求后去驗(yàn)證這個(gè)token,成功則返回請求數(shù)據(jù)
?
實(shí)現(xiàn)方式:JWT(JSON Web Token)認(rèn)證
原理:
① 用戶發(fā)送用戶名和密碼后,服務(wù)器認(rèn)證并生成JWT令牌(JSON對象),將其發(fā)回給客戶端
?(為了防止用戶篡改數(shù)據(jù),服務(wù)器在生成這個(gè)對象的時(shí)候會加上簽名)
② 客戶端將JWT令牌存儲在本地以便后續(xù)使用
③ 當(dāng)客戶端向另一個(gè)域名服務(wù)器發(fā)送請求時(shí),將JWT令牌作為請求頭(放在Authorization字段里頭)或請求參數(shù)發(fā)送
④ 服務(wù)器收到請求后,檢查JWT令牌的有效性,并進(jìn)行身份驗(yàn)證和授權(quán)
⑤ 若令牌有效則返回請求的數(shù)據(jù),否則返回未授權(quán)的錯(cuò)誤信息
JWT由三個(gè)部分組成:
1、Header(頭部):
{
?"alg": "HS256", // 令牌類型
?"typ": "JWT" //加密算法
}
2、Payload(負(fù)載):
{
"iss": "example.com",
"sub": "1234567890",
"aud": ["foo", "bar"],
"exp": 1648696800,
"nbf": 1648693200, // 2022年4月29日10:20:00
"iat": 1648694700,
"jti": "abcdef123456"
}
// 1、iss(issuer): 表示JWT簽發(fā)者的名稱,通常是一個(gè)字符串或URL。
// 2、sub(subject): 表示JWT的主題,即客戶端的唯一標(biāo)識符,通常是一個(gè)用戶ID。
// 3、aud(audience): 表示JWT的預(yù)期接收者,即對該JWT有效的接收方,可以是單個(gè)字符串或一個(gè)字符串?dāng)?shù)組。
// 4、exp(expiration time): 表示JWT的過期時(shí)間,用Unix時(shí)間戳表示。
// 5、nbf(not before): 表示JWT的生效時(shí)間,用Unix時(shí)間戳表示。
// 6、iat(issued at): 表示JWT的簽發(fā)時(shí)間,用Unix時(shí)間戳表示。
// 7、jti(JWT ID): 表示JWT的唯一標(biāo)識符,通常用于避免重放攻擊。
3、Signature(簽名):由Header、Payload和一個(gè)密鑰(secret,存儲在服務(wù)器端,對外不可見)進(jìn)行簽名生成。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
將Header、Payload和Signature通過'.'連接在一起形成JWT令牌,例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
代碼實(shí)現(xiàn):
① 引入依賴
<dependency>
? ?<groupId>io.jsonwebtoken</groupId>
? ?<artifactId>jjwt</artifactId>
? ?<version>0.9.1</version>
</dependency>
② 添加JWT配置
# JWT相關(guān)配置
jwt.secret=your-secret
jwt.expiration=3600
③ 創(chuàng)建JWT工具類:用于生成和解析JWT
@Component
public class JwtUtils {
?
? ?// 秘鑰
? ?@Value("${jwt.secret}")
? ?private String secret;
?
? ?// 過期時(shí)間,單位秒
? ?@Value("${jwt.expiration}")
? ?private Long expiration;
?
? ?// 生成JWT
? ?public String generateToken(Long userId) {
? ? ? ?SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); // 創(chuàng)建密鑰
? ? ? ?Date now = new Date();
? ? ? ?Date expireTime = new Date(now.getTime() + expiration * 1000);
? ? ? ?return Jwts.builder()
? ? ? ? ? .setIssuer("issuer") // 設(shè)置JWT的簽發(fā)者
? ? ? ? ? .setAudience("audience") // 設(shè)置JWT的接收方
? ? ? ? ? .setSubject(userId.toString()) // 設(shè)置JWT的主題
? ? ? ? ? .setIssuedAt(now) // 設(shè)置JWT的簽發(fā)時(shí)間
? ? ? ? ? .setExpiration(expireTime) // 設(shè)置JWT的過期時(shí)間
? ? ? ? ? .signWith(key, SignatureAlgorithm.HS256) // 用HS256算法和密鑰key簽名JWT
? ? ? ? ? .compact(); // 生成JWT字符串
? }
?
? ?// 解析JWT
? ?public Claims parseToken(String token) {
? ? ? ?SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); // 創(chuàng)建密鑰
? ? ? ?return Jwts.parserBuilder()
? ? ? ? ? ? ? .setSigningKey(key) // 設(shè)置用于解析JWT的密鑰
? ? ? ? ? ? ? .build()
? ? ? ? ? ? ? .parseClaimsJws(token) // 解析JWT,獲取Jws<Claims>實(shí)例
? ? ? ? ? ? ? .getBody();
? }
}
④ 配置攔截器:用于驗(yàn)證請求中的JWT是否有效
@Component
public class JwtInterceptor implements HandlerInterceptor {
?
? ?@Autowired
? ?private JwtUtils jwtUtils;
?
? ?@Override
? ?public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
? ? ? ?// 從請求頭中獲取token
? ? ? ?String token = request.getHeader("Authorization");
? ? ? ?// 判斷token是否存在并以Bearer開頭
? ? ? ?if (token != null && token.startsWith("Bearer ")) {
? ? ? ? ? ?token = token.substring(7); // 去掉token前綴
? ? ? ? ? ?Claims claims = jwtUtils.parseToken(token); // 解析JWT
? ? ? ? ? ?if (claims != null) {
? ? ? ? ? ? ? ?Long userId = Long.valueOf(claims.getSubject()); // 獲取JWT中的用戶id
? ? ? ? ? ? ? ?// 將用戶信息存儲到request中,方便后續(xù)操作
? ? ? ? ? ? ? ?request.setAttribute("userId", userId);
? ? ? ? ? ? ? ?return true;
? ? ? ? ? }
? ? ? }
? ? ? ?// 解析失敗,返回401未授權(quán)狀態(tài)碼
? ? ? ?response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
? ? ? ?return false; // 攔截請求
? }
}
?
⑤ 使用JWT
@RestController
@RequestMapping("/api")
public class UserController {
? ?
? ?@Autowired
? ?private UserService userService;
? ?
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody UserLoginDto userLoginDto) {
? // 用戶登錄邏輯
? ?
? // 生成 JWT token
? String token = Jwts.builder()
? ? ? ? ? .setSubject(user.getUsername()) // 主題
? ? ? ? ? .claim("roles", user.getRoles())
? ? ? ? ? .setIssuedAt(new Date()) // 簽發(fā)時(shí)間
? ? ? ? ? .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 過期時(shí)間
? ? ? ? ? .signWith(SignatureAlgorithm.HS512, SECRET_KEY) // 使用算法和密鑰對token進(jìn)行簽名。
? ? ? ? ? .compact(); // 將JWT token生成為字符串
? ?
? // 返回 token 給客戶端
? return ResponseEntity.ok(new AuthResponse(token));
}
?
? ?
@GetMapping("/users")
@PreAuthorize("hasAuthority('ROLE_ADMIN')") // 擁有 ROLE_ADMIN 權(quán)限的用戶才能訪問該方法
public ResponseEntity<?> getUsers(@RequestHeader("Authorization") String authorizationHeader) {
? ? ? ?String token = authorizationHeader.substring(7); // 去掉 "Bearer" 前綴
? ?
? ? ? ?// 驗(yàn)證 token 是否有效
? ? ? ?Claims claims = Jwts.parser() // 獲取一個(gè)JwtParser對象
? ? ? ? .setSigningKey(SECRET_KEY) // 設(shè)置JWT的簽名密鑰,用于校驗(yàn)JWT的合法性
? ? ? ? .parseClaimsJws(token) // 將其轉(zhuǎn)化為Jws對象
? ? ? ? ? .getBody(); // 獲取Jws對象中的payload信息
? ?
? ? ?// 獲取用戶列表邏輯
}
?
? ?
? ?@GetMapping("/users/{id}")
? ?public ResponseEntity<?> getUserById(@PathVariable Long id) {
? ? ? ?// 根據(jù)用戶ID獲取用戶信息邏輯
? }
? ?
? ?// 省略其他接口...
}
Session和Token認(rèn)證的區(qū)別:
????????Session和Token都是常用的用戶認(rèn)證方式,它們的作用都是為了驗(yàn)證用戶身份和授權(quán)訪問資源,但是它們的實(shí)現(xiàn)方式有所不同。
????????Session是一種服務(wù)器端認(rèn)證方式,通常通過在服務(wù)器端保存用戶的登錄信息(較安全),以便在后續(xù)的請求中進(jìn)行驗(yàn)證。當(dāng)用戶進(jìn)行登錄操作時(shí),服務(wù)器會創(chuàng)建一個(gè)Session,并給這個(gè)Session分配一個(gè)唯一的標(biāo)識符(Session ID),然后將這個(gè)Session ID發(fā)送給客戶端保存??蛻舳嗽诤罄m(xù)的請求中需要攜帶這個(gè)Session ID,服務(wù)器端根據(jù)這個(gè)Session ID來查找對應(yīng)的Session,從而驗(yàn)證用戶的身份。
????????Token是一種無狀態(tài)認(rèn)證方式,通常通過在客戶端保存用戶的登錄信息(不安全),以便在后續(xù)的請求中進(jìn)行驗(yàn)證。當(dāng)用戶進(jìn)行登錄操作時(shí),服務(wù)器會生成一個(gè)Token,并將這個(gè)Token發(fā)送給客戶端保存。客戶端在后續(xù)的請求中需要攜帶這個(gè)Token,服務(wù)器端通過驗(yàn)證這個(gè)Token來確定用戶的身份。
優(yōu)缺點(diǎn):
????????Session需要在服務(wù)器端保存用戶的登錄信息,因此需要占用服務(wù)器的資源,并且需要在分布式系統(tǒng)中進(jìn)行Session共享和Session失效管理(工程量大)。
????????Token是無狀態(tài)的,不需要在服務(wù)器端保存用戶的登錄信息,因此具有良好的可擴(kuò)展性,并且可以很方便地實(shí)現(xiàn)分布式系統(tǒng)中的認(rèn)證和授權(quán)。用解析token的計(jì)算時(shí)間換取session的存儲空間,從而減輕服務(wù)器壓力,減少頻繁查詢數(shù)據(jù)庫
????????Session的安全性比較高,因?yàn)镾ession的內(nèi)容保存在服務(wù)器端,客戶端無法直接修改Session的內(nèi)容
????????Token的安全性相對較低,因?yàn)門oken的內(nèi)容保存在客戶端,客戶端可以通過一些手段來篡改Token的內(nèi)容。
Session和Token的應(yīng)用場景:
????????一般來說,使用 session 可能更適合傳統(tǒng)的 Web 應(yīng)用,因?yàn)樗ǔP枰脩粼跒g覽器中持續(xù)地與應(yīng)用交互,而且涉及到敏感數(shù)據(jù)的處理。例如,在電子商務(wù)網(wǎng)站中,用戶需要登錄才能訪問個(gè)人購物車和訂單等敏感信息,此時(shí)可以使用 session 來驗(yàn)證用戶身份,并在服務(wù)器端存儲相關(guān)的用戶信息和狀態(tài)。文章來源:http://www.zghlxwxcb.cn/news/detail-437255.html
????????而在 API (應(yīng)用程序編程接口)設(shè)計(jì)和單頁面應(yīng)用中,使用 token 可能更加常見。由于 API 和單頁面應(yīng)用的特性,客戶端可以直接與 API 或后端服務(wù)通信,而不需要經(jīng)過瀏覽器的中間層。此時(shí),使用 token 可以避免一些 session 的問題,如跨域和服務(wù)器負(fù)載均衡等。同時(shí),token 也更容易在不同服務(wù)之間進(jìn)行傳遞和共享,比如使用 OAuth2 等協(xié)議來實(shí)現(xiàn)單點(diǎn)登錄和授權(quán)等功能。文章來源地址http://www.zghlxwxcb.cn/news/detail-437255.html
到了這里,關(guān)于Web應(yīng)用程序的身份驗(yàn)證:Session認(rèn)證、Token認(rèn)證的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!