Java单点登录(SSO)

一文带你了解Java单点登录(SSO)

目录

单点登录简介

什么是单点登录(SSO)

单点登录(Single Sign-On, SSO)是一种身份验证机制,允许用户使用一组凭据登录多个相关但独立的软件系统。用户只需要登录一次,就可以访问所有被授权的系统,无需重复输入用户名和密码。

SSO的核心概念

核心概念:
  身份认证: 验证用户身份的过程
  会话管理: 维护用户登录状态
  票据机制: 用于验证用户身份的凭证
  信任关系: 各系统间的互相信任机制
  单点登出: 一次登出所有系统的机制

SSO的优势

优势:
  - 用户体验: 一次登录,多处访问,提升用户体验
  - 安全性: 集中管理用户身份,降低密码泄露风险
  - 管理效率: 统一用户管理,减少维护成本
  - 合规性: 满足企业安全策略和合规要求
  - 扩展性: 易于集成新的应用系统

SSO流程简介图

基本SSO流程

用户 应用A 应用B SSO服务器 访问应用A 检查用户登录状态 未登录,重定向到登录页 输入用户名密码 验证用户身份 重定向回应用A,携带票据 验证票据 票据有效,创建本地会话 显示应用A内容 访问应用B 检查用户登录状态 用户已登录,返回用户信息 显示应用B内容 用户 应用A 应用B SSO服务器

基于Cookie的SSO流程

Cookie存在
Cookie不存在
有效
无效
成功
失败
成功
失败
用户访问应用
检查本地Cookie
验证Cookie有效性
重定向到SSO登录页
创建本地会话
用户输入凭据
SSO验证身份
设置SSO Cookie
显示错误信息
重定向回原应用
应用验证SSO Cookie
用户正常访问应用

基于Token的SSO流程

Token验证流程
Token存在
Token不存在
有效
无效
成功
失败
成功
失败
解析JWT Token
验证签名
检查过期时间
验证用户权限
用户访问应用
检查本地Token
验证Token有效性
重定向到SSO登录页
创建本地会话
用户输入凭据
SSO验证身份
生成JWT Token
显示错误信息
重定向回原应用
应用验证JWT Token
用户正常访问应用

单点登录的实现方式

1. 基于Cookie的SSO

实现原理
@Component
public class CookieBasedSSO {
  
    private static final String SSO_COOKIE_NAME = "SSO_SESSION_ID";
    private static final String SSO_DOMAIN = ".example.com";
  
    public void setSSOCookie(HttpServletResponse response, String sessionId) {
        Cookie ssoCookie = new Cookie(SSO_COOKIE_NAME, sessionId);
        ssoCookie.setDomain(SSO_DOMAIN);
        ssoCookie.setPath("/");
        ssoCookie.setHttpOnly(true);
        ssoCookie.setSecure(true); // HTTPS环境下使用
        ssoCookie.setMaxAge(3600); // 1小时过期
      
        response.addCookie(ssoCookie);
    }
  
    public String getSSOCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (SSO_COOKIE_NAME.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
}
会话验证
@Service
public class CookieSSOService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public boolean validateSSOSession(String sessionId) {
        String key = "sso:session:" + sessionId;
        Object userInfo = redisTemplate.opsForValue().get(key);
        return userInfo != null;
    }
  
    public UserInfo getUserInfo(String sessionId) {
        String key = "sso:session:" + sessionId;
        return (UserInfo) redisTemplate.opsForValue().get(key);
    }
  
    public void createSSOSession(String sessionId, UserInfo userInfo) {
        String key = "sso:session:" + sessionId;
        redisTemplate.opsForValue().set(key, userInfo, Duration.ofHours(1));
    }
}

2. 基于Token的SSO

JWT Token生成
@Service
public class JWTSSOService {
  
    @Value("${jwt.secret}")
    private String jwtSecret;
  
    @Value("${jwt.expiration}")
    private long jwtExpiration;
  
