如何实现百度登录窗口_单点登录系统SSO是如何实现的?

原文在这里:

单点登录系统SSO是如何实现的?​mp.weixin.qq.com
7235dd3854b3d99534c2c247852ab3ec.png

deaa7efc64ec3997855d6b4247577d58.png
扫码关注公众号【爪哇优太儿】

所谓单点登录就是在A系统登录以后,跳转到B系统,此时可以直接访问B系统的资源,而不需要二次登录,目前这种需求已经非常普遍了,那么背后是怎么实现的呢?本文将用一个实际的例子来给大家详细的讲解下。

准备工作

(1)准备3个域名来模拟3个站点,http://www.site1.com和http://www.site2.com是业务域,http://www.usercenter.com是用户中心。一般单点登录系统背后都有一个独立的用户中心。

当然我们不需要真的去万网申请3个域名,只需要修改下本机的host文件即可,修改C:WindowsSystem32driversetc,添加以下内容:

127.0.0.1 http://www.site1.com

127.0.0.1 http://www.site2.com

127.0.0.1 www.usercenter.com

(2)准备一个nginx,把三个域名都挂上

我们本机启动3个tomcat,端口分别是site1:8081,site2:8082,usercenter:8080,通过nginx统一用标准的80来访问。

server {
        listen       80;
        server_name  www.usercenter.com;
        location / {
             proxy_set_header Host $host;
             proxy_set_header X-Real-Ip $remote_addr;
             proxy_set_header X-Forwarded-For $remote_addr;
             proxy_pass   http://127.0.0.1:8080;
        }
    }

    server {
        listen       80;
        server_name  www.site1.com;
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-Ip $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass   http://127.0.0.1:8081;
        }
    }

    server {
        listen       80;
        server_name  www.site2.com;
        location / {
             proxy_set_header Host $host;
             proxy_set_header X-Real-Ip $remote_addr;
             proxy_set_header X-Forwarded-For $remote_addr;
             proxy_pass   http://127.0.0.1:8082;
        }
    }

现在我们就可以在本机用3个tomcat来模拟3个独立域名的服务器了。

原理分析

(1)浏览器访问site1,site1首先检查请求中是否带有site1的cookie,cookie的值是用户的token。如果有则向用户中心检查token的有效性,如果有效则可以直接访问site1,如果没有或者失效,重定向到用户中心去登录。

(2)浏览器进入用户中心的登录页面,用户输入用户名和密码,登录成功以后,用户中心需要写usercenter域的cookie,cookie的值是随机生成的一个token,然后重定向回到site1,同时把token作为url的参数给回传过去,因为cookie是usercenter域的,不是site1域的,只能通过url参数来传递。

(3)浏览器到了site1,还是首先检查cookie为空,然后需要检查参数中是否有用户中心设置的token参数,如果有,检查参数有限性,拿到用户信息,写site1的cookie,cookie的值是用户中心设置的token。

此时,如果浏览器继续访问site1的页面,会回传site1的cookie,只要去用户中心校验有效性即可。

如果,此时要跳转到site2,site2同样也要做:

(1)浏览器访问site2,site2首先检查请求中是否带有site2的cookie,cookie的值是用户的token。如果有则向用户中心检查token的有效性,如果有效则可以直接访问site2,如果没有或者失效,重定向到用户中心去登录。

(2)浏览器进入用户中心要做登录的时候,此时浏览器是传递了site1登录成功以后用户中心下发的cookie的,因为此时访问的是usercenter域,因此用户中心拿到cookie以后,只需要去校验有效性,校验通过则重定向回site2.

(3)浏览器到了site2,还是首先检查cookie为空,然后需要检查参数中是否有用户中心设置的token参数,如果有,检查参数有效性,拿到用户信息,写site2的cookie,cookie的值是用户中心设置的token。

此时,如果浏览器继续访问site2的页面,会回传site2的cookie,只要去用户中心校验有效性即可。

上面啰啰嗦嗦一大堆,看上去很绕很抽象,一团乱麻,还是来看代码吧,看代码就清爽多了,图就不画了,感觉帮助不大。

代码实现

本文我们用springboot+thymeleaf来实现,springmvc也是一样的道理。

site1访问需要登录的页面的时候:

@GetMapping("/main")
  public String main(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
    //首先从cookie中取token,这里只能取site1的cookie
    //首次访问肯是空,除非后面登陆成功以后设置过
    String tk = getFromCookie(request);
    if(tk != null) {
      //验证token有效性
      String username = getFromUserCenter(tk);
      if(username != null) {
        model.addAttribute("username", username);
        return "main";
      }else {
        //如果失效,重新登录
        String url = getFullUrl(request);
        return "redirect:"+String.format(USER_CENTER_LOGIN_URL, URLEncoder.encode(url, "UTF-8"));
      }
    }else {
      //从参数中取,如果从用户中心跳转回来,会在参数中传递token
      tk = getFromParam(request);
      if(tk != null) {
        //还是要校验下参数的有效性
        String username = getFromUserCenter(tk);
        if(username != null) {
          //生成自己站点下面的cookie
          Cookie cookie = new Cookie(COOKIE_NAME, tk);
          cookie.setMaxAge(Integer.MAX_VALUE);
          response.addCookie(cookie);
          //进入主页
          model.addAttribute("username", username);
          return "main";
        }else {//说明token已经过期,进入用户中心做登录
          String url = getFullUrl(request);
          return "redirect:"+String.format(USER_CENTER_LOGIN_URL, URLEncoder.encode(url, "UTF-8"));
        }
      }else {
        //进入用户中心做登录
        String url = getFullUrl(request);
        return "redirect:"+String.format(USER_CENTER_LOGIN_URL, URLEncoder.encode(url, "UTF-8"));
      }
    }
  }

