1. 从零开始搭建微服务架构1.0(登录模块)
Authorizetion -> Bearer Token -> 复制登录时的"accessToken"的值。子模块:右键父模块 -> 新建 -> 模块 -> 直接创建一个SpringBoot项目(其他不选)右键父模块 -> 新建 -> 模块 -> 直接创建一个SpringBoot项目(其他不选)Body -> raw -> 添加以下内容(登录时"refreshToken"的值)将该token更换
·
1.1 必备环境
1.1.1 Nacos
1.1.1.1 下载
下载地址:https://nacos.io/download/nacos-server/
- 下载最新压缩包

- 安装好后,内容如下

1.1.1.2 打开
进入bin目录,从此打开cmd,输入以下命令
startup.cmd -m standalone

- 访问http://localhost:8848/nacos,能出现以下界面表示成功

1.1.2 redis
1.1.2.1 下载
- Windows版下载地址:https://github.com/microsoftarchive/redis/releases
- Linux版下载地址: https://download.redis.io/releases/
1.1.2.2 打开
我安装的是windows版,目录如下。
双击redis-server.exe命令即可成功打开

- 出现以下界面表示成功

1.2 创建基础工程
1.2.1 创建父子模块
父模块:新建一个Java项目,构建系统选择maven
子模块:右键父模块 -> 新建 -> 模块 -> 直接创建一个SpringBoot项目(其他不选)


最终目录结构如下:

1.2.2 配置父子模块
1.2.2.1 父模块
- 补充pom.xml的properties标签
版本需一一对应,版本说明
<spring.cloud.version>2023.0.1</spring.cloud.version>
<spring.boot.version>3.2.4</spring.boot.version>
<spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version>
- 添加固定依赖
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 添加modules标签
<modules>
<module>user</module>
<module>gateway</module>
</modules>
1.2.2.2 子模块
- 根据父模块更改pom.xml的parent标签

- 删除一些依赖(也可以不删)

- 在pom.xml添加启动依赖
user子模块
<dependencies>
<!--......原有依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
gateway子模块
<dependencies>
<!--......原有依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
1.2.3 运行子模块
- 在子模块启动类上加入@EnableDiscoveryClient注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserApplication {
//......原有代码
}
- 在子模块的application.properties(src/main/resources/application.properties)添加不同的端口号
server.port=8081
- 进入nacos的bin目录,从该目录打开cmd并输入以下命令打开nacos
startup.cmd -m standalon
- 运行子模块后,访问http://localhost:8848/nacos,出现以下界面表示成功!

1.3 网关和禁止绕过网关
1.3.1 gateway模块
1.3.1.1 网关转发
- 在resources目录下创建application.yml文件,在其中添加一下nacos配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos地址
- 定义routes,为user子模块配置路由规则。
每个规则都应包含id、uri、predicates和filters
spring:
cloud:
gateway:
routes: # 路由规则(列表项,下方每个路由用"- "开头)
# 转发到user服务
- id: route-user # 路由唯一标识(列表项,必须以"- "开头)
uri: lb://user # 服务名(Nacos中注册的user服务名,确保Nacos中存在)
predicates: # 匹配规则(列表项,子项用"- "开头)
- Path=/api/user/** # 路径匹配(注意Path前的"- ")
filters: # 过滤规则(列表项,子项用"- "开头)
- StripPrefix=2
1.3.1.2 网关设置请求体
在配置文件中添加一个全局的pre过滤器。
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=X-From-Gateway, true
#......以下配置保持不变
1.3.2 user子模块
- 在resources目录下创建application.yml文件,在其中添加一下nacos配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos地址(保持不变)
ip: 127.0.0.1 # 强制注册为本地回环地址(关键!)
port: 8081 # 可选:显式指定端口(确保与user服务端口一致)
- 创建一个Usercontroller,在其中添加一个测试接口
package cn.edu.guet.user.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 测试
* @return
*/
@GetMapping("/ceShi")
public String ceShi(){
return "ceshi";
}
}
- 创建拦截器
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class GatewayInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String fromGatewayHeader = request.getHeader("X-From-Gateway");
if (!"true".equals(fromGatewayHeader)) {
// 如果没有这个请求头,或者值不正确,说明请求不是从网关来的
// 拒绝请求,返回403 Forbidden
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
// 校验通过,继续处理请求
return true;
}
}
- 注册拦截器
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final GatewayInterceptor gatewayInterceptor;
public WebConfig(GatewayInterceptor gatewayInterceptor) {
this.gatewayInterceptor = gatewayInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,并指定拦截所有路径
registry.addInterceptor(gatewayInterceptor).addPathPatterns("/**");
}
}
1.3.2 postman测试
- 直接访问测试接口(不通过网关)

