智慧社區項目開發(二)——基于 JWT 的登錄驗證功能實現詳解

在 Web 應用中,登錄驗證是保障系統安全的核心環節。本文將結合具體接口文檔,詳細講解如何基于 JWT(JSON Web Token)實現登錄驗證功能,包括 JWT 配置、工具類封裝、登錄流程處理等關鍵步驟,幫助開發者快速理解并落地類似功能。

一、需求分析:接口文檔解讀

本次實現的登錄驗證功能需滿足以下接口文檔要求,核心接口包括:

接口名稱請求方式接口地址核心功能描述
生成驗證碼GET/captcha生成驗證碼并存儲(用于登錄校驗)
用戶登錄POST/login校驗用戶信息,生成 JWT 令牌返回
Token 校驗GET/checkToken驗證令牌有效性
退出登錄POST/logout清除令牌,退出登錄

其中,登錄接口(/login)?是核心,需接收前端傳遞的username(用戶名)、password(密碼)、captcha(驗證碼)、uuid(驗證碼唯一標識),驗證通過后返回token(令牌)和expire(令牌過期時間)。

二、技術選型:JWT 為何適合登錄驗證?

JWT 是一種基于 JSON 的輕量級令牌,用于在客戶端和服務器之間安全傳遞信息。其優勢在于:

  • 無狀態:服務器無需存儲會話信息,令牌本身包含用戶身份等關鍵信息,適合分布式系統。
  • 安全性:通過簽名機制確保令牌不被篡改。
  • 自包含:可在令牌中嵌入用戶權限等信息,減少數據庫查詢。

本次使用jjwt庫實現 JWT 功能,配合 Redis 存儲驗證碼和令牌,兼顧安全性與效率。

三、實現步驟:從配置到接口落地

1. JWT 配置:基礎參數定義

首先在配置文件中定義 JWT 的核心參數,用于生成和驗證令牌:

jwt:secret: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4K67DMlSPXbgG0MPp0gH  # 簽名密鑰(需保密)expire: 86400000  # 令牌過期時間(毫秒),此處為24小時subject: door  # 令牌主題(可選,用于標識令牌用途)
  • secret:簽名密鑰,生成令牌時用于加密,驗證時用于解密,需確保安全性(建議生產環境使用更長更復雜的密鑰)。
  • expire:對應登錄接口返回的expire字段,控制令牌有效期。

2. 依賴導入:引入 JWT 工具庫

pom.xml中引入jjwt依賴,用于處理 JWT 的生成與解析:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

該版本穩定且功能完善,支持 HS256 等簽名算法,滿足本次需求。

3. JWT 工具類:封裝令牌核心操作

封裝JwtUtil工具類,實現令牌的生成、校驗等核心功能,代碼如下

package com.qcby.community.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.util.Date;
import java.util.UUID;@ConfigurationProperties(prefix = "jwt") // 綁定配置文件中jwt前綴的參數
@Component
public class JwtUtil {private long expire; // 過期時間(從配置文件注入)private String secret; // 簽名密鑰(從配置文件注入)private String subject; // 令牌主題(從配置文件注入)/*** 生成令牌* @param userId 用戶ID(作為令牌中的核心標識)* @return 生成的JWT令牌字符串*/public String createToken(String userId) {return Jwts.builder().claim("userId", userId) // 自定義載荷:存儲用戶ID.setSubject(subject) // 令牌主題.setExpiration(new Date(System.currentTimeMillis() + expire)) // 過期時間.setId(UUID.randomUUID().toString()) // 唯一標識(可選).signWith(SignatureAlgorithm.HS256, secret) // 使用HS256算法簽名.compact(); // 組裝令牌}/*** 校驗令牌有效性* @param token 待校驗的令牌* @return 校驗結果(true:有效;false:無效)*/public boolean checkToken(String token){if(StringUtils.isEmpty(token)){return false; // 令牌為空,直接無效}try {// 解析令牌(自動驗證簽名和過期時間)Jws<Claims> claimsJws = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);return true; // 解析成功,令牌有效} catch (Exception e) {// 解析失敗(簽名錯誤、過期等),令牌無效return false;}}// getter/setter(用于注入配置參數)public long getExpire() { return expire; }public void setExpire(long expire) { this.expire = expire; }public String getSecret() { return secret; }public void setSecret(String secret) { this.secret = secret; }public String getSubject() { return subject; }public void setSubject(String subject) { this.subject = subject; }
}