usercenter处理业务域的跳转和做登录:

当各个站定向用户中跳的时候,首先访问这个USER_CENTER_LOGIN_URL:

@GetMapping(value="/login")
  public String to_login(HttpServletRequest request, Model model)throws Exception {
    String redir_url = request.getParameter("redir_url");
    //先看是否存在usercenter下面额cookie
    //只要有一个站点登录成功过,其他的站点往用户中心跳的时候
    //都会携带这个cookie,只要有效,就可以不用再次登陆
    String tk = getFromCookie(request);
    if(tk == null) {//去登陆
      model.addAttribute("redir_url", redir_url);
      return "login";
    }else {//check是否有效
      String username = getByToken(tk);
      if(username == null) {
        model.addAttribute("redir_url", redir_url);
        return "login";
      }else {//如果有效,直接跳回去
        redir_url = addTokenToUrl(redir_url, tk);
        return "redirect:"+redir_url;
      }
    }
  }

在用户中心做登录:

@PostMapping(value="/login")
  public String do_login(LoginUser loginUser, String redirect_url, HttpServletResponse res, Model model)throws Exception {
    String username = loginUser.getUsername();
    LoginUser userDB = getFromDB(username);
    if(userDB == null) {
      model.addAttribute("errmsg", "用户不存在");
      model.addAttribute("redir_url", redirect_url);
      return "login";
    }
    String pwdDB = userDB.getPassword();
    if(!pwdDB.equals(loginUser.getPassword())) {
      model.addAttribute("errmsg", "密码错误");
      model.addAttribute("redir_url", redirect_url);
      return "login";
    }
    //存redis,注意要设置有效期
    String tk = UUID.randomUUID().toString();
    redis.put(tk, userDB);
    //生成用户中心的cookie
    Cookie cookie = new Cookie(COOKIE_NAME, tk);
    cookie.setDomain("www.usercenter.com");
    cookie.setPath("/");
    cookie.setMaxAge(Integer.MAX_VALUE);
    res.addCookie(cookie);
    //跳转回去
    redirect_url = addTokenToUrl(redirect_url, tk);
    return "redirect:"+redirect_url;
  }

下面是用户中心校验token是否有效,有效就返回用户信息:

 @GetMapping(value="/getByToken")
  @ResponseBody
  public String getByToken(String token)throws Exception {
    //这里注意:延长下redis的有效期
    LoginUser user = redis.get(token);
    if(user != null) {
      return user.getUsername();
    }else {
      return null;
    }
  }

代码其实还是挺简单的,主要的界面截图如下:

(1)浏览器访问http://www.site1.com/index.html,这个是个静态页面,不需要登陆:

3088b0a53d028ebdaff98251a6172ab8.png
this is site1 index<br/>
<input type="button" onclick="visitMain()" value="visit main page" />
<script>
function visitMain(){
  window.location.href="http://www.site1.com/site1/main";
}
</script>

(2)点击visit main page按钮,访问site1站点下需要登录的页面,此时会重定向到usercenter,完整的url是

http://www.usercenter.com/user_center/login?redir_url=http%3A%2F%2Fwww.site1.com%2Fsite1%2Fmain:​www.usercenter.com

0830daa1d8ce7095de521c562369ad23.png

(3)输入用户名和密码做登陆,登录成功,跳转回site1,完整的url:

http://www.site1.com/site1/main?user_center_tk=19d89eb0-b135-492f-a684-2a8fc6f7e859,注意:参数中传递了token:​www.site1.com

323c77057512099fceb77da93308f4c3.png

此时,刷新页面的的话,site1是可以拿到cookie中的token的,只需要去检验token有效性即可。

欢迎你:<span th:text="${username}"></span>,这是site1的主页面<br/>
<input type="button" onclick="goToSite2()" value="go to site2"/>
<script>
function goToSite2(){
  window.location.href="http://www.site2.com/site2/main";
}
</script>

(4)点击页面上go to site2按钮,会直接进入site2的主界面而不需要做登录,因为site2首先是去用户中心,用户中心会拿到之前设置的cookie,校验通过以后,会跳转回site2,注意看site2的url:

http://www.site2.com/site2/main?user_center_tk=19d89eb0-b135-492f-a684-2a8fc6f7e859:​www.site2.com

ab8bcb9bd76de7f720f18ed5a4e985fd.png
<span th:text="${username}"></span>,这是site2的主页面<br/>
<input type="button" onclick="goToSite1()" value="go to site1"/>
<script>
function goToSite1(){
  window.location.href="http://www.site1.com/site1/main";
}
</script>

以上就是整个项目的实现,主要的点在于cookie不能跨域,A站只能设置A站的cookie,也只能读取A站的cookie,明白了这个就ok了。退出的代码就不写了,在site1退出的时候,首先要删除site1的cookie,然后要删除用户中心的redis即可。大家有兴趣可以自己实现下。

完整的代码下载:扫码关注文章开头的公众号看原文

代码仅仅是为了展示整个流程,如果是在实际项目中使用可以在业务域上用拦截器之类的来做。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值