- 通过网关测试接口

1.4 JWT Token生成与认证
1.4.1 共享认证模块
1.4.1.1 创建auth-common模块并配置依赖
- 创建auth-common
右键父模块 -> 新建 -> 模块 -> 直接创建一个SpringBoot项目(其他不选)
- 补充父模块的modules
<modules>
<module>auth-common</module>
<!--......以下配置不变-->
</modules>
- 配置auth-common依赖

- 添加以下依赖
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
1.4.1.2 创建JWT配置类和工具类
- JwtProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "framework.jwt")
@Data
public class JwtProperties {
private String secretKey;
private Integer accessTokenExpireMinutes;
private Integer refreshTokenExpireDays;
}
- JwtUtil.java
import cn.edu.guet.authcommon.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Component
public class JwtUtil {
private final JwtProperties jwtProperties;
private final Key key;
@Autowired
public JwtUtil(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
this.key = Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes());
}
/**
* 生成 Access Token
*/
public String generateAccessToken(Map<String, Object> claims) {
claims.put("token_type", "access");
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date expireDate = new Date(nowMillis + (long)jwtProperties.getAccessTokenExpireMinutes() * 60 * 1000);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expireDate)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
/**
* 生成 Refresh Token
*/
public String generateRefreshToken(Map<String, Object> claims) {
claims.put("token_type", "refresh");
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Date expireDate = new Date(nowMillis + (long)jwtProperties.getRefreshTokenExpireDays() * 24 * 60 * 60 * 1000);
return Jwts.builder()
.setClaims(claims)
.setId(UUID.randomUUID().toString())
.setIssuedAt(now)
.setExpiration(expireDate)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
/**
* 解析 Token
*/
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
// 新增一个获取属性的方法,用于登录接口返回有效期
public JwtProperties getJwtProperties() {
return jwtProperties;
}
}
1.4.2 gateway模块
14.2.1 引入共享认证模块
- 导入auth-common模块和lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