核心說明

  • createToken方法:根據用戶 ID 生成令牌,包含用戶標識、過期時間等信息,通過secret簽名確保不可篡改,對應登錄接口成功后返回的token
  • checkToken方法:用于驗證令牌有效性(包括簽名正確性和是否過期),對應/checkToken接口的核心邏輯。

4. 登錄接口實現:完整流程處理

登錄接口(/login)是驗證流程的核心,需完成驗證碼校驗、用戶信息驗證、令牌生成等步驟,代碼如下:

@RestController
public class LoginController {@Autowiredprivate JwtUtil jwtUtil; // 注入JWT工具類@Autowiredprivate UserService userService; // 用戶服務@Autowiredprivate RedisTemplate redisTemplate; // Redis模板(用于存儲驗證碼和令牌)/*** 處理登錄請求* @param loginForm 前端傳遞的登錄參數(包含username、password、captcha、uuid)* @return 登錄結果(成功返回token和expire;失敗返回錯誤信息)*/@PostMapping("/login")public Result login(@RequestBody LoginForm loginForm){// 1. 驗證碼校驗(基于Redis)// 從Redis獲取驗證碼(鍵為uuid,對應生成驗證碼接口返回的uuid)String code = (String) redisTemplate.opsForValue().get(loginForm.getUuid());if(code == null){return Result.ok().put("status", "fail").put("data", "驗證碼已過期");}if(!code.equals(loginForm.getCaptcha())){return Result.ok().put("status", "fail").put("data", "驗證碼錯誤");}// 2. 驗證用戶名QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", loginForm.getUsername());User user = userService.getOne(queryWrapper);if(user == null){return Result.error("用戶名錯誤");}// 3. 驗證密碼(SHA256加密比對)String password = SecureUtil.sha256(loginForm.getPassword()); // 前端密碼加密if(!password.equals(user.getPassword())){ // 與數據庫中加密后的密碼比對return Result.error("密碼錯誤");}// 4. 驗證用戶狀態(是否被鎖定)if(user.getStatus() == 0) {return Result.error("賬號已被鎖定,請聯系管理員");}// 5. 登錄成功:生成令牌并返回String token = jwtUtil.createToken(String.valueOf(user.getUserId())); // 生成token// 將token存入Redis(鍵為"communityuser-用戶ID",過期時間與token一致)redisTemplate.opsForValue().set("communityuser-"+user.getUserId(), token, jwtUtil.getExpire(), TimeUnit.MILLISECONDS);// 組裝返回結果(符合接口文檔:data包含token和expire)Map<String,Object> map = new HashMap<>();map.put("token", token);map.put("expire", jwtUtil.getExpire());return Result.ok().put("data", map);}
}

流程對應接口文檔說明

  • 參數接收LoginForm包含uuid(驗證碼標識)、captcha(驗證碼)、username(用戶名)、password(密碼),完全匹配登錄接口的請求參數。
  • 驗證碼校驗:通過uuid從 Redis 獲取驗證碼(生成驗證碼接口會將uuidcode存入 Redis),驗證過期和正確性,對應生成驗證碼接口的交互邏輯。
  • 返回結果:登錄成功時,返回data對象包含tokenexpire,與接口文檔中登錄成功的返回結構一致;失敗時返回對應錯誤信息。

5. Token 校驗接口實現

基于JwtUtilcheckToken方法,實現/checkToken接口:

