Java实现SSO单点登录案例

wen java案例 4

本文目录导读:

Java实现SSO单点登录案例

  1. SSO单点登录架构
  2. 核心实现代码
  3. 使用说明
  4. 核心特性
  5. 扩展建议

我来为您提供一个完整的Java实现SSO单点登录的案例,这个案例将涵盖核心概念、实现方式和完整代码。

SSO单点登录架构

核心组件:

  1. 认证中心(SSO Server):负责用户认证和票据管理
  2. 业务系统(Client):需要登录的业务系统
  3. 认证协议:自定义实现的简单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"

核心特性

  1. 集中认证:所有登录都在认证中心完成
  2. Token管理:使用Redis存储和管理Token
  3. 会话共享:多个系统共享登录状态
  4. 单点登出:一次登出,所有系统失效
  5. 安全可靠:Token过期时间控制,防止恶意攻击

扩展建议

  1. 使用标准协议:生产环境建议使用OAuth2、CAS或SAML
  2. 增加安全措施:HTTPS传输、Token加密、刷新Token机制
  3. 分布式部署:使用Redis集群存储Token
  4. 监控与日志:记录所有认证事件,便于问题排查
  5. 权限控制:结合RBAC权限模型,实现细粒度访问控制

这个案例提供了一个完整的SSO实现基础,您可以根据实际需求进行扩展和优化。

抱歉,评论功能暂时关闭!