- 添加yml配置
framework:
jwt:
# 密钥:一个32位或更长的随机字符串,用于对Token进行签名和验证。
# 生产环境中务必使用一个复杂的随机密钥,并妥善保管。
secretKey: sY6pBq8nE0yX3zH9vJ5cK2mD1fG7aL4uT8wP3oR0qI2
# accessToken的有效期,单位为分钟
accessTokenExpireMinutes: 15
# refreshToken的有效期,单位为天
refreshTokenExpireDays: 7
1.4.2.2 生成白名单
- 创建网关配置类AuthGatewayProperties.java
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "framework.auth")
@Data
public class AuthGatewayProperties {
private List<String> whiteList;
}
- 添加yml配置
framework:
#......原有配置保持不变
auth:
# 白名单路径,这些路径不需要认证
whiteList:
- /api/user/user/login
- /api/user/user/register
1.4.2.3 实现Token黑名单(reids)
- 添加redis的响应式依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
- 配置redis
spring:
# ......你的原有配置
data:
redis:
host: localhost
port: 6379
#密码我没设置过,默认为空
password:
database: 15
- 实现黑名单服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.time.Duration;
@Service
public class AuthBlacklistService {
@Autowired
private ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
/**
* 将 Token 加入黑名单,并设置过期时间。
* @param token 要加入黑名单的 Token。
* @param expiration Token 的剩余有效期。
*/
public Mono<Boolean> blacklistToken(String token, Duration expiration) {
String key = "blacklist:" + token;
return reactiveRedisTemplate.opsForValue().set(key, "invalid", expiration);
}
/**
* 检查 Token 是否在黑名单中。
* @param token 要检查的 Token。
* @return 如果 Token 在黑名单中,返回 true,否则返回 false。
*/
public Mono<Boolean> isTokenBlacklisted(String token) {
String key = "blacklist:" + token;
return reactiveRedisTemplate.hasKey(key);
}
}
- 增加一个内部接口
import cn.edu.guet.authcommon.util.JwtUtil;
import cn.edu.guet.gateway.service.AuthBlacklistService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
// GatewayBlacklistController.java (在 gateway 模块中创建)
@RestController
@RequestMapping("/internal")
public class GatewayBlacklistController {
@Autowired
private AuthBlacklistService authBlacklistService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/blacklist")
public Mono<ResponseEntity<Map<String, String>>> blacklist(@RequestBody Map<String, String> payload) {
String token = payload.get("token");
if (token == null) {
return Mono.just(ResponseEntity.badRequest().body(Map.of("message", "缺少令牌")));
}
try {
Claims claims = jwtUtil.parseToken(token);
Date expiration = claims.getExpiration();
Duration duration = Duration.between(Instant.now(), expiration.toInstant());
// 将 Token 加入黑名单,并设置剩余有效期
return authBlacklistService.blacklistToken(token, duration)
.thenReturn(ResponseEntity.ok(Map.of("message", "Token成功列入黑名单.")));
} catch (Exception e) {
return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of("message", "令牌无效")));
}
}
}
1.4.2.4 创建全局过滤器
- yml配置中定义全局过滤器
spring:
cloud:
gateway:
forwarded:
enabled: true
# 定义全局过滤器
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
#......原有配置保持不变
- 创建全局过滤器AuthFilter.java
package cn.edu.guet.gateway.filter;
import cn.edu.guet.authcommon.util.JwtUtil;
import cn.edu.guet.gateway.properties.AuthGatewayProperties; // 导入新创建的配置类
import cn.edu.guet.gateway.service.AuthBlacklistService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Autowired
private JwtUtil jwtUtil;
// 注入创建的配置类
@Autowired
private AuthGatewayProperties authGatewayProperties;
// 注入新的黑名单服务
@Autowired
private AuthBlacklistService authBlacklistService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 1. 从配置类中获取白名单列表,并检查当前路径是否在其中
if (authGatewayProperties.getWhiteList().contains(path)) {
return chain.filter(exchange);
}
// 2. 获取请求头中的Access Token
String token = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (token == null || !token.startsWith("Bearer ")) {
return unauthorizedResponse(exchange.getResponse(), "访问令牌缺失或无效");
}
try {
// 3. 解析和验证 Token
String tokenValue = token.substring(7);
Claims claims = jwtUtil.parseToken(tokenValue);
//4. 验证 Token 类型
if (!"access".equals(claims.get("token_type", String.class))) {
return unauthorizedResponse(exchange.getResponse(), "令牌类型无效");
}
// 6.首先检查 Token 是否在黑名单中**
return authBlacklistService.isTokenBlacklisted(tokenValue)
.flatMap(isBlacklisted -> {
if (isBlacklisted) {
return unauthorizedResponse(exchange.getResponse(), "Token 已失效。");
}
// 7. 如果不在黑名单,将用户信息透传到下游服务,继续验证和放行
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", claims.get("userId", Long.class).toString())
.header("X-User-Role", claims.get("role", String.class))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
} catch (ExpiredJwtException e) {
return unauthorizedResponse(exchange.getResponse(), "令牌已过期");
} catch (Exception e) {
return unauthorizedResponse(exchange.getResponse(), "令牌无效");
}
}
private Mono<Void> unauthorizedResponse(ServerHttpResponse response, String message) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
@Override
public int getOrder() {
return -100; // 确保在所有路由之前执行
}
}
1.4.2.4 更新启动类
启动类加入新的注解
@ComponentScan(basePackages = {"cn.edu.guet.gateway", "cn.edu.guet.authcommon"})

1.4.3 user模块
1.4.3.1 引入共享认证模块
- 导入auth-common模块和lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