    public String generateSSOToken(UserInfo userInfo) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);
      
        return Jwts.builder()
            .setSubject(userInfo.getUsername())
            .claim("userId", userInfo.getUserId())
            .claim("roles", userInfo.getRoles())
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
  
    public Claims validateSSOToken(String token) {
        try {
            return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();
        } catch (Exception e) {
            throw new InvalidTokenException("Invalid JWT token");
        }
    }
  
    public UserInfo extractUserInfo(String token) {
        Claims claims = validateSSOToken(token);
      
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername(claims.getSubject());
        userInfo.setUserId(claims.get("userId", Long.class));
        userInfo.setRoles(claims.get("roles", List.class));
      
        return userInfo;
    }
}
Token验证中间件
@Component
public class JWTSSOInterceptor implements HandlerInterceptor {
  
    @Autowired
    private JWTSSOService jwtSSOService;
  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler) throws Exception {
      
        // 跳过不需要验证的路径
        if (isExcludedPath(request.getRequestURI())) {
            return true;
        }
      
        String token = extractToken(request);
        if (token == null) {
            redirectToLogin(request, response);
            return false;
        }
      
        try {
            UserInfo userInfo = jwtSSOService.extractUserInfo(token);
            request.setAttribute("userInfo", userInfo);
            return true;
        } catch (InvalidTokenException e) {
            redirectToLogin(request, response);
            return false;
        }
    }
  
    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
      
        // 从URL参数获取token
        return request.getParameter("token");
    }
  
    private void redirectToLogin(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        String loginUrl = "https://sso.example.com/login?redirect=" + 
                         URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
        response.sendRedirect(loginUrl);
    }
  
    private boolean isExcludedPath(String uri) {
        return uri.startsWith("/public/") || 
               uri.startsWith("/static/") || 
               uri.equals("/health");
    }
}

3. 基于CAS的SSO

CAS客户端配置
@Configuration
@EnableCas
public class CASConfig {
  
    @Value("${cas.server.url}")
    private String casServerUrl;
  
    @Value("${cas.client.url}")
    private String casClientUrl;
  
    @Bean
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl(casServerUrl + "/login");
        entryPoint.setServiceProperties(serviceProperties());
        return entryPoint;
    }
  
    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(casClientUrl + "/login/cas");
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }
  
    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(cas20ServiceTicketValidator());
        provider.setAuthenticationUserDetailsService(userDetailsService());
        provider.setKey("casProviderKey");
        return provider;
    }
  
    @Bean
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        return new Cas20ServiceTicketValidator(casServerUrl);
    }
}

4. 基于OAuth2的SSO

OAuth2配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  
    @Autowired
    private AuthenticationManager authenticationManager;
  
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("web-app")
            .secret("{noop}secret")
            .authorizedGrantTypes("authorization_code", "refresh_token")
            .scopes("read", "write")
            .redirectUris("http://localhost:8080/login/oauth2/code/")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(86400);
    }
  
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService());
    }
  
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess("permitAll()")
               .checkTokenAccess("isAuthenticated()");
    }
}

重难点分析

1. 安全性问题

会话劫持防护
@Component
public class SessionSecurityService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public void createSecureSession(String sessionId, UserInfo userInfo, 
                                  HttpServletRequest request) {
        // 绑定IP地址
        String clientIP = getClientIP(request);
        String userAgent = request.getHeader("User-Agent");
      
        SessionInfo sessionInfo = new SessionInfo();
        sessionInfo.setUserInfo(userInfo);
        sessionInfo.setClientIP(clientIP);
        sessionInfo.setUserAgent(userAgent);
        sessionInfo.setCreateTime(LocalDateTime.now());
      
        String key = "sso:session:" + sessionId;
        redisTemplate.opsForValue().set(key, sessionInfo, Duration.ofHours(1));
    }
  
    public boolean validateSessionSecurity(String sessionId, HttpServletRequest request) {
        String key = "sso:session:" + sessionId;
        SessionInfo sessionInfo = (SessionInfo) redisTemplate.opsForValue().get(key);
      
        if (sessionInfo == null) {
            return false;
        }
      
        // 检查IP地址是否变化
        String currentIP = getClientIP(request);
        if (!currentIP.equals(sessionInfo.getClientIP())) {
            log.warn("IP地址变化,可能存在会话劫持: {}", sessionId);
            return false;
        }
      
        // 检查User-Agent是否变化
        String currentUserAgent = request.getHeader("User-Agent");
        if (!currentUserAgent.equals(sessionInfo.getUserAgent())) {
            log.warn("User-Agent变化,可能存在会话劫持: {}", sessionId);
            return false;
        }
      
        return true;
    }
  
    private String getClientIP(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
            return xForwardedFor.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}