@GetMapping("/checkToken")
public Result checkToken(HttpServletRequest request){// 從請求頭獲取token(假設前端將token放在Authorization頭中)String token = request.getHeader("Authorization");boolean valid = jwtUtil.checkToken(token);if(valid){return Result.ok().put("status", "ok");}else{return Result.ok().put("status", "error");}
}

該接口直接調用JwtUtil的校驗方法,返回statusokerror,完全符合接口文檔要求。

可以先在前端的 permission.js里代碼進行修改,

import router from "./router";
import store from "./store";
import { Message } from "element-ui";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import { getToken } from "@/utils/auth";
import getPageTitle from "@/utils/get-page-title";
import { checkToken } from "@/api/sys/login";NProgress.configure({ showSpinner: false });const whiteList = ["/login", "/auth-redirect"]; // 沒有重定向白名單router.beforeEach(async (to, from, next) => {NProgress.start();// 設置頁面標題document.title = getPageTitle(to.meta.title);let token = getToken();if (token) {//校驗TokencheckToken(token).then(res => {if (res.code === 200 && res.status === "error") {next({ path: "/error" });}});if (to.path === "/login") {// 如果已登錄,請重定向到主頁next({ path: "/" });NProgress.done();} else {// 確定用戶是否通過getInfo獲取了權限角色const hasRoles = store.getters.roles && store.getters.roles.length > 0;if (hasRoles) {next();} else {next();// try {//   // 獲取用戶信息//   const { routers } = await store.dispatch("user/getInfo");//   // 基于角色生成可訪問的路由映射//   const accessRoutes = await store.dispatch(//     "permission/generateRoutes",//     { routers }//   );//   // 動態添加可訪問的路由//   router.addRoutes(accessRoutes);//   // hack方法 確保addRoutes已完成//   // 設置replace:true,這樣導航就不會留下歷史記錄//   next({ ...to, replace: true });// } catch (error) {//   // 刪除令牌并轉到登錄頁重新登錄//   await store.dispatch("user/resetToken");//   Message.error(error || "Has Error");//   // next(`/login?redirect=${to.path}`)//   next("/login");//   NProgress.done();// }}}} else {/* 沒有token */if (whiteList.indexOf(to.path) !== -1) {// 在免登錄白名單,直接進入next();} else {// 否則全部重定向到登錄頁// next(`/login?redirect=${to.path}`)next("/login");NProgress.done();}}
});router.afterEach(() => {// 結束進度條NProgress.done();
});

修改前的邏輯(注釋部分)

原本的代碼實現了完整的權限控制流程:

  1. 用戶登錄后,獲取用戶角色和權限信息(通過?store.dispatch("user/getInfo"))。
  2. 根據用戶角色動態生成可訪問的路由(通過?store.dispatch("permission/generateRoutes"))。
  3. 使用?router.addRoutes()?動態添加路由,確保用戶只能訪問其權限范圍內的頁面。