- 添加yml配置
framework:
jwt:
# 密钥:一个32位或更长的随机字符串,用于对Token进行签名和验证。
# 生产环境中务必使用一个复杂的随机密钥,并妥善保管。
secretKey: sY6pBq8nE0yX3zH9vJ5cK2mD1fG7aL4uT8wP3oR0qI2
# accessToken的有效期,单位为分钟
accessTokenExpireMinutes: 15
# refreshToken的有效期,单位为天
refreshTokenExpireDays: 7
1.4.3.2 创建内部测试所用类
- controller
package cn.edu.guet.user.controller;
import cn.edu.guet.authcommon.util.JwtUtil;
import cn.edu.guet.user.domain.User;
import cn.edu.guet.user.dto.LoginDTO;
import cn.edu.guet.user.service.UserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
private final JwtUtil jwtUtil;
private final RestTemplate restTemplate = new RestTemplate();
@Autowired
public UserController(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestBody LoginDTO loginDTO) {
// 验证用户名和密码
User user = userService.verifyUser(loginDTO.getUsername(), loginDTO.getPassword());
if (user == null) {
// 验证失败,返回 401 Unauthorized
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", "用户名或密码错误"));
}
// 验证成功,生成 JWT Token,包含用户ID和角色
Map<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
claims.put("role", user.getRole());
String accessToken = jwtUtil.generateAccessToken(claims);
String refreshToken = jwtUtil.generateRefreshToken(claims);
return ResponseEntity.ok(Map.of(
"accessToken", accessToken,
"refreshToken", refreshToken,
"accessTokenExpireIn", jwtUtil.getJwtProperties().getAccessTokenExpireMinutes() * 60,
"refreshTokenExpireIn", jwtUtil.getJwtProperties().getRefreshTokenExpireDays() * 24 * 60 * 60
));
}
@PostMapping("/refresh")
public ResponseEntity<Map<String, Object>> refreshToken(@RequestBody Map<String, String> payload) {
String refreshToken = payload.get("refreshToken");
if (refreshToken == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of("message", "缺少刷新令牌"));
}
try {
Claims claims = jwtUtil.parseToken(refreshToken);
// 验证 Token 类型是否为 'refresh'
if (!"refresh".equals(claims.get("token_type", String.class))) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", "令牌类型无效"));
}
// 验证通过,生成新的 Access Token
String newAccessToken = jwtUtil.generateAccessToken(claims);
return ResponseEntity.ok(Map.of(
"accessToken", newAccessToken,
"accessTokenExpireIn", jwtUtil.getJwtProperties().getAccessTokenExpireMinutes() * 60
));
} catch (ExpiredJwtException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of("message", "刷新令牌已过期,请重新登录"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", "刷新令牌无效"));
}
}
@GetMapping("/logout")
public ResponseEntity<Map<String, String>> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader) {
String token = authorizationHeader.substring(7);
// 构造请求体,将 token 传递给网关的内部接口
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(Map.of("token", token), headers);
try {
// 调用网关的内部黑名单接口
// 这里的 URL 应该是网关的实际内部地址和端口
restTemplate.postForEntity("http://localhost:8888/internal/blacklist", requestEntity, String.class);
return ResponseEntity.ok(Map.of("message", "注销成功,令牌无效"));
} catch (Exception e) {
// 如果内部调用失败,不影响用户的登出体验,仅记录错误
return ResponseEntity.ok(Map.of("message", "注销成功,但令牌失效失败"));
}
}
}
- domain
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private Long id;
private String username;
private String password; // 数据库中存储的是哈希值,而不是明文
private String role;
}
- dto
import lombok.Data;
@Data
public class LoginDTO {
private String username;
private String password;
}
- service接口、serviceImpl实现类
import cn.edu.guet.user.domain.User;
public interface UserService {
User verifyUser(String username, String password);
}
package cn.edu.guet.user.service.impl;
import cn.edu.guet.user.domain.User;
import cn.edu.guet.user.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public User verifyUser(String username, String password) {
// 演示用的占位符逻辑:
if ("admin".equals(username) && "123456".equals(password)) {
User user = new User();
user.setId(1L);
user.setUsername("admin");
user.setRole("admin");
return user;
}
return null;
}
}
1.4.3.3 更新启动类
启动类加入新的注解
@ComponentScan(basePackages = {"cn.edu.guet.user", "cn.edu.guet.authcommon"})

1.4.4 postman测试
1.4.4.1 登录验证
- 传参
Body -> raw -> 添加以下内容
{
"username": "admin",
"password": "123456"
}
- 发送请求

1.4.4.2 刷新验证
- 传入请求头
Authorizetion -> Bearer Token -> 复制登录时的"accessToken"的值

- 传参
Body -> raw -> 添加以下内容(登录时"refreshToken"的值)
{
"refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYWRtaW4iLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsInVzZXJJZCI6MSwianRpIjoiZTZjZWQ1YTMtNTEzOS00YzY1LWE4ZDItYTJlMTAxNTIxYTFiIiwiaWF0IjoxNzU2NzE1MjM0LCJleHAiOjE3NTczMjAwMzR9.vRsvoNe-QbzLMUzOMKnCw0xrnKpdP8S15QuUg6FTzH4"
}
- 发送请求

- 验证该accessToken是否有用
将该token更换一下请求头中的token,然后再次发送请求,能出结果表示成功

1.4.4.3 登出验证
- 首先进行登录

- 将该accessToken传入,进行登出,此刻能正常响应

- 再次发送这个请求,会返回401

更多推荐

所有评论(0)