防重放攻击
@Component
public class ReplayAttackProtection {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public boolean validateNonce(String nonce, long timestamp) {
        // 检查时间戳是否在有效期内(5分钟)
        long currentTime = System.currentTimeMillis();
        if (Math.abs(currentTime - timestamp) > 300000) {
            return false;
        }
      
        // 检查nonce是否已被使用
        String key = "nonce:" + nonce;
        Boolean isUsed = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(isUsed)) {
            return false;
        }
      
        // 标记nonce为已使用,设置过期时间
        redisTemplate.opsForValue().set(key, "used", Duration.ofMinutes(5));
        return true;
    }
  
    public String generateNonce() {
        return UUID.randomUUID().toString();
    }
}

2. 性能优化

分布式会话管理
@Configuration
public class RedisSessionConfig {
  
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
      
        // 设置序列化器
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, 
                                   ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
      
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
      
        template.afterPropertiesSet();
        return template;
    }
  
    @Bean
    public RedisSessionRepository redisSessionRepository(RedisTemplate<String, Object> redisTemplate) {
        return new RedisSessionRepository(redisTemplate);
    }
}
会话缓存策略
@Service
public class SessionCacheService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    @Cacheable(value = "session", key = "#sessionId")
    public SessionInfo getSessionInfo(String sessionId) {
        String key = "sso:session:" + sessionId;
        return (SessionInfo) redisTemplate.opsForValue().get(key);
    }
  
    @CacheEvict(value = "session", key = "#sessionId")
    public void invalidateSession(String sessionId) {
        String key = "sso:session:" + sessionId;
        redisTemplate.delete(key);
    }
  
    @CachePut(value = "session", key = "#sessionId")
    public SessionInfo updateSession(String sessionId, SessionInfo sessionInfo) {
        String key = "sso:session:" + sessionId;
        redisTemplate.opsForValue().set(key, sessionInfo, Duration.ofHours(1));
        return sessionInfo;
    }
}

3. 单点登出问题

全局登出实现
@Service
public class GlobalLogoutService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    @Autowired
    private ApplicationEventPublisher eventPublisher;
  
    public void globalLogout(String sessionId) {
        // 1. 获取用户信息
        SessionInfo sessionInfo = getSessionInfo(sessionId);
        if (sessionInfo == null) {
            return;
        }
      
        // 2. 获取用户的所有活跃会话
        String userKey = "user:sessions:" + sessionInfo.getUserInfo().getUserId();
        Set<String> userSessions = redisTemplate.opsForSet().members(userKey);
      
        // 3. 清除所有会话
        if (userSessions != null) {
            for (String session : userSessions) {
                invalidateSession(session);
            }
        }
      
        // 4. 清除用户会话集合
        redisTemplate.delete(userKey);
      
        // 5. 发布登出事件
        eventPublisher.publishEvent(new UserLogoutEvent(sessionInfo.getUserInfo()));
      
        log.info("用户 {} 全局登出成功", sessionInfo.getUserInfo().getUsername());
    }
  
    public void registerUserSession(String userId, String sessionId) {
        String userKey = "user:sessions:" + userId;
        redisTemplate.opsForSet().add(userKey, sessionId);
        redisTemplate.expire(userKey, Duration.ofHours(1));
    }
}

应用场景

1. 企业内部系统集成

@Service
public class EnterpriseSSOService {
  
    @Autowired
    private SSOClient ssoClient;
  
    public void integrateWithEnterpriseSystems(String userId) {
        // 集成ERP系统
        integrateERP(userId);
      
        // 集成CRM系统
        integrateCRM(userId);
      
        // 集成OA系统
        integrateOA(userId);
      
        // 集成财务系统
        integrateFinance(userId);
    }
  
    private void integrateERP(String userId) {
        // 调用ERP系统API,同步用户信息
        ERPUserInfo erpUser = ssoClient.getERPUserInfo(userId);
        if (erpUser != null) {
            // 处理ERP用户信息
            log.info("ERP系统集成成功: {}", userId);
        }
    }
  