修改后的邏輯(直接?next()

當你將這部分代碼注釋掉并直接調用?next()?時,會發生以下變化:

  1. 權限控制失效
    所有用戶(無論是否登錄、擁有何種角色)都可以訪問任意路由,包括需要特定權限的頁面。例如,普通用戶可能可以訪問管理員頁面。

  2. 動態路由未生成
    router.addRoutes()?未執行,意味著基于用戶角色的動態路由配置不會生效。應用可能只能訪問靜態定義的基礎路由。

  3. 用戶信息未獲取
    store.dispatch("user/getInfo")?未執行,Vuex 中不會存儲用戶角色、權限等信息,導致頁面上可能無法正確顯示與用戶相關的內容(如用戶名、頭像、導航菜單)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/916602.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/916602.shtml
英文地址,請注明出處:http://en.pswp.cn/news/916602.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jmeter的元件使用介紹:(七)后置處理器詳解

Jmeter的后置處理器主要用于取樣器執行后的提取數據操作。 Jmeter常用的后置處理器有:Json提取器、正則表達式提取器、邊界提取器、Beanshell后置處理器。此外還有Xpath提取器、CSS選擇器提取器等&#xff0c;由于這兩項多用前端頁面提取元素&#xff0c;目前的項目基本都是采…

Allure的安裝,在Pytest中的簡單使用以及生成測試報告

目錄 1.Allure的安裝 1--下載網址 2--選擇對應系統版本下載 3--配置Allure環境變量 4--驗證安裝是否成功 5--配置JAVAJDK的環境變量&#xff08;如果已經配置&#xff0c;可以忽視這一步&#xff09; 2.python中pytestAllure 1--python安裝Allure包 2--生成測試報告 1--使用pyt…

Oracle 數據庫報 ora-00257 錯誤并且執行alter system switch logfile 命令卡死的解決過程

Oracle 數據庫報 ora-00257 錯誤并且執行alter system switch logfile 命令卡死的解決過程 7月26日下午&#xff0c;某醫院用戶的 HIS 系統無法連接&#xff0c;報如下錯誤&#xff1a;初步判斷是歸檔日志問題。 用戶的 HIS 系統數據庫是雙節點 Oracle 11g Rac 集群。登錄服務器…

ArKTS:List 數組

一種&#xff1a;/**# encoding: utf-8# 版權所有 2025 ©涂聚文有限公司? # 許可信息查看&#xff1a;言語成了邀功盡責的功臣&#xff0c;還需要行爲每日來值班嗎# 描述&#xff1a; 數組# Author : geovindu,Geovin Du 涂聚文.# IDE : DevEco Studio 5.1.1 …

Spring Boot 3整合Spring AI實戰:9輪面試對話解析AI應用開發

Spring Boot 3整合Spring AI實戰&#xff1a;9輪面試對話解析AI應用開發 第1輪&#xff1a;基礎配置與模型調用 周先生&#xff1a;cc&#xff0c;先聊聊Spring AI的基礎配置吧。如何在Spring Boot 3項目中集成Ollama&#xff1f; cc&#xff1a;我們可以通過OllamaConfig.java…

標準SQL語句示例

一、基礎操作1. 數據庫操作-- 1. 創建數據庫 CREATE DATABASE 數據庫名稱 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;-- 2. 刪除數據庫 DROP DATABASE IF EXISTS 數據庫名稱;-- 3. 選擇數據庫 USE 數據庫名稱;-- 4. 顯示所有數據庫 SHOW DATABASES;-- 5. 查看數據庫創…

STM32-基本定時器

一.基本定時器簡介 STM32F1 系列共有 8 個定時器&#xff0c;分別為&#xff1a;基本定時器、通用定時器、高級定時器。基本定時器 TIM6 和 TIM7 是一個 16 位的只能向上計數的定時器&#xff0c;只能定時&#xff0c;沒有外部IO。 二.基本定時器功能 上圖為基本定時器的功能框…

ofd文件轉pdf

主要后端使用Java實現&#xff0c;前端可隨意搭配http請求添加依賴&#xff1a;<!-- OFD解析與轉換庫 --><dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>1.17.9</version></…

4.應用層自定義協議與序列化

1.應用層程序員寫的一個個解決我們實際問題, 滿足我們日常需求的網絡程序, 都是在應用層1.1再談“協議”協議是一種 "約定". socket api 的接口, 在讀寫數據時, 都是按 "字符串" 的方式來發送接收的. 如果我們要傳輸一些 "結構化的數據" 怎么辦呢…

【QT搭建opencv環境】

本文參考以下文章&#xff1a; https://blog.csdn.net/weixin_43763292/article/details/112975207 https://blog.csdn.net/qq_44743171/article/details/124335100 使用軟件 QT 5.14.2下載地址&#xff1a;download.qt.io 選擇版本&#xff1a;Qt 5.14.2 Qt 5.14.2百度網盤鏈接…

golang--函數棧

一、函數棧的組成結構&#xff08;棧幀&#xff09; 每個函數調用對應一個棧幀&#xff0c;包含以下核心部分&#xff1a; 1. 參數區 (Arguments) 位置&#xff1a;棧幀頂部&#xff08;高地址端&#xff09;內容&#xff1a; 函數調用時傳入的參數按從右向左順序壓棧&#xff…

【FAQ】創建Dynamics 365 Sales環境

參考文章&#xff1a;5 分鐘內安裝 Dynamics 365 Sales 步驟 1&#xff1a;訪問 Power Platform 管理中心 導航到make.powerapps.com&#xff0c;然后點擊右上角的齒輪圖標。選擇管理中心&#xff0c;或者訪問aka.ms/ppac訪問 Power Platform 管理中心。 第 2 步&#xff1a…

【數據庫】使用Sql Server將分組后指定字段的行數據轉為一個字段顯示,并且以逗號隔開每個值,收藏不迷路

大家好&#xff0c;我是全棧小5&#xff0c;歡迎來到《小5講堂》。 這是《Sql Server》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。 溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01; 目錄前言示例數據集數…

7.項目起步(1)

1&#xff0c;項目起步-初始化項目并使用git管理創建項目并精細化配置src目錄調整git 管理項目2項目起步-配置別名路徑聯想提示什么是別名路徑聯想提示如何進行配置 &#xff08;自動配置了&#xff09;{"compilerOptions" : {"baseUrl" : "./",…

【C++詳解】深入解析繼承 類模板繼承、賦值兼容轉換、派生類默認成員函數、多繼承與菱形繼承

文章目錄一、繼承概念二、繼承定義定義格式繼承后基類成員訪問方式的變化類模板的繼承三、基類和派?類間的轉換(賦值兼容轉換)四、繼承中的作用域隱藏規則兩道筆試常考題五、派生類的默認成員函數四個常見默認成員函數實現?個不能被繼承的類六、繼承與友元七、繼承與靜態成員…

加法器 以及ALU(邏輯算術單元)

加法器框架&#xff0c;首先介紹原理&#xff0c;然后引入一位加法器最后再引入多位加法器最后引入帶符號的加法器這一節涉及到的硬件電路的知識理解就好&#xff0c;實在看不懂就跳過&#xff0c;但是封裝以后的功能必須看懂。這是一個一般的加法過程涉及到的必要元素圖中已經…

設計模式實戰:自定義SpringIOC(親手實踐)

上一篇&#xff1a;設計模式實戰&#xff1a;自定義SpringIOC&#xff08;理論分析&#xff09; 自定義SpringIOC&#xff08;親手實踐&#xff09; 上一篇文章&#xff0c;我們介紹了SpringIOC容器的核心組件及其作用&#xff0c;下面我們來動手仿寫一個SpringIOC容器&#…

力扣面試150(42/150)

7.28 20. 有效的括號 給定一個只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判斷字符串是否有效。 有效字符串需滿足&#xff1a; 左括號必須用相同類型的右括號閉合。左括號必須以正確的順序閉合。每個右括號都有一…

基于黑馬教程——微服務架構解析(二):雪崩防護+分布式事務

之前的兩篇文章我們介紹了微服務的基礎概念及其服務間通信機制。本篇將深入探討微服務的核心保障&#xff1a;服務保護與分布式事務。一、微服務保護問題描述&#xff1a; 在一個購物車的微服務中&#xff0c;倘若某一項服務&#xff08;服務A&#xff09;同一時刻訪問的數據十…

LeetCode: 429 N叉樹的層序遍歷

題目描述給定一個 N 叉樹&#xff0c;返回其節點值的層序遍歷&#xff08;即從左到右&#xff0c;逐層訪問每一層的所有節點&#xff09;。示例輸入格式&#xff08;層序序列化&#xff09;&#xff1a;輸入示意&#xff1a;1/ | \3 2 4/ \5 6輸出&#xff1a;[[1], [3,2,4…