Java 单点登录 (SSO) 有以下几种解决方案:

  1. 基于令牌(Token)的 SSO:用户在登录成功后,得到一个令牌,然后该令牌被用于访问其他相互信任的应用程序。Shiro 和 Spring Security 都支持基于令牌的 SSO。
  2. 基于代理(Proxy)的 SSO:一个代理服务器接收来自用户的请求,并将其重定向到适当的应用程序,并将用户身份验证信息传递给该应用程序。常见的代理服务器包括 Apache HTTP Server、Nginx 等。
  3. 基于中央认证授权(Central Authentication Authorization, CAS) 的 SSO:CAS 是一种开源的单点登录协议,它使用令牌来实现 SSO,可以与各种应用程序进行集成。CAS 服务器负责验证用户身份并生成令牌,应用程序则只需检查令牌即可。
  4. 基于安全标记(Security Assertion Markup Language, SAML) 的 SSO:SAML 是一种 XML 标记语言,用于在不同的应用程序之间交换用户身份验证和授权数据。当用户在一个应用程序上进行身份验证时,SAML 将会生成一个安全标记,并将其发送到其他应用程序以完成 SSO。
  5. OAuth 和 OpenID Connect:OAuth 用于授权,OpenID Connect 则用于身份验证。它们都是基于令牌的 SSO 解决方案,被广泛应用于第三方身份验证和授权领域。

以下是一些使用 Java 编写基于 Spring Security 的简单 SSO 示例

基于 Cookie 的 SSO

// 配置认证中心
@Configuration
@EnableWebSecurity
public class AuthServerConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login")
                .and()
                .logout().logoutSuccessUrl("/login").invalidateHttpSession(true)
                .deleteCookies("JSESSIONID", "MYAPPID");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

// 配置子应用
@Configuration
public class SubAppConfig {
    // ...
    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/sub/login")
    public String subLogin(HttpServletRequest request, HttpServletResponse response) {
        // 检查是否已经登录,如果已经登录就不需要再次重定向到认证中心了
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if ("MYAPPID".equals(cookie.getName())) {
                    return "index";
                }
            }
        }

        // 未登录则跳转到认证中心进行认证
        response.sendRedirect("http://authserver.com/login?redirect_uri=http://subapp.com/sub/login/callback");
        return null;
    }

    @RequestMapping("/sub/login/callback")
    public String subLoginCallback(HttpServletRequest request, HttpServletResponse response) {
        // 认证中心认证成功后会重定向到此接口,携带认证令牌
        String token = request.getParameter("token");

        // 将认证令牌保存为 Cookie,并跳转回子应用首页
        Cookie cookie = new Cookie("MYAPPID", token);
        cookie.setPath("/");
        cookie.setMaxAge(24 * 60 * 60);
        response.addCookie(cookie);
        return "index";
    }
}

基于JWT的SSO

// 配置认证中心
@Configuration
@EnableWebSecurity
public class AuthServerConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login")
                .and()
                .logout().logoutSuccessUrl("/login").invalidateHttpSession(true)
                .deleteCookies("JSESSIONID");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

// 配置子应用
@Configuration
public class SubAppConfig {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${authserver.url:http://authserver.com}")
    private String authServerUrl;

    @Value("${jwt.secret}")
    private String jwtSecret;

    @RequestMapping("/index")
    public String index(HttpServletRequest request) {
        String token = getTokenFromCookie(request);
        String username = getUsernameFromToken(token);

        // ...
    }

    @RequestMapping("/sub/login")
    public String subLogin(HttpServletRequest request, HttpServletResponse response) {
        // 检查是否已经登录,如果已经登录就不需要再次重定向到认证中心了
        String token = getTokenFromCookie(request);
        if (StringUtils.isNotEmpty(token)) {
            return "index";
        }

        // 未登录则跳转到认证中心进行认证
        response.sendRedirect(authServerUrl + "/login?redirect_uri=http://subapp.com/sub/login/callback");
        return null;
    }

    @RequestMapping("/sub/login/callback")
    public String subLoginCallback(HttpServletRequest request, HttpServletResponse response) {
        // 认证中心认证成功后会重定向到此接口,携带 JWT
        String jwt = request.getParameter("jwt");

        // 验证 JWT 签名,并解析出用户信息
        Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(jwt).