    private void integrateCRM(String userId) {
        // 调用CRM系统API,同步用户信息
        CRMUserInfo crmUser = ssoClient.getCRMUserInfo(userId);
        if (crmUser != null) {
            // 处理CRM用户信息
            log.info("CRM系统集成成功: {}", userId);
        }
    }
}

2. 多租户SaaS应用

@Service
public class MultiTenantSSOService {
  
    @Autowired
    private TenantService tenantService;
  
    public SSOConfig getTenantSSOConfig(String tenantId) {
        Tenant tenant = tenantService.getTenant(tenantId);
        if (tenant == null) {
            throw new TenantNotFoundException("租户不存在: " + tenantId);
        }
      
        return SSOConfig.builder()
            .ssoServerUrl(tenant.getSsoServerUrl())
            .clientId(tenant.getClientId())
            .clientSecret(tenant.getClientSecret())
            .redirectUri(tenant.getRedirectUri())
            .build();
    }
  
    public void handleTenantLogin(String tenantId, String username, String password) {
        SSOConfig config = getTenantSSOConfig(tenantId);
      
        // 根据租户配置进行SSO认证
        SSOResult result = authenticateWithTenant(config, username, password);
      
        if (result.isSuccess()) {
            // 创建租户特定的会话
            createTenantSession(tenantId, result.getUserInfo());
        }
    }
}

3. 移动应用SSO

@Service
public class MobileSSOService {
  
    @Autowired
    private JWTSSOService jwtSSOService;
  
    public MobileSSOResponse mobileLogin(String username, String password, String deviceId) {
        // 验证用户凭据
        UserInfo userInfo = authenticateUser(username, password);
      
        // 生成移动端专用Token
        String mobileToken = generateMobileToken(userInfo, deviceId);
      
        // 记录设备登录信息
        recordDeviceLogin(userInfo.getUserId(), deviceId);
      
        return MobileSSOResponse.builder()
            .token(mobileToken)
            .userInfo(userInfo)
            .expiresIn(3600)
            .build();
    }
  
    private String generateMobileToken(UserInfo userInfo, String deviceId) {
        // 在JWT中添加设备信息
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userInfo.getUserId());
        claims.put("deviceId", deviceId);
        claims.put("tokenType", "mobile");
      
        return Jwts.builder()
            .setSubject(userInfo.getUsername())
            .addClaims(claims)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 3600000))
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
}

Spring Boot集成

1. 主配置类

@SpringBootApplication
@EnableCaching
@EnableAsync
public class SSOApplication {
  
    public static void main(String[] args) {
        SpringApplication.run(SSOApplication.class, args);
    }
  
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
  
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        mapper.setTimeZone(TimeZone.getDefault());
        return mapper;
    }
}

