Java使用Cookie實(shí)現(xiàn)認(rèn)證跳轉(zhuǎn)功能
一、Cookie 和 Header 的區(qū)別
項(xiàng)目 | Cookie | Header |
---|---|---|
定義 | 存儲(chǔ)在瀏覽器中的字段,用于保持用戶狀態(tài) | HTTP 請(qǐng)求/響應(yīng)的元數(shù)據(jù),描述請(qǐng)求數(shù)據(jù)信息 |
默認(rèn)行為 | 每次同域同路徑請(qǐng)求時(shí)自動(dòng)被瀏覽器附加到請(qǐng)求中 | 需要開發(fā)者手動(dòng)在請(qǐng)求中配置 |
存儲(chǔ) | 可持久存儲(chǔ)在本地瀏覽器 | 每次請(qǐng)求時(shí)重新傳送 |
與 JS 的關(guān)系 | 如果設(shè)置 HttpOnly ,JS 無法讀取 | JS 可以自由操作 Header |
通用場(chǎng)景 | 登錄狀態(tài)保持,身份聲明 | JWT Token、代理等信息傳遞 |
總結(jié):
Cookie 更適合于 Web 應(yīng)用自動(dòng)附帶的狀態(tài)保持,Header 則適用于前后端分離、接口授權(quán)等場(chǎng)景。
此外,如果系統(tǒng)是前后端分離或移動(dòng)端調(diào)用 API,推薦使用 Header + Bearer Token 的方式;而傳統(tǒng) Web 系統(tǒng)則更偏好基于 Cookie 的方案,方便瀏覽器自動(dòng)攜帶狀態(tài)。
二、功能需求簡(jiǎn)述
當(dāng)前系統(tǒng)需求并非完整用戶系統(tǒng)登錄,而是一個(gè)基于輸入 Token 的快速標(biāo)記機(jī)制,滿足以下目標(biāo):
- 前端提供 Token(如 email)
- 后端生成對(duì)應(yīng)的 JWT 并存入瀏覽器 Cookie
- 后續(xù)訪問頁面時(shí):
- 自動(dòng)讀取 Cookie 中的 JWT
- 后端解析 JWT,確認(rèn)身份合法則自動(dòng)跳轉(zhuǎn)
- 前端彈出提示當(dāng)前用戶 Token 和解析出的信息
三、項(xiàng)目結(jié)構(gòu)說明
本項(xiàng)目基于 Spring Boot 構(gòu)建,包含前后端組件,結(jié)構(gòu)如下:
MyTestJava ├── src/ │ └── main/ │ ├── java/ │ │ └── org.example/ │ │ ├── Main.java // SpringBoot 啟動(dòng)類 │ │ ├── controller/ │ │ │ └── TokenEntryController.java // 控制器:處理接口請(qǐng)求 │ │ └── util/ │ │ └── JwtUtils.java // 工具類:生成/解析 JWT │ └── resources/ │ ├── static/ │ │ └── index.html // 前端頁面 │ └── application.properties // 配置文件 ├── pom.xml // Maven 配置
說明:
- JwtUtils:JWT 的封裝生成器,負(fù)責(zé) create / parse
- TokenEntryController:接口控制器,處理前端發(fā)送的 token 保存請(qǐng)求和驗(yàn)證請(qǐng)求
- index.html:純前端展示頁面,包含輸入框與登錄判斷邏輯
- application.properties:可配置端口、秘鑰等
四、代碼實(shí)現(xiàn)分析
JWT 工具類 JwtUtils
@Component public class JwtUtils { private static final String SECRET = "cT9gHD9Myp&Jz@3E*U2a%Ld!Fg#xZvPf"; private static final Key KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); private static final long EXPIRATION = 30 * 24 * 60 * 60 * 1000L; // 30天 public String createToken(String email) { return Jwts.builder() .setSubject(email) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(KEY) .compact(); } public String parseToken(String token) { try { return Jwts.parserBuilder() .setSigningKey(KEY) .build() .parseClaimsJws(token) .getBody() .getSubject(); } catch (JwtException e) { return null; } } }
說明:
SECRET
是服務(wù)端自定義加密密鑰,推薦保存在配置文件中;- 可使用
Keys.secretKeyFor(SignatureAlgorithm.HS256)
動(dòng)態(tài)生成,但不適合生產(chǎn),因?yàn)榉?wù)重啟后舊 token 將無法解析。
Controller: TokenEntryController
@RestController @RequestMapping("/api") public class TokenEntryController { @Autowired private JwtUtils jwtUtils; @PostMapping("/token") public ResponseEntity<Map<String, Object>> saveToken(@RequestBody Map<String, String> payload, HttpServletResponse response) { String email = payload.get("token"); String token = jwtUtils.createToken(email); Cookie cookie = new Cookie("login_token", token); cookie.setHttpOnly(true); cookie.setPath("/"); cookie.setMaxAge(30 * 24 * 60 * 60); response.addCookie(cookie); return ResponseEntity.ok(Map.of( "status", "success", "redirectUrl", "https://www.baidu.com" )); } @GetMapping("/entry") public ResponseEntity<Map<String, Object>> checkToken(@CookieValue(value = "login_token", required = false) String token) { String email = jwtUtils.parseToken(token); if (email != null) { return ResponseEntity.ok(Map.of( "status", "success", "email", email, "redirectUrl", "https://www.baidu.com" )); } else { return ResponseEntity.ok(Map.of("status", "fail")); } } }
前端 HTML 邏輯
index.html 使用原生 JavaScript 與 Spring Boot 后端交互。
<input type="text" id="tokenInput" placeholder="請(qǐng)輸入 Email" /> <button onclick="sendToken()">發(fā)送</button> <script> function sendToken() { const token = document.getElementById("tokenInput").value; fetch("/api/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token }) }) .then(res => res.json()) .then(data => { if (data.status === "success") { window.location.href = data.redirectUrl; } }); } window.onload = function () { fetch("/api/entry") .then(res => res.json()) .then(body => { if (body.status === "success") { const token = getCookie("login_token"); alert("已登錄\nToken: " + token + "\nEmail: " + body.email); window.location.href = body.redirectUrl; } }); }; function getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? decodeURIComponent(match[2]) : null; } </script>
五、總結(jié)
- Cookie 和 Header 各有優(yōu)勢(shì),要根據(jù)場(chǎng)景選擇
- 使用 Cookie 可以自動(dòng)附加身份信息,適合 Web 項(xiàng)目
- JWT 分布系統(tǒng)輕量、無狀態(tài)、可擴(kuò)展
- 固定 KEY 應(yīng)該保存在配置文件中,而非隨機(jī)生成
- 瀏覽器無法讀取
HttpOnly
Cookie,確保安全性;如需前端讀 token,請(qǐng)將 HttpOnly = false
附錄:完整文件(可自行補(bǔ)全代碼)
Spring Boot 項(xiàng)目目錄結(jié)構(gòu)參考
src/main/java/org/example/ ├── controller/ │ └── LoginController.java # 登錄與驗(yàn)證碼相關(guān)接口 ├── model/ │ └── User.java # 用戶模型類 ├── service/ │ └── UserService.java # 登錄邏輯與驗(yàn)證碼緩存管理 ├── util/ │ └── EmailSender.java # 郵件發(fā)送工具類 └── Main.java # SpringBoot 啟動(dòng)類 src/main/resources/ ├── static/index.html # 前端測(cè)試頁面 └── application.properties # 郵件 + Redis + DB 配置項(xiàng)
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>MyTestJava</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!-- Spring Boot 父項(xiàng)目 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.3</version> <relativePath/> </parent> <dependencies> <!-- Spring Boot Web 模塊(包含內(nèi)嵌 Tomcat) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot 開發(fā)工具模塊 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <!-- JWT 核心 API --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <!-- JWT 實(shí)現(xiàn)類 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- JWT 序列化/反序列化 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- Jakarta Servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
index.html
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>Token 驗(yàn)證</title> <style> body { background-color: #f0f2f5; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .container { background-color: white; padding: 30px 40px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-align: center; } .input-group { display: flex; align-items: center; justify-content: center; gap: 10px; } input[type="text"] { padding: 10px; width: 220px; font-size: 16px; border: 1px solid #ccc; border-radius: 6px; } button { padding: 10px 20px; font-size: 16px; background-color: #1890ff; color: white; border: none; border-radius: 6px; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: #40a9ff; } </style> </head> <body> <div class="container"> <div class="input-group"> <label for="token-input"> <input type="text" id="token-input" placeholder="輸入 Token" /> </label> <button onclick="sendToken()">發(fā)送</button> </div> </div> <script> function sendToken() { const token = document.getElementById("token-input").value; fetch("/api/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token }) }) .then(response => response.json()) .then(data => { if (data.status === "success") { window.location.href = data.redirectUrl; } else { alert("Token 無效"); } }); } // 頁面加載后自動(dòng)訪問 entry 進(jìn)行判斷 window.onload = function () { fetch("/api/entry", { method: "GET" }) .then(response => { if (!response.ok) return null; return response.json(); }) .then(body => { if (body && body.status === "success") { alert("\n解析出的Email是:\n" + body.email); window.location.href = body.redirectUrl; } }) .catch(err => { console.error("檢查登錄狀態(tài)異常:", err); }); }; </script> </body> </html>
Main.java
package org.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * ================================================== * This class ${NAME} is responsible for [功能描述]. * * @author darker * @version 1.0 * ================================================== */ @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
JwtUtils.java
package org.example.util; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; /** * ================================================== * This class JwtUtils is responsible for [功能描述]. * * @author darker * @version 1.0 * ================================================== */ @Component public class JwtUtils { private static final String SECRET = "cT9gHD9Myp&Jz@3E*U2a%Ld!Fg#xZvPf"; private static final Key KEY = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); private static final long EXPIRATION = 30 * 24 * 60 * 60 * 1000L; // 30天 public String createToken(String email) { return Jwts.builder() .setSubject(email) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(KEY) .compact(); } public String parseToken(String token) { try { return Jwts.parserBuilder() .setSigningKey(KEY) .build() .parseClaimsJws(token) .getBody() .getSubject(); } catch (JwtException e) { return null; // 無效/過期 } } }
TokenEntryController.java
package org.example.controller; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.example.util.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * ================================================== * This class TokenEntryController is responsible for [功能描述]. * * @author draker * @version 1.0 * ================================================== */ @RestController @RequestMapping("/api") public class TokenEntryController { @Autowired private JwtUtils jwtUtils; /** * 創(chuàng)建 Token 并寫入 Cookie */ @PostMapping("/token") public ResponseEntity<Map<String, Object>> createToken(@RequestBody Map<String, String> payload, HttpServletResponse response) { String identity = payload.get("token"); // 可以是 email、userId 等 Map<String, Object> result = new HashMap<>(); if (identity == null || identity.isEmpty()) { result.put("status", "fail"); return ResponseEntity.badRequest().body(result); } String token = jwtUtils.createToken(identity); Cookie cookie = new Cookie("login_token", token); cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(30 * 24 * 60 * 60); // 30 天 response.addCookie(cookie); result.put("status", "success"); result.put("redirectUrl", "https://www.baidu.com"); return ResponseEntity.ok(result); } /** * 檢查 Cookie 中的 Token 并驗(yàn)證跳轉(zhuǎn) */ @GetMapping("/entry") public ResponseEntity<Map<String, Object>> checkToken(@CookieValue(value = "login_token", required = false) String token) { Map<String, Object> result = new HashMap<>(); if (token != null) { String email = jwtUtils.parseToken(token); if (email != null) { result.put("status", "success"); result.put("email", email); result.put("redirectUrl", "https://www.baidu.com"); return ResponseEntity.ok(result); } } result.put("status", "fail"); return ResponseEntity.ok(result); } }
希望本文對(duì) Cookie 和 Header 在實(shí)際進(jìn)程中的使用有所啟發(fā),也為基于 Spring Boot 實(shí)現(xiàn)輕量登錄認(rèn)證提供思路。
以上就是Java使用Cookie實(shí)現(xiàn)認(rèn)證跳轉(zhuǎn)功能的詳細(xì)內(nèi)容,更多關(guān)于Java Cookie認(rèn)證跳轉(zhuǎn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談maven的jar包和war包區(qū)別 以及打包方法
下面小編就為大家分享一篇淺談maven的jar包和war包區(qū)別 以及打包方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-11-11使用Java創(chuàng)建數(shù)據(jù)透視表并導(dǎo)出為PDF的方法
數(shù)據(jù)透視分析是一種強(qiáng)大的工具,可以幫助我們從大量數(shù)據(jù)中提取有用信息并進(jìn)行深入分析,本文將介紹如何使用Java來構(gòu)建PivotTable以及實(shí)現(xiàn)數(shù)據(jù)透視分析,并將其導(dǎo)出為PDF2023-10-10Spring實(shí)戰(zhàn)之ResourceLoaderAware加載資源用法示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之ResourceLoaderAware加載資源用法,結(jié)合實(shí)例形式分析了spring使用ResourceLoaderAware加載資源相關(guān)配置與操作技巧,需要的朋友可以參考下2020-01-01Spring mvc是如何實(shí)現(xiàn)與數(shù)據(jù)庫的前后端的連接操作的?
今天給大家?guī)淼氖顷P(guān)于Spring mvc的相關(guān)知識(shí),文章圍繞著Spring mvc是如何實(shí)現(xiàn)與數(shù)據(jù)庫的前后端的連接操作的展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06MapStruct處理Java中實(shí)體與模型間不匹配屬性轉(zhuǎn)換的方法
今天小編就為大家分享一篇關(guān)于MapStruct處理Java中實(shí)體與模型間不匹配屬性轉(zhuǎn)換的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03Spring?Boot教程之提高開發(fā)效率必備工具lombok
這篇文章主要介紹了Spring?Boot教程之提高開發(fā)效率必備工具lombok的相關(guān)資料,需要的朋友可以參考下2022-08-08