若微服务数量多,如果每个服务都改动,
工作量大
,则可以只在网关和用户中心进行改动,也是可以实现服务之间的跳转。
这种方式可以通过在网关服务中生成和验证 Sa-Token,并将其与现有的 Token关联存储在 Redis 中。用户中心提供额外的接口来验证和生成新的 Sa-Token。
这样只需要改造网关和用户服务
,其他服务保持现状,即可实现各个应用之间的跳转
实现方案
为了实现这个目标,同时尽量减少对现有系统的改动,可以在网关和用户中心进行必要的改动,以确保用户在登录后能够在多个系统中无缝访问。
下面是详细的方案和步骤:
一、用户中心改造
用户中心需要提供两个接口:
- 登录接口:用户登录并生成原有业务系统的 Token。
- Token 验证和 Sa-Token 生成接口:验证原有 Token 并生成 Sa-Token。
1. 登录接口
@RestController
@RequestMapping("/auth")
public class AuthController {
@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@RequestParam String username, @RequestParam String password) {
Map<String, Object> result = new HashMap<>();
// 进行用户名和密码校验
if (checkCredentials(username, password)) {
String originalToken = generateOriginalToken(username); // 生成原有业务系统的 Token
result.put("originalToken", originalToken);
return ResponseEntity.ok(result);
}
result.put("error", "登录失败");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
}
private boolean checkCredentials(String username, String password) {
// 检查用户名和密码逻辑
return true; // 假设校验成功
}
private String generateOriginalToken(String username) {
// 生成原有业务系统的 Token
return "original-token";
}
}
2. Token 验证和 Sa-Token 生成接口
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/check")
public ResponseEntity<SaResult> checkToken(@RequestParam String token) {
// 验证原有 Token
if (validateOriginalToken(token)) {
String userId = getUserIdFromOriginalToken(token);
StpUtil.login(userId);
String saToken = StpUtil.getTokenValue();
// 存储原有 Token 和 Sa-Token 的映射关系
redisTemplate.opsForValue().set("sa-token:" + token, saToken);
redisTemplate.opsForValue().set("original-token:" + saToken, token);
return ResponseEntity.ok(SaResult.ok("Token 有效").set("saToken", saToken));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(SaResult.error("Token 无效"));
}
private boolean validateOriginalToken(String token) {
// 验证原有业务系统的 Token
return true; // 假设校验成功
}
private String getUserIdFromOriginalToken(String token) {
// 从原有业务系统的 Token 中解析出用户ID
return "parsed-user-id";
}
}
二、网关服务改造
网关服务需要在所有请求到达后端服务前,进行原有 Token 的验证和 Sa-Token 的生成和验证。
1. 引入 Redis 依赖
确保网关服务的 pom.xml
文件中包含 Redis 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置 Redis
在 application.yml
文件中配置 Redis 连接信息:
spring:
redis:
host: localhost
port: 6379
password: ""
3. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String originalToken = exchange.getRequest().getHeaders().getFirst("Authorization");
if (originalToken != null) {
String saToken = redisTemplate.opsForValue().get("sa-token:" + originalToken);
if (saToken == null) {
// 调用用户中心的接口生成 Sa-Token
saToken = generateSaToken(originalToken);
if (saToken == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
exchange.getRequest().mutate().header("Sa-Token", saToken).build();
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
private String generateSaToken(String originalToken) {
// 调用用户中心的接口生成 Sa-Token
String url = "http://user-center/auth/check?token=" + originalToken;
ResponseEntity<SaResult> response = restTemplate.getForEntity(url, SaResult.class);
if (response.getStatusCode() == HttpStatus.OK && response.getBody().getCode() == 200) {
return response.getBody().get("saToken").toString();
}
return null;
}
@Override
public int getOrder() {
return -100;
}
}
三、工作流程
-
用户登录:
- 用户通过用户中心的登录接口进行登录,用户中心生成原有业务系统的 Token 并返回给前端。
-
请求网关:
- 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
-
网关处理:
- 网关拦截请求,从请求头中获取原有的 Token。
- 网关调用用户中心的 Token 验证接口来验证原有 Token 并生成 Sa-Token。
- 将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
- 将 Sa-Token 添加到请求头中,转发请求到后端服务。
-
后端服务:
- 后端服务可以继续保持现有的 Token 逻辑,不需要改动。所有的 Token 验证和生成逻辑都在网关和用户中心处理。
模块详细介绍
在上述方案中,实现单点登录的关键步骤包括:
- 用户中心提供接口验证原有 Token 并生成 Sa-Token:通过
/auth/check
接口,用户中心接收原有业务系统的 Token,验证其有效性并生成新的 Sa-Token。 - 网关服务拦截所有请求,验证和生成 Sa-Token:网关服务在接收到请求时,首先检查请求头中的原有 Token,并调用用户中心的
/auth/check
接口来验证 Token 并生成 Sa-Token,将生成的 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。
这些步骤确保了用户在登录后,可以使用原有 Token 进行验证,并通过 Sa-Token 进行单点登录的验证,从而实现了单点登录的效果。
具体的实现过程
-
用户登录生成原有业务系统的 Token:
- 用户通过用户中心的登录接口进行登录。
- 用户中心生成原有业务系统的 Token 并返回给前端。
-
网关服务处理用户请求:
- 前端在请求其他服务时,将原有的 Token 添加到请求头中,发送请求到网关。
- 网关服务拦截请求,获取请求头中的原有 Token。
-
网关服务验证和生成 Sa-Token:
- 网关服务调用用户中心的
/auth/check
接口,验证原有 Token 的有效性。 - 用户中心验证原有 Token 有效后,生成 Sa-Token 并返回给网关服务。
- 网关服务将原有 Token 和 Sa-Token 的映射关系存储在 Redis 中。
- 网关服务将生成的 Sa-Token 添加到请求头中,转发请求到后端服务。
- 网关服务调用用户中心的
-
后端服务处理请求:
- 后端服务可以继续保持现有的 Token 验证逻辑,不需要改动。
- 所有的 Token 验证和生成逻辑都在网关和用户中心处理。
实现细节
1. 用户中心新增 Token 验证和生成 Sa-Token 接口
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/check")
public ResponseEntity<SaResult> checkToken(@RequestParam String token) {
// 验证原有 Token
if (validateOriginalToken(token)) {
String userId = getUserIdFromOriginalToken(token);
StpUtil.login(userId);
String saToken = StpUtil.getTokenValue();
// 存储原有 Token 和 Sa-Token 的映射关系
redisTemplate.opsForValue().set("sa-token:" + token, saToken);
redisTemplate.opsForValue().set("original-token:" + saToken, token);
return ResponseEntity.ok(SaResult.ok("Token 有效").set("saToken", saToken));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(SaResult.error("Token 无效"));
}
private boolean validateOriginalToken(String token) {
// 验证原有业务系统的 Token
return true; // 假设校验成功
}
private String getUserIdFromOriginalToken(String token) {
// 从原有业务系统的 Token 中解析出用户ID
return "parsed-user-id";
}
}
2. 网关服务 Token 校验过滤器
@Component
public class TokenFilter implements GlobalFilter, Ordered {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String originalToken = exchange.getRequest().getHeaders().getFirst("Authorization");
if (originalToken != null) {
String saToken = redisTemplate.opsForValue().get("sa-token:" + originalToken);
if (saToken == null) {
// 调用用户中心的接口生成 Sa-Token
saToken = generateSaToken(originalToken);
if (saToken == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
exchange.getRequest().mutate().header("Sa-Token", saToken).build();
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
private String generateSaToken(String originalToken) {
// 调用用户中心的接口生成 Sa-Token
String url = "http://user-center/auth/check?token=" + originalToken;
ResponseEntity<SaResult> response = restTemplate.getForEntity(url, SaResult.class);
if (response.getStatusCode() == HttpStatus.OK && response.getBody().getCode() == 200) {
return response.getBody().get("saToken").toString();
}
return null;
}
@Override
public int getOrder() {
return -100;
}
}
工作流程总结
- 用户登录并获取原有业务系统的 Token。
- 用户在请求服务时,携带原有 Token 发送请求到网关。
- 网关验证原有 Token,并通过用户中心生成 Sa-Token。
- 网关将 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。
- 后端服务保持现有逻辑不变,网关和用户中心负责所有的 Token 验证和生成。
通过以上步骤,达成了在现有系统中实现的目标,同时最大限度地减少了对现有系统的改动。
五、总结
通过上述详细的改造步骤和代码示例,可以在不改动后端服务的情况下,实现单点登录。所有的 Token
验证和生成逻辑都集中在网关和用户中心,实现了 Token 的统一管理和验证。这样既实现了项目之间跳转的目标,又最大限度地减少了对现有系统的改造。