多系统使用共享session实现SSO登录案例

在上一篇文章中我们讲述了单系统如何使用cookie+session来实现登录的案例。但是在大一点的公司里面,往往需要实现多个系统之间的SSO,那么这又该如何实现呢?

关于SSO的知识点本文不再赘述,本文假设的场景是公司里面有多个网站:

  • doc.zhangxun.com
  • code.zhangxun.com

这些网站的一级域名都是相同的,仅仅是二级域名不同。而且各个系统独立部署,运行在不同的tomcat中,现在需要用户在公司任意一个系统完成登录后,在一定时间内,访问公司其它网站都不再需要登录,从而实现SSO。

问题一:Cookie的跨域

由于浏览器的同源策略限制,我们都知道Cookie是不能跨域的,即www.google.com的cookie是不能被www.baidu.com使用的。但是我们的场景是同一个公司内不同的系统,他们的一级域名是相同的,二级域名不同,这种情况下,cookie能够共用吗?我们可以来试验一下。

首先,我们修改hosts文件,位于C:\Windows\System32\drivers\etc目录下,从而创建如上两个网站。

127.0.0.1       doc.zhangxun.com
127.0.0.1       code.zhangxun.com

然后,我们参考上一篇文章,分别创建两个不同的模块,分别为doc和code。它们的如下代码完全一样。


    org.springframework.boot
    spring-boot-starter-thymeleaf


    org.springframework.boot
    spring-boot-starter-web



    org.projectlombok
    lombok
    true


    org.springframework.boot
    spring-boot-starter-test
    test

@Slf4j
@Controller
public class LoginController {

    @GetMapping("/index")
    public String index(@CookieValue(value = "userid", required = false) String userid, HttpSession session){
        if(ObjectUtils.isEmpty(userid) || ObjectUtils.isEmpty(session.getAttribute(userid))){
            // 1.cookie中没有userid,表示用户从来没有登录过
            // 2.根据cookie中的userid在session中找不到用户信息,表示用户的session过期了,或者是伪造的userid
            log.info("需要用户重新登录!");
            return "login";
        }
        return "index";
    }

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password, HttpSession session, HttpServletResponse response){
        if(!"admin".equals(username) || !"123".equals(password)){
            log.info("用户名/密码不正确!");
            return "login";
        }

        // 服务器端保存用户的登录信息,并设置登录态2分钟内有效(以用户最后一次和服务器交互开始计算)
        String userid = UUID.randomUUID().toString();
        session.setAttribute("username", username);
        session.setAttribute(userid, userid);
        session.setMaxInactiveInterval(120);

        // 设置用户浏览器端的cookie,用来识别用户的身份,并设置有效期为5分钟(以cookie创建时开始计算)
        Cookie cookie = new Cookie("userid", userid);
        cookie.setMaxAge(300);
        response.addCookie(cookie);
        return "index";
    }
}



    
    用户主页


用户主页

欢迎:[[${session.username}]]




    
    登录页面


登录页面

此时,我们同时启动这两个应用:

  1. 访问http://doc.zhangxun.com:8080/index,由于第一次登录,没有cookie和session用户信息,因此进入登录页面;
  2. 用户登录后,创建了session和cookie中的用户信息,成功进入用户页面;
  3. 此时再次访问http://doc.zhangxun.com:8080/index,无需登录直接进入用户页面,而且观察浏览器中的请求信息,是有cookie用户信息的;
  4. 访问另外一个网站http://code.zhangxun.com:8080/index,发现没有任何cookie和session用户信息,因此进入登录页面;

如上步骤3和4的对比截图如下:

cookie不能跨域共享

如此,说明,在默认情况下,多个网站即使一级域名相同,二级域名不同也是无法共享cookie的。现在我们来改动下如上代码,让cookie能在一级域名相同的情况下进行共享。

cookie.setDomain("zhangxun.com");

如上,只要在登录过程中,设置cookie的共享域为一级域名即可实现共享了。如下是实验截图:

cookie实现跨域共享

但是结果显示,在执行步骤4的时候,虽然获取了共享的cookie用户信息,但是还是无法实现SSO,这是因为session不同导致的,仔细查看上图,发现cookie中的JSESSIONID是不同的,所以说明了这是两个不同的会话,无法实现session用户信息共享。

PS:如上两个应用应该端口号错开,比如分别使用8080和8081,图就不改了。

问题二:Session的共享

Session共享的实现方案一般来说有三种:

  • 使用Tomcat集群Session全局复制,即集群内每个Tomcat中都保留全量session用户信息,这种方案明显低效且有严重的性能问题,不推荐;
  • 根据用户请求的来源IP地址进行Hash映射到固定的服务器上,如此同一个用户会被固定在一台服务器上进行服务,如此就不需要session共享了,但问题是一旦某台服务器宕机,相当一部分用户的session信息就会丢失,而且后续需要弹性增减服务器的时候会比较麻烦,也不推荐。
  • 使用一种分布式可扩展的工具代替tomcat中的session,使得所有服务器都能安全可靠地共享session,比如redis,比较推荐,本文下面也是基于这样方案进行演示。

首先本地需要安装redis并启动,这个就不赘述了。然后两个应用同时修改代码如下:


    org.springframework.boot
    spring-boot-starter-data-redis

spring.redis.host=localhost
spring.redis.port=6379
@Slf4j
@Controller
public class LoginController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/index")
    public String index(@CookieValue(value = "userid", required = false) String userid, Model model){
        if(ObjectUtils.isEmpty(userid) || ObjectUtils.isEmpty(redisTemplate.opsForValue().get(userid))){
            // 1.cookie中没有userid,表示用户从来没有登录过
            // 2.根据cookie中的userid在共享session中找不到用户信息,表示用户的session过期了,或者是伪造的userid
            log.info("需要用户重新登录!");
            return "login";
        }
        model.addAttribute("username", redisTemplate.opsForValue().get(userid));
        return "index";
    }

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password, HttpServletResponse response, Model model){
        if(!"admin".equals(username) || !"123".equals(password)){
            log.info("用户名/密码不正确!");
            return "login";
        }

        // 服务器端保存用户的登录信息,并设置登录态2分钟内有效(以用户最后一次和服务器交互开始计算)
        String userid = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(userid, username, 120, TimeUnit.SECONDS);

        // 设置用户浏览器端的cookie,用来识别用户的身份,并设置有效期为5分钟(以cookie创建时开始计算)
        Cookie cookie = new Cookie("userid", userid);
        cookie.setMaxAge(300);
        cookie.setDomain("zhangxun.com");
        response.addCookie(cookie);

        model.addAttribute("username", username);
        return "index";
    }

}



    
    用户主页


用户主页

欢迎:[[${username}]]




    
    登录页面


登录页面

两个应用同时启动后,再重复上述4个步骤,就能发现cookie和session都能实现共享了。

下一篇介绍如果两个网站域名完全不同,即一级域名也不同的情况下如何实现SSO。

你可能感兴趣的:(多系统使用共享session实现SSO登录案例)