2. 安全配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Autowired
    private UserDetailsService userDetailsService;
  
    @Autowired
    private JWTSSOService jwtSSOService;
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/public/**", "/auth/**", "/health").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(new JWTSSOFilter(jwtSSOService), 
                           UsernamePasswordAuthenticationFilter.class)
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
  
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

3. 控制器

@RestController
@RequestMapping("/api/sso")
public class SSOController {
  
    @Autowired
    private SSOService ssoService;
  
    @Autowired
    private JWTSSOService jwtSSOService;
  
    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
        try {
            UserInfo userInfo = ssoService.authenticate(request.getUsername(), 
                                                      request.getPassword());
          
            String token = jwtSSOService.generateSSOToken(userInfo);
          
            return ResponseEntity.ok(LoginResponse.builder()
                .token(token)
                .userInfo(userInfo)
                .expiresIn(3600)
                .build());
        } catch (AuthenticationException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(LoginResponse.builder()
                    .error("认证失败")
                    .build());
        }
    }
  
    @PostMapping("/logout")
    public ResponseEntity<Void> logout(@RequestHeader("Authorization") String token) {
        if (token != null && token.startsWith("Bearer ")) {
            String jwtToken = token.substring(7);
            ssoService.logout(jwtToken);
        }
        return ResponseEntity.ok().build();
    }
  
    @GetMapping("/user")
    public ResponseEntity<UserInfo> getCurrentUser(@RequestAttribute UserInfo userInfo) {
        return ResponseEntity.ok(userInfo);
    }
  
    @PostMapping("/validate")
    public ResponseEntity<ValidationResponse> validateToken(@RequestBody String token) {
        try {
            UserInfo userInfo = jwtSSOService.extractUserInfo(token);
            return ResponseEntity.ok(ValidationResponse.builder()
                .valid(true)
                .userInfo(userInfo)
                .build());
        } catch (Exception e) {
            return ResponseEntity.ok(ValidationResponse.builder()
                .valid(false)
                .error(e.getMessage())
                .build());
        }
    }
}

4. 配置文件

# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

  cache:
    type: redis
    redis:
      time-to-live: 3600000
      cache-null-values: false

jwt:
  secret: your-secret-key-here
  expiration: 3600000

sso:
  server:
    url: https://sso.example.com
    login-path: /login
    logout-path: /logout
    validate-path: /validate
  
  client:
    id: your-client-id
    secret: your-client-secret
    redirect-uri: http://localhost:8080/callback

logging:
  level:
    com.example.sso: DEBUG
    org.springframework.security: DEBUG

5. 异常处理

@ControllerAdvice
public class SSOExceptionHandler {
  
    @ExceptionHandler(InvalidTokenException.class)
    public ResponseEntity<ErrorResponse> handleInvalidToken(InvalidTokenException e) {
        ErrorResponse error = ErrorResponse.builder()
            .error("INVALID_TOKEN")
            .message("Token无效或已过期")
            .timestamp(LocalDateTime.now())
            .build();
      
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
    }
  
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthentication(AuthenticationException e) {
        ErrorResponse error = ErrorResponse.builder()
            .error("AUTHENTICATION_FAILED")
            .message("用户名或密码错误")
            .timestamp(LocalDateTime.now())
            .build();
      
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
    }
  
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception e) {
        ErrorResponse error = ErrorResponse.builder()
            .error("INTERNAL_ERROR")
            .message("服务器内部错误")
            .timestamp(LocalDateTime.now())
            .build();
      
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

最佳实践

1. 安全最佳实践

安全建议:
  - 使用HTTPS传输所有SSO相关数据
  - 实现Token过期和刷新机制
  - 记录所有SSO操作的审计日志
  - 实现防暴力破解机制
  - 定期轮换密钥和证书
  - 实现多因素认证(MFA)
  - 监控异常登录行为

2. 性能最佳实践

性能建议:
  - 使用Redis等缓存存储会话信息
  - 实现Token黑名单机制
  - 合理设置Token过期时间
  - 使用异步处理非关键操作
  - 实现负载均衡和集群部署
  - 监控SSO系统性能指标

3. 监控和日志

@Component
public class SSOMonitoringService {
  
    @Autowired
    private MeterRegistry meterRegistry;
  
    public void recordLoginAttempt(String username, boolean success) {
        if (success) {
            meterRegistry.counter("sso.login.success").increment();
        } else {
            meterRegistry.counter("sso.login.failure").increment();
        }
    }
  
    public void recordTokenValidation(String token, boolean valid) {
        if (valid) {
            meterRegistry.counter("sso.token.valid").increment();
        } else {
            meterRegistry.counter("sso.token.invalid").increment();
        }
    }
  
    @EventListener
    public void handleUserLoginEvent(UserLoginEvent event) {
        log.info("用户登录事件: username={}, timestamp={}, ip={}", 
                event.getUsername(), event.getTimestamp(), event.getClientIP());
    }
  
    @EventListener
    public void handleUserLogoutEvent(UserLogoutEvent event) {
        log.info("用户登出事件: username={}, timestamp={}", 
                event.getUsername(), event.getTimestamp());
    }
}

总结

Java单点登录(SSO)是企业级应用中的重要技术,通过本文章的学习,您应该能够:

  1. 理解SSO的核心概念和优势
  2. 掌握不同SSO实现方式的原理和代码
  3. 分析SSO系统的重难点和解决方案
  4. 了解SSO在各种应用场景中的使用
  5. 在Spring Boot中正确集成SSO功能
  6. 遵循最佳实践,构建安全可靠的SSO系统

SSO系统的成功实施需要综合考虑安全性、性能、可维护性等多个方面,建议在实际项目中根据具体需求选择合适的实现方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值