feat(admin_server): 实现管理员登录和 JWT 认证功能
- 新增管理员登录接口和相关逻辑 - 实现 JWT 生成和验证功能 - 添加 Redis 服务用于黑名单 token - 更新应用配置,增加 JWT 相关设置- 重构部分代码以支持新功能
This commit is contained in:
parent
8e3e8fc8b8
commit
c007ae5c8a
69
pom.xml
69
pom.xml
|
|
@ -1,6 +1,6 @@
|
|||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>admin_server</artifactId>
|
||||
|
|
@ -13,58 +13,113 @@
|
|||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring-boot.version>2.6.13</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.2.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot 配置处理器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 单元测试 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ✅ Swagger 2.0 + Springfox Starter -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- ✅ Knife4j for Springfox(注意不是 openapi3) -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Devtools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.example.admin_server.common;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class RedisService {
|
||||
|
||||
@Resource(name = "stringRedisTemplate") // 指定注入名字为 stringRedisTemplate 的bean
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 将 token 加入黑名单,并设置过期时间(毫秒)
|
||||
*/
|
||||
public void blacklistToken(String token, long expirationMillis) {
|
||||
stringRedisTemplate.opsForValue().set(token, "blacklisted", expirationMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 token 是否在黑名单中
|
||||
*/
|
||||
public boolean isTokenBlacklisted(String token) {
|
||||
return stringRedisTemplate.hasKey(token);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.example.admin_server.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public Docket adminApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.groupName("管理端")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.example.admin_server.controller.admin"))
|
||||
.paths(PathSelectors.ant("/api/admin/**"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket clientApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.groupName("客户端")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.example.admin_server.controller.client"))
|
||||
.paths(PathSelectors.ant("/api/client/**"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Docket employeeApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.groupName("员工端")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.example.admin_server.controller.employee"))
|
||||
.paths(PathSelectors.ant("/api/employee/**"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("后台管理接口文档")
|
||||
.description("接口文档说明")
|
||||
.version("1.0")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.example.admin_server.config;
|
||||
|
||||
import com.example.admin_server.interceptor.JwtInterceptor;
|
||||
import com.example.admin_server.utils.JwtUtil;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig implements WebMvcConfigurer {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public WebSecurityConfig(JwtUtil jwtUtil) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
// 注册自定义的 JWT 拦截器
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new JwtInterceptor(jwtUtil))
|
||||
.addPathPatterns("/api/admin/**", "/api/client/**"); // 只拦截需要鉴权的路径
|
||||
}
|
||||
|
||||
// Spring Security的过滤器链配置
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 关闭csrf,因为JWT不需要csrf保护
|
||||
.csrf().disable()
|
||||
// 使用无状态会话,不创建session
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
// 允许匿名访问的接口(登录、注册等)
|
||||
.authorizeRequests()
|
||||
.antMatchers("/api/public/**", "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", "/doc.html").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
// 禁用默认登录页
|
||||
.formLogin().disable()
|
||||
// 禁用默认注销功能
|
||||
.logout().disable();
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.example.admin_server.constant;
|
||||
|
||||
/**
|
||||
* 权限常量
|
||||
*/
|
||||
public class AuthConst {
|
||||
// 管理端
|
||||
public static final String ADMIN_AUTHORIZATION_HEADER = "adminAuthToken";
|
||||
// 客户端
|
||||
public static final String FRONT_AUTHORIZATION_HEADER = "assessToken";
|
||||
}
|
||||
|
|
@ -1,19 +1,60 @@
|
|||
package com.example.admin_server.controller.admin;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.example.admin_server.common.Result;
|
||||
import com.example.admin_server.enums.ResultCode;
|
||||
import com.example.admin_server.mapper.AdminMapper;
|
||||
import com.example.admin_server.model.dto.LoginDto;
|
||||
import com.example.admin_server.model.entity.Admin;
|
||||
import com.example.admin_server.utils.JwtUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.util.DigestUtils;
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 后台管理员表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author FallingCliff
|
||||
* @since 2025-05-24
|
||||
*/
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
@RequestMapping("/api/admin")
|
||||
@RequiredArgsConstructor
|
||||
@Api(tags = {"管理员接口"})
|
||||
public class AdminController {
|
||||
|
||||
private final AdminMapper adminMapper;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
@PostMapping("/login")
|
||||
@ApiOperation(value = "管理员登陆")
|
||||
public Result<?> login(@RequestBody LoginDto request) {
|
||||
Admin admin = adminMapper.selectOne(new QueryWrapper<Admin>()
|
||||
.eq("username", request.getUsername()));
|
||||
|
||||
if (admin == null) {
|
||||
return Result.of(ResultCode.UNAUTHORIZED, "用户不存在");
|
||||
}
|
||||
|
||||
String inputPassword = DigestUtils.md5DigestAsHex(request.getPassword().getBytes());
|
||||
System.out.println(inputPassword);
|
||||
if (!admin.getPassword().equals(inputPassword)) {
|
||||
return Result.of(ResultCode.UNAUTHORIZED, "密码错误");
|
||||
}
|
||||
|
||||
if (admin.getStatus() == 0) {
|
||||
return Result.of(ResultCode.FAIL, "账号已禁用");
|
||||
}
|
||||
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("id", admin.getId());
|
||||
claims.put("username", admin.getUsername());
|
||||
claims.put("isSuper", admin.getIsSuper());
|
||||
|
||||
String token = jwtUtil.generateToken(claims);
|
||||
return Result.ok(token);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.example.admin_server.controller.admin;
|
||||
|
||||
import com.example.admin_server.config.AppConfig;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -10,14 +10,14 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin")
|
||||
@Tag(name = "AdminMain")
|
||||
@Api(tags = {"AdminMain"})
|
||||
@RequiredArgsConstructor
|
||||
public class AdminMainController {
|
||||
|
||||
private final AppConfig appConfig;
|
||||
|
||||
@GetMapping("/welcome")
|
||||
@Operation(summary = "Hello admin")
|
||||
@ApiOperation(value = "Hello admin")
|
||||
public String welcome() {
|
||||
return "Hello admin" + appConfig.getEnvName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.example.admin_server.controller.client;
|
||||
|
||||
import com.example.admin_server.config.AppConfig;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -10,14 +10,14 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/client")
|
||||
@Tag(name = "ClientMain")
|
||||
@Api(tags = {"ClientMain"})
|
||||
@RequiredArgsConstructor
|
||||
public class ClientMainController {
|
||||
|
||||
private final AppConfig appConfig;
|
||||
|
||||
@GetMapping("/welcome")
|
||||
@Operation(summary = "Hello client")
|
||||
@ApiOperation(value = "Hello client")
|
||||
public String welcome(){
|
||||
return "Hello client" + appConfig.getEnvName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.example.admin_server.controller.employee;
|
||||
|
||||
import com.example.admin_server.config.AppConfig;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -10,14 +10,14 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/employee")
|
||||
@Tag(name = "EmployeeMain", description = "welcome")
|
||||
@Api(tags = {"EmployeeMain"})
|
||||
@RequiredArgsConstructor
|
||||
public class EmployeeMainController {
|
||||
|
||||
private final AppConfig appConfig;
|
||||
|
||||
@GetMapping("/welcome")
|
||||
@Operation(summary = "Hello employee")
|
||||
@ApiOperation(value = "Hello employee")
|
||||
public String welcome() {
|
||||
return "Hello employee" + appConfig.getEnvName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public enum ResultCode {
|
|||
|
||||
SUCCESS(200, "成功"),
|
||||
FAIL(500, "失败"),
|
||||
NOT_LOGIN(400, "未登录"),
|
||||
UNAUTHORIZED(401, "未授权"),
|
||||
FORBIDDEN(403, "禁止访问"),
|
||||
NOT_FOUND(404, "资源不存在"),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.example.admin_server.interceptor;
|
||||
|
||||
import com.example.admin_server.common.Result;
|
||||
import com.example.admin_server.constant.AuthConst;
|
||||
import com.example.admin_server.enums.ResultCode;
|
||||
import com.example.admin_server.utils.JwtUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class JwtInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public JwtInterceptor(JwtUtil jwtUtil) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) throws Exception {
|
||||
String requestURI = request.getRequestURI();
|
||||
|
||||
String token = null;
|
||||
String clientType = null;
|
||||
|
||||
if (requestURI.startsWith("/api/admin/")) {
|
||||
// 管理端请求
|
||||
token = request.getHeader(AuthConst.ADMIN_AUTHORIZATION_HEADER);
|
||||
clientType = "admin";
|
||||
} else if (requestURI.startsWith("/api/client/")) {
|
||||
// 客户端请求
|
||||
token = request.getHeader(AuthConst.FRONT_AUTHORIZATION_HEADER);
|
||||
clientType = "front";
|
||||
} else {
|
||||
// 非需鉴权路径,可以放行
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(token)) {
|
||||
log.warn("请求[{}]缺少{}令牌", requestURI, clientType);
|
||||
writeJsonResponse(response, Result.of(ResultCode.NOT_LOGIN));
|
||||
return false;
|
||||
}
|
||||
|
||||
Optional<Claims> claims = jwtUtil.parseToken(token);
|
||||
if (!claims.isPresent() || jwtUtil.isTokenExpired(token)) {
|
||||
log.warn("请求[{}]令牌无效或已过期", requestURI);
|
||||
writeJsonResponse(response, Result.of(ResultCode.UNAUTHORIZED, "token无效或过期"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通过校验,将Claims和客户端类型存入请求属性,方便后续使用
|
||||
request.setAttribute("claims", claims.get());
|
||||
request.setAttribute("clientType", clientType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writeJsonResponse(HttpServletResponse response, Object resultObj) throws Exception {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
String json = com.fasterxml.jackson.databind.json.JsonMapper.builder().build().writeValueAsString(resultObj);
|
||||
response.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.example.admin_server.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class LoginDto {
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.example.admin_server.utils;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${app.jwt.secret:mwsK9Ol9Ni2IyTvcdgFDVBxatw8QWx2o}")
|
||||
private String secret;
|
||||
|
||||
@Value("${app.jwt.expiration:86400000}") // 默认过期时间:1天(毫秒)
|
||||
private long expire;
|
||||
|
||||
private Key key;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.key = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 JWT token
|
||||
* @param claims 自定义载荷,比如 userId、roles 等
|
||||
* @return token 字符串
|
||||
*/
|
||||
public String generateToken(Map<String, Object> claims) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expire);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JWT,返回 Optional<Claims>
|
||||
* @param token JWT字符串
|
||||
* @return Claims 或空 Optional
|
||||
*/
|
||||
public Optional<Claims> parseToken(String token) {
|
||||
try {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
return Optional.of(claims);
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
log.warn("解析JWT失败: {}", e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Token 是否过期
|
||||
* @param token JWT字符串
|
||||
* @return true 如果过期或者解析失败
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
return parseToken(token)
|
||||
.map(claims -> claims.getExpiration().before(new Date()))
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Token 是否有效(非空且未过期)
|
||||
* @param token JWT字符串
|
||||
* @return true 有效
|
||||
*/
|
||||
public boolean isTokenValid(String token) {
|
||||
return parseToken(token).isPresent() && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取 userId (假设保存在 claims 的 "userId" 字段)
|
||||
* @param token JWT字符串
|
||||
* @return Optional<String> 用户ID
|
||||
*/
|
||||
public Optional<String> getUserIdFromToken(String token) {
|
||||
return parseToken(token)
|
||||
.map(claims -> claims.get("userId", String.class));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,9 @@ server:
|
|||
|
||||
# application.yml (公用配置)
|
||||
spring:
|
||||
mvc:
|
||||
path match:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: my-app
|
||||
jackson:
|
||||
|
|
@ -15,6 +18,9 @@ spring:
|
|||
|
||||
app:
|
||||
env-name: 'dev'
|
||||
jwt:
|
||||
secret: 's9TfkPeAKJOlDw4ox3r6VhAMG7KfI0RK'
|
||||
expiration: 86400000
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ server:
|
|||
|
||||
# application.yml (公用配置)
|
||||
spring:
|
||||
mvc:
|
||||
path match:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: my-app
|
||||
jackson:
|
||||
|
|
@ -15,6 +18,9 @@ spring:
|
|||
|
||||
app:
|
||||
env-name: 'prop'
|
||||
jwt:
|
||||
secret: '2n9g8b5TfsYLBZGFwE5ImpeQH5u0djEl'
|
||||
expiration: 86400000
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ server:
|
|||
|
||||
# application.yml (公用配置)
|
||||
spring:
|
||||
mvc:
|
||||
path match:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: my-app
|
||||
jackson:
|
||||
|
|
@ -15,6 +18,9 @@ spring:
|
|||
|
||||
app:
|
||||
env-name: 'test'
|
||||
jwt:
|
||||
secret: 'ylZS9cQ40nBvcZxJKu9zCIDt096BpZbm'
|
||||
expiration: 86400000
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ server:
|
|||
|
||||
# application.yml (公用配置)
|
||||
spring:
|
||||
mvc:
|
||||
path match:
|
||||
matching-strategy: ant_path_matcher
|
||||
application:
|
||||
name: my-app
|
||||
jackson:
|
||||
|
|
@ -18,6 +21,9 @@ spring:
|
|||
|
||||
app:
|
||||
env-name: 'dev'
|
||||
jwt:
|
||||
secret: 'mwsK9Ol9Ni2IyTvcdgFDVBxatw8QWx2o'
|
||||
expiration: 86400000
|
||||
|
||||
|
||||
springdoc:
|
||||
|
|
|
|||
Loading…
Reference in New Issue