Spring?Boot項(xiàng)目開發(fā)接口權(quán)限控制之登錄+JWT實(shí)戰(zhàn)指南
前言
在Spring Boot項(xiàng)目開發(fā)中,完成接口開發(fā)只是基礎(chǔ),接口的安全控制才是提升項(xiàng)目專業(yè)度的關(guān)鍵——如果接口沒有權(quán)限校驗(yàn),任何人都能隨意調(diào)用接口、操作數(shù)據(jù),會(huì)造成嚴(yán)重的安全隱患。本文承接“接口開發(fā)完成后無安全控制”的場景,手把手教大家實(shí)現(xiàn)用戶登錄接口、整合JWT完成令牌生成與驗(yàn)證,以及通過攔截器/過濾器實(shí)現(xiàn)接口權(quán)限校驗(yàn),完整落地接口安全體系,適配實(shí)際項(xiàng)目與畢設(shè)需求,快速提升開發(fā)專業(yè)度。
一、前置準(zhǔn)備:環(huán)境搭建與核心依賴
本次實(shí)戰(zhàn)基于Spring Boot + MyBatis-Plus + JWT + Spring Security(簡化版),核心依賴如下,無需額外引入過多復(fù)雜組件,兼顧輕量與實(shí)用。
1.1 Maven 核心依賴
<!-- Spring Boot Web 核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 核心依賴(操作數(shù)據(jù)庫) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驅(qū)動(dòng) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JWT 核心依賴(生成/驗(yàn)證令牌) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- lombok 簡化實(shí)體類 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Security 簡化版(可選,用于權(quán)限注解) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1.2 核心配置(application.yml)
配置數(shù)據(jù)庫連接、JWT核心參數(shù)(密鑰、過期時(shí)間)、MyBatis-Plus相關(guān)配置,集中管理,便于后續(xù)修改:
spring:
# 數(shù)據(jù)庫配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_jwt?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
# Spring Security 配置(關(guān)閉默認(rèn)登錄頁,簡化配置)
security:
basic:
enabled: false
user:
name: admin
password: admin
# MyBatis-Plus 配置
mybatis-plus:
mapper-locations: classpath:mybatis/mapper/**/*.xml
type-aliases-package: com.example.demo.entity
configuration:
map-underscore-to-camel-case: true # 駝峰命名映射
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志(便于調(diào)試)
# JWT 自定義配置(自己定義,后續(xù)代碼中讀取)
jwt:
secret: springboot-jwt-2026-security-key # 密鑰(生產(chǎn)環(huán)境需更換為復(fù)雜密鑰,避免泄露)
expiration: 86400000 # 令牌過期時(shí)間(單位:毫秒),這里設(shè)置為24小時(shí)
header: Authorization # 請(qǐng)求頭中攜帶令牌的字段名,默認(rèn)Authorization
1.3 數(shù)據(jù)庫表設(shè)計(jì)(用戶表)
核心是用戶表,存儲(chǔ)用戶名、密碼(加密存儲(chǔ))、角色(用于后續(xù)權(quán)限區(qū)分,簡化版用字符串表示),無需復(fù)雜設(shè)計(jì),適配登錄與權(quán)限校驗(yàn)核心需求:
CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用戶主鍵', `username` varchar(50) NOT NULL COMMENT '用戶名(唯一)', `password` varchar(100) NOT NULL COMMENT '密碼(加密存儲(chǔ))', `role` varchar(20) NOT NULL COMMENT '用戶角色(如:admin、user)', `status` tinyint(1) DEFAULT 1 COMMENT '狀態(tài)(1:正常,0:禁用)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系統(tǒng)用戶表';
說明:密碼需加密存儲(chǔ)(本文使用BCrypt加密,Spring Security自帶,無需額外引入工具),禁止明文存儲(chǔ),避免安全風(fēng)險(xiǎn)。
二、核心實(shí)戰(zhàn)一:用戶登錄接口設(shè)計(jì)
登錄接口是權(quán)限控制的入口,核心邏輯:接收前端傳入的用戶名/密碼 → 校驗(yàn)用戶名是否存在、密碼是否匹配 → 校驗(yàn)通過生成JWT令牌 → 返回令牌給前端;校驗(yàn)失敗返回錯(cuò)誤信息。
2.1 實(shí)體類與DTO設(shè)計(jì)
2.1.1 用戶實(shí)體類(SysUser)
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 系統(tǒng)用戶實(shí)體類
*/
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.AUTO)
private Long id;
// 用戶名(唯一)
private String username;
// 加密后的密碼
private String password;
// 用戶角色(admin:管理員,user:普通用戶)
private String role;
// 狀態(tài)(1:正常,0:禁用)
private Integer status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
2.1.2 登錄請(qǐng)求DTO(LoginDTO)
接收前端傳入的登錄參數(shù),用于參數(shù)校驗(yàn)(用戶名、密碼非空):
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 登錄請(qǐng)求DTO(接收前端參數(shù))
*/
@Data
public class LoginDTO {
// 用戶名,非空校驗(yàn)
@NotBlank(message = "用戶名不能為空")
private String username;
// 密碼,非空校驗(yàn)
@NotBlank(message = "密碼不能為空")
private String password;
}
2.1.3 登錄響應(yīng)DTO(LoginVO)
返回給前端的結(jié)果,包含JWT令牌、用戶名、角色等信息,避免返回敏感數(shù)據(jù)(如加密后的密碼):
import lombok.Data;
/**
* 登錄響應(yīng)VO(返回給前端)
*/
@Data
public class LoginVO {
// JWT令牌
private String token;
// 用戶名
private String username;
// 用戶角色
private String role;
// 令牌過期時(shí)間(單位:毫秒)
private Long expiration;
}
2.2 Mapper、Service層實(shí)現(xiàn)(用戶校驗(yàn))
2.2.1 SysUserMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 用戶Mapper接口
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
// 根據(jù)用戶名查詢用戶(登錄核心查詢)
SysUser selectByUsername(@Param("username") String username);
}
2.2.2 SysUserService
核心是用戶校驗(yàn)邏輯:查詢用戶、校驗(yàn)密碼、校驗(yàn)用戶狀態(tài),使用Spring Security的BCryptPasswordEncoder進(jìn)行密碼加密與匹配:
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.SysUser;
import com.example.demo.mapper.SysUserMapper;
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
/**
* 用戶Service層
*/
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
@Autowired
private SysUserMapper sysUserMapper;
// BCrypt密碼加密器(Spring Security自帶)
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
/**
* 登錄校驗(yàn):根據(jù)用戶名和密碼校驗(yàn)用戶
* @param username 用戶名
* @param password 明文密碼
* @return 校驗(yàn)通過的用戶信息
*/
public SysUser loginCheck(String username, String password) {
// 1. 根據(jù)用戶名查詢用戶
SysUser user = sysUserMapper.selectByUsername(username);
Assert.notNull(user, "用戶名不存在");
// 2. 校驗(yàn)用戶狀態(tài)(是否禁用)
Assert.isTrue(user.getStatus() == 1, "用戶已禁用,請(qǐng)聯(lián)系管理員");
// 3. 校驗(yàn)密碼(明文密碼與數(shù)據(jù)庫加密密碼匹配)
Assert.isTrue(passwordEncoder.matches(password, user.getPassword()), "密碼錯(cuò)誤");
// 4. 校驗(yàn)通過,返回用戶信息(注意:不要返回密碼)
user.setPassword(null);
return user;
}
/**
* 新增用戶(用于測試,密碼自動(dòng)加密)
* @param user 用戶信息
* @return 新增結(jié)果
*/
public boolean addUser(SysUser user) {
// 密碼加密后存儲(chǔ)
user.setPassword(passwordEncoder.encode(user.getPassword()));
return save(user);
}
}
2.3 Controller層:登錄接口實(shí)現(xiàn)
接收前端請(qǐng)求,調(diào)用Service層校驗(yàn)用戶,校驗(yàn)通過后調(diào)用JWT工具類生成令牌,返回登錄結(jié)果:
import com.example.demo.dto.LoginDTO;
import com.example.demo.dto.LoginVO;
import com.example.demo.entity.SysUser;
import com.example.demo.service.SysUserService;
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* 登錄控制器
*/
@RestController
@RequestMapping("/auth")
@Validated
public class LoginController {
@Autowired
private SysUserService sysUserService;
@Autowired
private JwtUtil jwtUtil;
// 從配置文件讀取JWT過期時(shí)間
@Value("${jwt.expiration}")
private Long expiration;
/**
* 用戶登錄接口
* @param loginDTO 登錄參數(shù)(用戶名、密碼)
* @return 登錄結(jié)果(JWT令牌等)
*/
@PostMapping("/login")
public LoginVO login(@Valid @RequestBody LoginDTO loginDTO) {
// 1. 登錄校驗(yàn)(用戶名、密碼、用戶狀態(tài))
SysUser user = sysUserService.loginCheck(loginDTO.getUsername(), loginDTO.getPassword());
// 2. 生成JWT令牌(傳入用戶ID、用戶名、角色)
String token = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
// 3. 組裝返回結(jié)果
LoginVO loginVO = new LoginVO();
loginVO.setToken(token);
loginVO.setUsername(user.getUsername());
loginVO.setRole(user.getRole());
loginVO.setExpiration(expiration);
return loginVO;
}
}
2.4 測試登錄接口
先調(diào)用addUser方法新增測試用戶(如username=admin,password=123456,role=admin),密碼會(huì)自動(dòng)加密存儲(chǔ);
2. 用Postman調(diào)用POST接口:http://localhost:8080/auth/login,請(qǐng)求體如下:
{
"username": "admin",
"password": "123456"
}
- 響應(yīng)結(jié)果(成功):返回token、username、role、expiration,前端需存儲(chǔ)token,后續(xù)請(qǐng)求接口時(shí)攜帶該token。
三、核心實(shí)戰(zhàn)二:JWT 生成與驗(yàn)證(工具類實(shí)現(xiàn))
JWT(JSON Web Token)是一種無狀態(tài)的令牌機(jī)制,核心優(yōu)勢是無需在服務(wù)器存儲(chǔ)會(huì)話信息,僅通過令牌本身即可完成身份驗(yàn)證,適合分布式系統(tǒng)。本文封裝JWT工具類,實(shí)現(xiàn)令牌生成、驗(yàn)證、解析核心功能。
3.1 JWT工具類(JwtUtil)
核心邏輯:讀取配置文件中的密鑰和過期時(shí)間,使用jjwt依賴提供的API生成令牌、驗(yàn)證令牌有效性、解析令牌中的用戶信息(用戶ID、用戶名、角色)。
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* JWT工具類:生成令牌、驗(yàn)證令牌、解析令牌
*/
@Component
public class JwtUtil {
// 從配置文件讀取JWT密鑰
@Value("${jwt.secret}")
private String secret;
// 從配置文件讀取令牌過期時(shí)間(毫秒)
@Value("${jwt.expiration}")
private Long expiration;
// 令牌前綴(規(guī)范寫法,前端請(qǐng)求頭攜帶時(shí)需加上)
private static final String TOKEN_PREFIX = "Bearer ";
/**
* 生成JWT令牌
* @param userId 用戶ID
* @param username 用戶名
* @param role 用戶角色
* @return JWT令牌(不帶前綴)
*/
public String generateToken(Long userId, String username, String role) {
// 令牌過期時(shí)間 = 當(dāng)前時(shí)間 + 配置的過期時(shí)間
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
// 構(gòu)建JWT令牌
return Jwts.builder()
// 載荷:存儲(chǔ)用戶核心信息(自定義字段)
.claim("userId", userId)
.claim("username", username)
.claim("role", role)
// 主題(可自定義,一般存儲(chǔ)用戶名)
.setSubject(username)
// 簽發(fā)時(shí)間
.setIssuedAt(new Date())
// 過期時(shí)間
.setExpiration(expirationDate)
// 簽名算法:HS256,使用配置的密鑰
.signWith(SignatureAlgorithm.HS256, secret)
// 壓縮令牌
.compact();
}
/**
* 驗(yàn)證JWT令牌有效性
* @param token 待驗(yàn)證的令牌(可帶前綴,也可不帶)
* @return true:有效,false:無效
*/
public boolean validateToken(String token) {
try {
// 去除令牌前綴(如果有)
token = token.replace(TOKEN_PREFIX, "");
// 解析令牌,驗(yàn)證簽名和過期時(shí)間
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
// 捕獲所有異常(簽名錯(cuò)誤、令牌過期、格式錯(cuò)誤等)
return false;
}
}
/**
* 解析JWT令牌,獲取載荷中的用戶信息(通用方法)
* @param token 令牌(可帶前綴)
* @return 載荷對(duì)象(包含自定義的userId、username、role)
*/
public Claims parseToken(String token) {
token = token.replace(TOKEN_PREFIX, "");
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 從令牌中獲取用戶名
* @param token 令牌
* @return 用戶名
*/
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("username", String.class);
}
/**
* 從令牌中獲取用戶角色
* @param token 令牌
* @return 用戶角色
*/
public String getRoleFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("role", String.class);
}
/**
* 從令牌中獲取用戶ID
* @param token 令牌
* @return 用戶ID
*/
public Long getUserIdFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("userId", Long.class);
}
}
3.2 JWT核心注意事項(xiàng)
1. 密鑰(secret):生產(chǎn)環(huán)境必須更換為復(fù)雜的隨機(jī)字符串(如32位隨機(jī)字符),避免泄露,否則會(huì)導(dǎo)致令牌被偽造; 2. 過期時(shí)間(expiration):根據(jù)業(yè)務(wù)需求設(shè)置,一般為1-24小時(shí),過期后前端需重新登錄獲取新令牌; 3. 載荷信息:僅存儲(chǔ)非敏感核心信息(如用戶ID、用戶名、角色),禁止存儲(chǔ)密碼、手機(jī)號(hào)等敏感數(shù)據(jù); 4. 令牌前綴:前端請(qǐng)求時(shí),需在請(qǐng)求頭Authorization中攜帶令牌,格式為:Bearer + 空格 + 令牌(如:Bearer eyJhbGciOiJIUzI1NiJ9...)。
四、核心實(shí)戰(zhàn)三:攔截器 / 過濾器實(shí)現(xiàn)接口權(quán)限校驗(yàn)
生成JWT令牌后,需要攔截所有需要權(quán)限的接口,校驗(yàn)令牌的有效性、用戶的權(quán)限,實(shí)現(xiàn)接口保護(hù)。本文分別實(shí)現(xiàn)兩種方式:攔截器(HandlerInterceptor)和過濾器(Filter),按需選擇,核心邏輯一致。
方式一:攔截器(HandlerInterceptor)實(shí)現(xiàn)權(quán)限校驗(yàn)
攔截器是Spring MVC提供的組件,可攔截請(qǐng)求的預(yù)處理、后處理,適合與Spring生態(tài)整合,便于獲取Spring容器中的Bean(如JwtUtil),推薦使用。
4.1.1 自定義攔截器(JwtInterceptor)
核心邏輯:攔截請(qǐng)求 → 從請(qǐng)求頭獲取令牌 → 驗(yàn)證令牌有效性 → 解析用戶信息 → 校驗(yàn)用戶權(quán)限(可選) → 放行;若令牌無效/無令牌,直接返回401未授權(quán)。
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* JWT攔截器:實(shí)現(xiàn)接口權(quán)限校驗(yàn)
*/
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
// 從配置文件讀取請(qǐng)求頭中令牌的字段名
@Value("${jwt.header}")
private String jwtHeader;
/**
* 請(qǐng)求預(yù)處理(核心方法):在接口執(zhí)行前攔截,校驗(yàn)令牌
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 從請(qǐng)求頭中獲取令牌
String token = request.getHeader(jwtHeader);
// 2. 校驗(yàn)令牌是否存在
if (token == null || token.trim().isEmpty()) {
responseError(response, "未攜帶令牌,請(qǐng)先登錄");
return false;
}
// 3. 驗(yàn)證令牌有效性
if (!jwtUtil.validateToken(token)) {
responseError(response, "令牌無效或已過期,請(qǐng)重新登錄");
return false;
}
// 4. (可選)權(quán)限校驗(yàn):根據(jù)業(yè)務(wù)需求,校驗(yàn)用戶角色是否有權(quán)限訪問當(dāng)前接口
// 示例:假設(shè)/admin/**接口只有admin角色可訪問
String requestUri = request.getRequestURI();
String role = jwtUtil.getRoleFromToken(token);
if (requestUri.startsWith("/admin/") && !"admin".equals(role)) {
responseError(response, "權(quán)限不足,無法訪問");
return false;
}
// 5. 令牌校驗(yàn)通過,放行請(qǐng)求
return true;
}
/**
* 響應(yīng)錯(cuò)誤信息(統(tǒng)一返回JSON格式)
*/
private void responseError(HttpServletResponse response, String message) throws Exception {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401未授權(quán)
PrintWriter out = response.getWriter();
out.write("{\"code\":401,\"message\":\"" + message + "\"}");
out.flush();
out.close();
}
}
4.1.2 注冊(cè)攔截器(配置攔截規(guī)則)
通過WebMvcConfigurer配置攔截器,指定攔截哪些接口、放行哪些接口(如登錄接口無需攔截):
import com.example.demo.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Spring MVC 配置:注冊(cè)JWT攔截器
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
// 攔截所有接口
.addPathPatterns("/**")
// 放行接口(無需權(quán)限校驗(yàn))
.excludePathPatterns(
"/auth/login", // 登錄接口
"/auth/register", // 注冊(cè)接口(如果有)
"/swagger-ui/**", // Swagger文檔(開發(fā)環(huán)境放行)
"/v3/api-docs/**" // Swagger接口(開發(fā)環(huán)境放行)
);
}
}
方式二:過濾器(Filter)實(shí)現(xiàn)權(quán)限校驗(yàn)
過濾器是Servlet提供的組件,作用于請(qǐng)求進(jìn)入Servlet之前,更底層,適合通用的權(quán)限校驗(yàn)場景。實(shí)現(xiàn)邏輯與攔截器一致,僅代碼寫法不同。
4.2.1 自定義過濾器(JwtFilter)
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* JWT過濾器:實(shí)現(xiàn)接口權(quán)限校驗(yàn)
*/
@Component
public class JwtFilter implements Filter {
@Autowired
private JwtUtil jwtUtil;
@Value("${jwt.header}")
private String jwtHeader;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 獲取令牌
String token = request.getHeader(jwtHeader);
// 2. 校驗(yàn)令牌是否存在
if (token == null || token.trim().isEmpty()) {
responseError(response, "未攜帶令牌,請(qǐng)先登錄");
return;
}
// 3. 驗(yàn)證令牌有效性
if (!jwtUtil.validateToken(token)) {
responseError(response, "令牌無效或已過期,請(qǐng)重新登錄");
return;
}
// 4. (可選)權(quán)限校驗(yàn)(與攔截器邏輯一致)
String requestUri = request.getRequestURI();
String role = jwtUtil.getRoleFromToken(token);
if (requestUri.startsWith("/admin/") && !"admin".equals(role)) {
responseError(response, "權(quán)限不足,無法訪問");
return;
}
// 5. 放行請(qǐng)求
filterChain.doFilter(request, response);
}
// 響應(yīng)錯(cuò)誤信息(與攔截器一致)
private void responseError(HttpServletResponse response, String message) throws IOException {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
out.write("{\"code\":401,\"message\":\"" + message + "\"}");
out.flush();
out.close();
}
}
4.2.2 注冊(cè)過濾器
通過FilterRegistrationBean注冊(cè)過濾器,指定攔截規(guī)則,與攔截器的放行/攔截邏輯一致:
import com.example.demo.filter.JwtFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 過濾器配置:注冊(cè)JWT過濾器
*/
@Configuration
public class FilterConfig {
@Autowired
private JwtFilter jwtFilter;
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilterRegistration() {
FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(jwtFilter);
// 攔截所有接口
registrationBean.addUrlPatterns("/*");
// 放行接口(與攔截器一致)
registrationBean.addInitParameter("excludedPaths", "/auth/login,/auth/register,/swagger-ui/**,/v3/api-docs/**");
registrationBean.setName("JwtFilter");
registrationBean.setOrder(1); // 過濾器執(zhí)行順序,越小越先執(zhí)行
return registrationBean;
}
}
攔截器與過濾器對(duì)比(選型建議)
| 對(duì)比維度 | 攔截器(HandlerInterceptor) | 過濾器(Filter) |
|---|---|---|
| 所屬框架 | Spring MVC | Servlet |
| 執(zhí)行時(shí)機(jī) | 請(qǐng)求進(jìn)入Controller之前 | 請(qǐng)求進(jìn)入Servlet之前(更底層) |
| 依賴Spring | 是,可直接注入Spring Bean(如JwtUtil) | 否,需通過配置注入Bean,稍繁瑣 |
| 適用場景 | Spring Boot項(xiàng)目,需要結(jié)合Spring生態(tài)(如權(quán)限注解) | 通用Java Web項(xiàng)目,無需依賴Spring |
| 選型建議:Spring Boot項(xiàng)目優(yōu)先使用攔截器,整合更順暢,代碼更簡潔;非Spring項(xiàng)目使用過濾器。 |
五、實(shí)戰(zhàn)測試:接口權(quán)限校驗(yàn)效果
5.1 測試場景1:未攜帶令牌訪問需要權(quán)限的接口
調(diào)用接口:http://localhost:8080/admin/user/list(假設(shè)是管理員接口),未在請(qǐng)求頭攜帶Authorization令牌,響應(yīng)結(jié)果:
{
"code": 401,
"message": "未攜帶令牌,請(qǐng)先登錄"
}5.2 測試場景2:攜帶無效令牌訪問接口
請(qǐng)求頭攜帶錯(cuò)誤令牌(如隨意輸入字符串),響應(yīng)結(jié)果:
{
"code": 401,
"message": "令牌無效或已過期,請(qǐng)重新登錄"
}5.3 測試場景3:普通用戶訪問管理員接口
使用role=user的用戶登錄,獲取令牌,攜帶令牌訪問/admin/user/list接口,響應(yīng)結(jié)果:
{
"code": 401,
"message": "權(quán)限不足,無法訪問"
}5.4 測試場景4:攜帶有效令牌訪問接口
使用role=admin的用戶登錄,獲取令牌,在請(qǐng)求頭添加:Authorization: Bearer 令牌,訪問/admin/user/list接口,正常返回接口數(shù)據(jù),校驗(yàn)通過。
六、常見問題與解決方案
令牌驗(yàn)證失敗,提示“簽名錯(cuò)誤”?
解決方案:① 檢查JwtUtil中使用的密鑰(secret)是否與配置文件一致;② 確認(rèn)前端攜帶的令牌是否完整,無多余空格或字符;③ 檢查令牌生成時(shí)的簽名算法(本文用HS256)與驗(yàn)證時(shí)是否一致。
攔截器/過濾器未生效?
解決方案:① 檢查攔截器/過濾器是否添加@Component注解(注入Spring容器);② 檢查注冊(cè)配置類(WebMvcConfig/FilterConfig)是否添加@Configuration注解;③ 確認(rèn)攔截規(guī)則是否正確,避免放行接口寫錯(cuò)。
前端攜帶令牌后,后端仍提示“未攜帶令牌”?
解決方案:① 檢查前端請(qǐng)求頭字段名是否與配置文件中jwt.header一致(默認(rèn)Authorization);② 確認(rèn)令牌格式是否正確(Bearer + 空格 + 令牌);③ 排查跨域問題(若前后端分離,需配置跨域允許攜帶請(qǐng)求頭)。
密碼加密后,登錄時(shí)校驗(yàn)失敗?
解決方案:① 確認(rèn)新增用戶時(shí),是否使用BCryptPasswordEncoder加密密碼;② 登錄校驗(yàn)時(shí),是否使用matches方法(明文密碼與加密密碼匹配),而非直接equals比較。
七、總結(jié)
本文完整實(shí)現(xiàn)了Spring Boot接口權(quán)限控制的核心流程:從用戶登錄接口設(shè)計(jì)、JWT令牌生成與驗(yàn)證,到攔截器/過濾器實(shí)現(xiàn)權(quán)限校驗(yàn),完美解決“接口無安全控制”的問題,提升項(xiàng)目專業(yè)度。核心要點(diǎn):
登錄接口:核心是用戶校驗(yàn)(用戶名、密碼、狀態(tài)),密碼必須加密存儲(chǔ),避免安全風(fēng)險(xiǎn)。
JWT工具類:封裝生成、驗(yàn)證、解析令牌的核心方法,密鑰和過期時(shí)間需配置化,便于維護(hù)。
權(quán)限校驗(yàn):優(yōu)先使用攔截器(Spring Boot適配更好),按需配置攔截/放行規(guī)則,支持角色權(quán)限區(qū)分。
掌握本文內(nèi)容后,可直接將代碼整合到畢設(shè)或?qū)嶋H項(xiàng)目中,實(shí)現(xiàn)接口的安全保護(hù)。后續(xù)可進(jìn)一步擴(kuò)展:如令牌刷新機(jī)制、更細(xì)粒度的權(quán)限控制(基于接口的權(quán)限分配)、異常統(tǒng)一處理等,讓權(quán)限體系更完善。
到此這篇關(guān)于Spring Boot項(xiàng)目開發(fā)接口權(quán)限控制之登錄+JWT實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)SpringBoot接口權(quán)限控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot 如何使用 SaToken 進(jìn)行登錄認(rèn)證、權(quán)限管理及路由規(guī)則接口攔截
- SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證
- SpringBoot下token短信驗(yàn)證登入登出權(quán)限操作(token存放redis,ali短信接口)
- SpringBoot中給指定接口加上權(quán)限校驗(yàn)的實(shí)現(xiàn)
- SpringBoot集成AOP實(shí)現(xiàn)日志記錄與接口權(quán)限校驗(yàn)
- SpringBoot使用Spring Security實(shí)現(xiàn)基于URL的接口權(quán)限控制
相關(guān)文章
Servlet第一個(gè)項(xiàng)目的發(fā)布(入門)
這篇文章主要介紹了Servlet第一個(gè)項(xiàng)目的發(fā)布,下面是用servlet實(shí)現(xiàn)的一個(gè)簡單的web項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2021-04-04
在Spring應(yīng)用中進(jìn)行單元測試的解析和代碼演示
在Spring應(yīng)用中進(jìn)行單元測試通常涉及到Spring TestContext Framework,它提供了豐富的注解和工具來支持單元測試和集成測試,以下是如何在Spring應(yīng)用中進(jìn)行單元測試的詳細(xì)解析和代碼演示,需要的朋友可以參考下2024-06-06
spring boot與spring mvc的區(qū)別及功能介紹
這篇文章主要介紹了spring boot與spring mvc的區(qū)別是什么以及spring boot和spring mvc功能介紹,感興趣的朋友一起看看吧2018-02-02
Java JVM(Java虛擬機(jī))啟動(dòng)參數(shù)的設(shè)置方式
這段描述主要介紹了JavaJVM啟動(dòng)參數(shù)設(shè)置,涵蓋標(biāo)準(zhǔn)參數(shù)、非標(biāo)準(zhǔn)參數(shù)(-X)和非Stable參數(shù)(-XX)等并詳細(xì)解釋了常用參數(shù)如-Xms、-XX:MaxPermSize等等-XX:NewRatio等等等,幫助優(yōu)化Java應(yīng)用程序性能2026-06-06
Ajax實(shí)現(xiàn)搜索引擎自動(dòng)補(bǔ)全功能
本文主要介紹了Ajax實(shí)現(xiàn)搜索引擎自動(dòng)補(bǔ)全功能的實(shí)例解析。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-04-04
Java實(shí)現(xiàn)PDF文檔自動(dòng)化比較的教程解析
在日常工作中,我們經(jīng)常需要處理大量的 PDF 文檔,本文將深入探討如何利用 Java 技術(shù),結(jié)合強(qiáng)大的 Spire.PDF for Java 庫,實(shí)現(xiàn) PDF 文檔的自動(dòng)化比較,感興趣的可以了解下2025-11-11
Java必須學(xué)會(huì)的類的繼承與多態(tài)
這篇文章主要介紹了Java類的繼承與多態(tài)的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-08-08
Java實(shí)現(xiàn)驗(yàn)證碼的產(chǎn)生和驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)驗(yàn)證碼的產(chǎn)生和驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2012-01-01
SpringBoot中使用JeecgBoot的Autopoi導(dǎo)出Excel的方法步驟
這篇文章主要介紹了SpringBoot中使用JeecgBoot的Autopoi導(dǎo)出Excel的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
SpringBoot業(yè)務(wù)策略模式多實(shí)現(xiàn)類注入過程(Map注入)
這段文章主要討論了在業(yè)務(wù)處理過程中遇到的登錄場景,通過策略模式解決代碼冗余和維護(hù)困難的問題,具體介紹了策略模式的應(yīng)用場景、實(shí)現(xiàn)方式以及帶來的好處,適用于需要根據(jù)不同條件選擇不同實(shí)現(xiàn)邏輯的場景2026-06-06

