本文目录导读:

我来为您提供一个完整的Java实现SSO单点登录的案例,这个案例将涵盖核心概念、实现方式和完整代码。
SSO单点登录架构
核心组件:
- 认证中心(SSO Server):负责用户认证和票据管理
- 业务系统(Client):需要登录的业务系统
- 认证协议:自定义实现的简单SSO协议
核心实现代码
认证中心实现(SSO Server)
// TokenManager.java - 令牌管理器
@Component
public class TokenManager {
// 使用ConcurrentHashMap存储有效token
private static ConcurrentHashMap<String, Token> tokenStore = new ConcurrentHashMap<>();
// 使用Set存储已登录用户
private static ConcurrentHashMap<String, Set<String>> userTokens = new ConcurrentHashMap<>();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 生成token
public String generateToken(String username) {
String token = UUID.randomUUID().toString().replace("-", "");
Token tokenObj = new Token();
tokenObj.setToken(token);
tokenObj.setUsername(username);
tokenObj.setCreateTime(new Date());
tokenObj.setExpireTime(new Date(System.currentTimeMillis() + 30 * 60 * 1000)); // 30分钟过期
// 保存到Redis
redisTemplate.opsForValue().set("token:" + token, tokenObj, 30, TimeUnit.MINUTES);
// 记录用户的所有token
Set<String> tokens = userTokens.getOrDefault(username, new HashSet<>());
tokens.add(token);
userTokens.put(username, tokens);
return token;
}
// 校验token
public boolean validateToken(String token) {
Token tokenObj = (Token) redisTemplate.opsForValue().get("token:" + token);
if (tokenObj == null) {
return false;
}
// 检查是否过期
return tokenObj.getExpireTime().getTime() > System.currentTimeMillis();
}
// 获取token对应的用户名
public String getUsername(String token) {
Token tokenObj = (Token) redisTemplate.opsForValue().get("token:" + token);
return tokenObj != null ? tokenObj.getUsername() : null;
}
// 删除token(退出登录)
public void removeToken(String token) {
Token tokenObj = (Token) redisTemplate.opsForValue().get("token:" + token);
if (tokenObj != null) {
redisTemplate.delete("token:" + token);
if (userTokens.containsKey(tokenObj.getUsername())) {
userTokens.get(tokenObj.getUsername()).remove(token);
}
}
}
}
// AuthController.java - 认证中心控制器
@RestController
@RequestMapping("/sso")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private TokenManager tokenManager;
/**
* 登录接口
*/
@PostMapping("/login")
public Result login(@RequestParam String username,
@RequestParam String password,
@RequestParam(required = false) String redirectUrl) {
// 验证用户
User user = userService.validateUser(username, password);
if (user != null) {
// 生成token
String token = tokenManager.generateToken(username);
// 如果提供了回调地址,重定向回业务系统
if (redirectUrl != null && !redirectUrl.isEmpty()) {
return Result.success(new LoginResponse(token, redirectUrl));
}
return Result.success(new LoginResponse(token, null));
}
return Result.error("用户名或密码错误");
}
/**
* 校验token接口
*/
@GetMapping("/validate")
public Result validateToken(String token) {
if (tokenManager.validateToken(token)) {
String username = tokenManager.getUsername(token);
Map<String, Object> data = new HashMap<>();
data.put("valid", true);
data.put("username", username);
return Result.success(data);
}
return Result.error("token无效");
}
/**
* 退出登录接口
*/
@PostMapping("/logout")
public Result logout(@RequestParam String token) {
tokenManager.removeToken(token);
return Result.success("退出成功");
}
}
业务系统实现(Client)
// SSOFilter.java - SSO过滤器(用于拦截需要登录的请求)
@Component
public class SSOFilter implements Filter {
@Autowired
private SSOClientService ssoClientService;
@Value("${sso.server.url}")
private String ssoServerUrl;
@Value("${sso.client.url}")
private String clientBaseUrl;
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();
// 检查session中是否有token
String token = (String) session.getAttribute("sso_token");
if (token == null) {
// 从请求参数中获取token(可能来自SSO重定向)
token = req.getParameter("token");
if (token != null) {
// 验证token是否有效
if (ssoClientService.validateToken(token)) {
session.setAttribute("sso_token", token);
session.setAttribute("sso_username",
ssoClientService.getUsername(token));
}
}
}
if (token != null && session.getAttribute("sso_token") != null) {
// 已登录,放行
chain.doFilter(request, response);
} else {
// 未登录,重定向到SSO服务器
String redirectUrl = clientBaseUrl + req.getRequestURI();
String ssoLoginUrl = ssoServerUrl + "/sso/login?redirectUrl=" +
URLEncoder.encode(redirectUrl, "UTF-8");
resp.sendRedirect(ssoLoginUrl);
}
}
}
// SSOClientService.java - SSO客户端服务
@Service
public class SSOClientService {
@Value("${sso.server.url}")
private String ssoServerUrl;
private RestTemplate restTemplate = new RestTemplate();
/**
* 校验token是否有效
*/
public boolean validateToken(String token) {
String url = ssoServerUrl + "/sso/validate?token=" + token;
Result result = restTemplate.getForObject(url, Result.class);
return result != null && result.isSuccess();
}
/**
* 获取token对应的用户名
*/
public String getUsername(String token) {
String url = ssoServerUrl + "/sso/validate?token=" + token;
Result result = restTemplate.getForObject(url, Result.class);
if (result != null && result.isSuccess()) {
Map<String, Object> data = result.getData();
return (String) data.get("username");
}
return null;
}
/**
* 退出登录
*/
public void logout(String token) {
String url = ssoServerUrl + "/sso/logout?token=" + token;
restTemplate.postForObject(url, null, Result.class);
}
}
配置类
// SSOClientConfig.java - 客户端配置
@Configuration
public class SSOClientConfig {
@Bean
public FilterRegistrationBean ssoFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SSOFilter());
registration.addUrlPatterns("/business/*"); // 需要SSO保护的路径
registration.setOrder(1);
return registration;
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
// SSOServerApplication.java - 认证中心启动类
@SpringBootApplication
@EnableCaching
public class SSOServerApplication {
public static void main(String[] args) {
SpringApplication.run(SSOServerApplication.class, args);
}
}
业务系统控制器
// BusinessController.java - 业务系统
@RestController
@RequestMapping("/business")
public class BusinessController {
@Value("${sso.server.url}")
private String ssoServerUrl;
/**
* 业务方法 - 需要登录才能访问
*/
@GetMapping("/data")
public Result getBusinessData(HttpSession session) {
String username = (String) session.getAttribute("sso_username");
if (username == null) {
return Result.error("未登录");
}
// 模拟业务数据
Map<String, Object> data = new HashMap<>();
data.put("username", username);
data.put("businessData", "这里是业务数据");
data.put("currentTime", new Date());
return Result.success(data);
}
/**
* 退出登录
*/
@GetMapping("/logout")
public Result logout(HttpSession session,
HttpServletRequest request) {
String token = (String) session.getAttribute("sso_token");
if (token != null) {
// 调用SSO服务器注销
String url = ssoServerUrl + "/sso/logout?token=" + token;
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(url, null, Result.class);
session.invalidate();
}
return Result.success("退出成功");
}
}
数据结构定义
// Result.java - 统一返回结果
public class Result implements Serializable {
private boolean success;
private String message;
private Object data;
public static Result success(Object data) {
Result result = new Result();
result.setSuccess(true);
result.setMessage("成功");
result.setData(data);
return result;
}
public static Result error(String message) {
Result result = new Result();
result.setSuccess(false);
result.setMessage(message);
return result;
}
// getter/setter省略
}
// Token.java - Token对象
@Data
public class Token implements Serializable {
private String token;
private String username;
private Date createTime;
private Date expireTime;
public boolean isExpired() {
return expireTime.getTime() < System.currentTimeMillis();
}
}
// User.java - 用户实体
@Data
public class User implements Serializable {
private Long id;
private String username;
private String password;
private String email;
private String role;
// 构造函数、getter/setter省略
}
配置文件
# application.yml - 业务系统配置
server:
port: 8081
spring:
application:
name: sso-client
# SSO配置
sso:
server:
url: http://localhost:8080 # SSO认证中心地址
client:
url: http://localhost:8081 # 当前业务系统地址
---
# application.yml - 认证中心配置
server:
port: 8080
spring:
application:
name: sso-server
redis:
host: localhost
port: 6379
使用说明
启动认证中心
# 启动SSO Server (端口8080) mvn spring-boot:run
配置业务系统
- 复制SSOClient代码到你的业务系统中
- 配置application.yml中的SSO服务器地址
- 启动业务系统(端口8081)
测试流程
# 测试登录
curl -X POST "http://localhost:8080/sso/login" \
-d "username=admin&password=123456"
# 测试访问受保护资源(未登录会被重定向到SSO)
curl "http://localhost:8081/business/data"
# 使用token访问
curl "http://localhost:8081/business/data?token=xxxxx"
核心特性
- 集中认证:所有登录都在认证中心完成
- Token管理:使用Redis存储和管理Token
- 会话共享:多个系统共享登录状态
- 单点登出:一次登出,所有系统失效
- 安全可靠:Token过期时间控制,防止恶意攻击
扩展建议
- 使用标准协议:生产环境建议使用OAuth2、CAS或SAML
- 增加安全措施:HTTPS传输、Token加密、刷新Token机制
- 分布式部署:使用Redis集群存储Token
- 监控与日志:记录所有认证事件,便于问题排查
- 权限控制:结合RBAC权限模型,实现细粒度访问控制
这个案例提供了一个完整的SSO实现基础,您可以根据实际需求进行扩展和优化。