当前位置: 首页 > news >正文

说一下,项目中单点登录的实现原理

谈谈项目中单点登录的实现原理

一次登录,处处通行——单点登录(SSO)如何让用户体验飞起来

什么是单点登录?
想象一下这样的场景:早晨来到公司,你登录了OA系统;接着需要查看项目进度,又得登录项目管理平台;下午要申请报销,还得再次登录财务系统… 这样的重复登录体验是不是很糟糕?

单点登录(SSO) 就是为了解决这个问题而生的:用户只需登录一次,就可以访问所有相互信任的应用系统。

核心实现原理

  1. 基于Cookie的共享Session方案
    在早期项目中,我们采用基于Cookie的共享Session方案:

实现思路:

所有子系统使用同一个顶级域名
登录成功后,认证中心设置一个全局Session Cookie
其他子系统通过读取这个Cookie来验证用户身份
代码示例:

@Service
public class TraditionalSSOService {

public void login(HttpServletResponse response, String username) {// 创建全局SessionString globalSessionId = UUID.randomUUID().toString();// 存储Session信息redisTemplate.opsForValue().set("global_session:" + globalSessionId, username, 30, TimeUnit.MINUTES);// 设置全局Cookie,所有子域名都可以访问Cookie sessionCookie = new Cookie("GLOBAL_SESSION_ID", globalSessionId);sessionCookie.setDomain(".company.com");  // 设置顶级域名sessionCookie.setPath("/");sessionCookie.setMaxAge(30 * 60);  // 30分钟response.addCookie(sessionCookie);
}

}
局限性:

域名必须相同或具有父子关系
安全性较低,容易受到CSRF攻击
不适合跨域场景
2. 基于Token的现代SSO方案(主流)
现在我们普遍采用基于Token的SSO方案,核心流程如下:

2.1 登录时序图
用户访问业务系统A
→ 重定向到认证中心
→ 用户输入账号密码
→ 认证中心验证身份并生成Token
→ 重定向回业务系统A(携带Token)
→ 业务系统A向认证中心验证Token
→ 登录成功
2.2 认证中心实现
@RestController
@RequestMapping("/auth")
public class AuthCenterController {

@Autowired
private UserService userService;@Autowired
private JwtTokenProvider tokenProvider;/*** 登录接口*/
@PostMapping("/login")
public ResponseEntity<LoginResult> login(@RequestBody LoginRequest request) {// 1. 验证用户凭证User user = userService.authenticate(request.getUsername(), request.getPassword());// 2. 生成JWT TokenString token = tokenProvider.generateToken(user);// 3. 记录登录状态redisTemplate.opsForValue().set("sso_token:" + user.getId(), token, tokenProvider.getTokenValidity(), TimeUnit.SECONDS);return ResponseEntity.ok(new LoginResult(token, user));
}/*** 验证Token接口*/
@PostMapping("/verify")
public ResponseEntity<User> verifyToken(@RequestParam String token) {// 1. 验证Token有效性if (!tokenProvider.validateToken(token)) {return ResponseEntity.status(401).build();}// 2. 从Token中提取用户信息String userId = tokenProvider.getUserIdFromToken(token);// 3. 检查Token是否在服务端有记录(支持登出功能)String serverToken = redisTemplate.opsForValue().get("sso_token:" + userId);if (!token.equals(serverToken)) {return ResponseEntity.status(401).build();}User user = userService.findById(userId);return ResponseEntity.ok(user);
}/*** 登出接口*/
@PostMapping("/logout")
public ResponseEntity<?> logout(@RequestHeader("Authorization") String token) {String userId = tokenProvider.getUserIdFromToken(token.replace("Bearer ", ""));redisTemplate.delete("sso_token:" + userId);return ResponseEntity.ok().build();
}

}
2.3 JWT Token工具类
@Component
public class JwtTokenProvider {

@Value("${jwt.secret:defaultSecretKey}")
private String secretKey;@Value("${jwt.validity:3600}")
private long tokenValidityInSeconds;/*** 生成JWT Token*/
public String generateToken(User user) {Map<String, Object> claims = new HashMap<>();claims.put("userId", user.getId());claims.put("username", user.getUsername());claims.put("roles", user.getRoles());return Jwts.builder().setClaims(claims).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + tokenValidityInSeconds * 1000)).signWith(SignatureAlgorithm.HS256, secretKey).compact();
}/*** 验证Token有效性*/
public boolean validateToken(String token) {try {Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);return true;} catch (ExpiredJwtException e) {log.warn("Token已过期: {}", e.getMessage());} catch (Exception e) {log.warn("Token验证失败: {}", e.getMessage());}return false;
}/*** 从Token中提取用户ID*/
public String getUserIdFromToken(String token) {Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();return claims.get("userId", String.class);
}

}
2.4 业务系统拦截器
@Component
public class SSOInterceptor implements HandlerInterceptor {

@Autowired
private AuthClient authClient;@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 排除登录接口本身if (request.getRequestURI().contains("/login")) {return true;}// 1. 获取TokenString token = extractToken(request);if (token == null) {// 重定向到认证中心登录页redirectToLoginPage(request, response);return false;}// 2. 验证TokenUser user = authClient.verifyToken(token);if (user == null) {// Token无效,重新登录redirectToLoginPage(request, response);return false;}// 3. 将用户信息存入请求上下文UserContext.setCurrentUser(user);return true;
}private String extractToken(HttpServletRequest request) {// 从Header中获取String authHeader = request.getHeader("Authorization");if (authHeader != null && authHeader.startsWith("Bearer ")) {return authHeader.substring(7);}// 从URL参数中获取String tokenParam = request.getParameter("token");if (tokenParam != null) {return tokenParam;}// 从Cookie中获取Cookie[] cookies = request.getCookies();if (cookies != null) {for (Cookie cookie : cookies) {if ("SSO_TOKEN".equals(cookie.getName())) {return cookie.getValue();}}}return null;
}private void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response) throws IOException {String currentUrl = request.getRequestURL().toString();String queryString = request.getQueryString();if (queryString != null) {currentUrl += "?" + queryString;}String loginUrl = authClient.getAuthCenterUrl() + "/auth/login?redirect_url=" + URLEncoder.encode(currentUrl, "UTF-8");response.sendRedirect(loginUrl);
}

}
安全考虑与最佳实践

  1. Token安全策略
    @Component
    public class TokenSecurityService {

    /**

    • 生成安全的随机Token
      */
      public String generateSecureToken() {
      SecureRandom random = new SecureRandom();
      byte[] bytes = new byte[32];
      random.nextBytes(bytes);
      return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
      }

    /**

    • 设置安全Cookie
      */
      public void setSecureCookie(HttpServletResponse response,
      String name, String value, int maxAge) {
      Cookie cookie = new Cookie(name, value);
      cookie.setHttpOnly(true); // 防止XSS攻击
      cookie.setSecure(true); // 仅HTTPS传输
      cookie.setPath("/");
      cookie.setMaxAge(maxAge);
      response.addCookie(cookie);
      }
      }
  2. 防止重放攻击
    @Service
    public class ReplayAttackProtection {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**

    • 检查并记录Token使用
      */
      public boolean checkAndRecordTokenUsage(String token, String requestId) {
      String key = “token_usage:” + token + “:” + requestId;

      // 如果这个请求ID已经存在,说明是重放攻击
      Boolean result = redisTemplate.opsForValue().setIfAbsent(key, “1”, 5, TimeUnit.MINUTES);
      return Boolean.TRUE.equals(result);
      }
      }

  3. 数据库优化
    – 创建用户会话表
    CREATE TABLE user_sessions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id VARCHAR(64) NOT NULL,
    token VARCHAR(512) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    expires_at DATETIME NOT NULL,
    client_ip VARCHAR(45),
    user_agent TEXT,
    INDEX idx_user_id (user_id),
    INDEX idx_expires_at (expires_at),
    INDEX idx_token (token(64))
    );
    实际部署架构
    高可用架构设计
    客户端 → 负载均衡器 → [认证中心实例1, 认证中心实例2, …]

    [Redis集群]

    [数据库主从]
    监控与告警
    @Component
    public class SSOMonitor {

    @Autowired
    private MeterRegistry meterRegistry;

    private final Counter loginCounter;
    private final Counter tokenVerifyCounter;

    public SSOMonitor() {
    loginCounter = Counter.builder(“sso.login.requests”)
    .description(“登录请求次数”)
    .register(meterRegistry);

     tokenVerifyCounter = Counter.builder("sso.token.verify").description("Token验证次数").register(meterRegistry);
    

    }

    public void recordLogin(boolean success) {
    loginCounter.increment();
    if (!success) {
    // 记录登录失败指标
    }
    }
    }
    总结
    单点登录的实现需要综合考虑多个方面:

