赞
踩
若微服务数量多,如果每个服务都改动,
工作量大
,则可以只在网关和用户中心进行改动,也是可以实现服务之间的跳转。
这种方式可以通过在网关服务中生成和验证 Sa-Token,并将其与现有的 Token关联存储在 Redis 中。用户中心提供额外的接口来验证和生成新的 Sa-Token。
这样只需要改造网关和用户服务
,其他服务保持现状,即可实现各个应用之间的跳转
为了实现这个目标,同时尽量减少对现有系统的改动,可以在网关和用户中心进行必要的改动,以确保用户在登录后能够在多个系统中无缝访问。
下面是详细的方案和步骤:
用户中心需要提供两个接口:
@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";
}
}
@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 的生成和验证。
确保网关服务的 pom.xml
文件中包含 Redis 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application.yml
文件中配置 Redis 连接信息:
spring:
redis:
host: localhost
port: 6379
password: ""
@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;
}
}
用户登录:
请求网关:
网关处理:
后端服务:
在上述方案中,实现单点登录的关键步骤包括:
/auth/check
接口,用户中心接收原有业务系统的 Token,验证其有效性并生成新的 Sa-Token。/auth/check
接口来验证 Token 并生成 Sa-Token,将生成的 Sa-Token 存储在 Redis 中,并添加到请求头中转发给后端服务。这些步骤确保了用户在登录后,可以使用原有 Token 进行验证,并通过 Sa-Token 进行单点登录的验证,从而实现了单点登录的效果。
用户登录生成原有业务系统的 Token:
网关服务处理用户请求:
网关服务验证和生成 Sa-Token:
/auth/check
接口,验证原有 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";
}
}
@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 的统一管理和验证。这样既实现了项目之间跳转的目标,又最大限度地减少了对现有系统的改造。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。