用户体验:无缝的登录跳转,减少用户操作
安全性:Token安全、防重放攻击、安全传输
性能:缓存策略、数据库优化、高并发处理
可扩展性:支持多系统、跨域场景
可维护性:清晰的架构、完善的监控
通过合理的架构设计和技术选型,单点登录能够显著提升用户体验,同时保证系统的安全性和稳定性。

互动思考:在你的项目中,是如何处理移动端和Web端统一的单点登录需求的?欢迎在评论区分享你的实践经验!

http://www.dtcms.com/a/558753.html

相关文章:

  • 网站建设用书建设一个网站的需求分析
  • CUDA C++编程指南(1)——简介
  • 哪做网站最好程序员做的简单的网站
  • 不同的网站 做301百度网站登录
  • 厦门做网站软件网站建设公司好吗
  • 昆明学校网站设计公司搜索引擎优化与推广技术
  • 网站建设单位哪家好设计师接单的网站
  • 便宜做网站抖音代运营广州
  • 到底什么是:对称加密/非对称加密--公钥和密钥(下)
  • 恋爱网站建设成都小程序开发公司有哪些
  • 单位网站设计制作拓者设计吧注册还要钱
  • 赣州那里有做网站的公司长沙做网站seo优化外包
  • 上海自主建站模板手机高端网站建设
  • 开发指南140-跨服务数据范围限定
  • 网站建设实践心得体会wordpress 固定导航
  • 做网站可以不写代码百度推广投诉人工电话
  • Spring AI--工具调用
  • 网站的分辨率是多少平面设计师招聘广告文案
  • x402支付协议:促AI资产从概念走向落地
  • 重庆永川网站建设苏州公司网站建设价格
  • 做网站的是什么职位wordpress 分类图片尺寸
  • LeetCode LCR 085 括号生成
  • 域名怎么建设网站中国建设招标网住建部网站
  • 龙岩网站建设哪里比较好平潭县建设局网站
  • PCB设计<囫囵吞枣学习法>: 21_PCB设计之调整丝印和添加文字
  • 可信赖的常州网站建设电影网站建设成本
  • 农药化肥网站建设seo应该如何做
  • 机械加工网站平台北京网站优化常识
  • 做网站时兼容分辨率郑州做网站和app的公司
  • 安徽元鼎建设公司网站深圳软件公司排行