zoukankan      html  css  js  c++  java
  • 单点登录笔记

    背景

    在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。

    但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员

    来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。

    单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

    如图所示,图中有4个系统,分别是Application1、Application2、Application3、和SSO。Application1、Application2、Application3没有登录模块,而SSO只有登录模块,没有其他的业务模块,当Application1、Application2、Application3需要登录时,将跳到SSO系统,SSO系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。

    技术实现

    在说单点登录(SSO)的技术实现之前,我们先说一说普通的登录认证机制。

    如上图所示,我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie,这个Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个Cookie的名字叫做jsessionid,值在服务端(server)是唯一的。

    同域和不同域下的单点登录

    一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。

    我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.a.com中登录了,其实是在sso.a.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.a.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:

    • Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
    • sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。

      那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。

    Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:Spring-Session。这样第2个问题也解决了。

    同域下的单点登录就实现了,但这还不是真正的单点登录。

    登录认证服务器的控制层

    @Controller
    public class LoginController {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        //获取用户信息
        @GetMapping("/userInfo")
        @ResponseBody
        public String userInfo(
                @RequestParam(value = "token",required = true) String token
        ){
            String s = redisTemplate.opsForValue().get(token);
            System.out.println("用户的token结果为"+s);
            return s;
        }
    
    
        /*
         * @description 跳转登录页面
         * @param url: 重定向的路径
         * @param token: token令牌
         * @return java.lang.String
         * @author han
         * @date 2020年07月06日 16:30
         */
        @RequestMapping(value = "/login.html",method = RequestMethod.GET)
        public String doLogin(@RequestParam("redirect_url") String url,
                              Model model,
                              @CookieValue(value = "sso_token",required = false) String token){
            if (!StringUtils.isEmpty(token)){
                //说明之前已经登陆过了,就不用在登录了,直接返回到之前的页面
                return "redirect:"+url+"?token="+token;
            }
            model.addAttribute("url",url);
            //登录成功跳转页面,跳回到之前的页面
            return "login";
        }
    
        //登录方法
        @RequestMapping(value = "doLogin",method = RequestMethod.POST)
        public String loginPage(String username,
                                String password,
                                String url,
                                HttpServletResponse response){
            if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)){
                //将用户信息存储进redis中
                String uuid = UUID.randomUUID().toString().replace("-", "");
                redisTemplate.opsForValue().set(uuid,username);
                //将token保存到cookie中,
                Cookie cookie = new Cookie("sso_token",uuid);
                response.addCookie(cookie);
                //登陆成功,跳回之前的页面,携带登陆成功的token
                return "redirect:"+url+"?token="+uuid;
            }
            //登录成功跳转页面,跳回到之前的页面
            return "login";
        }
    }
    

    登录认证服务器的配置文件

    server.port=8080
    
    spring.redis.host=你的redis的地址
    

    客户端1的控制层

    @Controller
    public class HelloController {
    
        @Value("${sso.server.url}")
        String ssoServerUrl;
    
        @ResponseBody
        @RequestMapping(value = "/hello",method = RequestMethod.GET)
        public String hello(){
            return "hello";
        }
    
        /*
         * @description
         * @param model:
         * @param session:
         * @param token: 只要在登录页成功登录了,就携带token
         * @return java.lang.String
         * @author han
         * @date 2020年05月27日 12:29
         */
        @RequestMapping(value = "/employees",method = RequestMethod.GET)
        public String employees(Model model, HttpSession session,
                                @RequestParam(value = "token",required = false) String token){
            //判断token是否为空
            if (!StringUtils.isEmpty(token)){
                //去ssoserver获取当前token真正的用户信息
                RestTemplate restTemplate = new RestTemplate();
                String url="http://ssoserver.com:8080/userInfo?token=" + token;
                ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                String entityBody = forEntity.getBody();
                System.out.println("获取到的用户信息为"+entityBody);
                session.setAttribute("loginUser",entityBody);
            }
    
            Object user = session.getAttribute("loginUser");
            if (user==null){
                //没登录,跳转到登录服务器登录
                //跳转过去之后,使用url上的查询参数标识我们自己是哪个页面
                return "redirect:"+ssoServerUrl+"?redirect_url=http://client1.com:8081/employees";
            }else {
                ArrayList<String> list = new ArrayList<>();
                list.add("张三");
                list.add("李四");
                model.addAttribute("emps",list);
                return "list";
            }
        }
    }
    

    客户端1的配置文件

    server.port=8081
    
    sso.server.url=http://ssoserver.com:8080/login.html
    

    客户端2的内容和客户端1的内容是一样的,只是端口不同,所以客户端2的代码就不粘贴了

    接下来是页面部分,模板引擎使用的是thymeleaf,内容如下

    登录页面,放在认证服务器中,内容如下

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>登录页</title>
    </head>
    <body>
    
    <form action="/doLogin" method="post">
        用户名:<input name="username" /><br>
        密码:<input name="password" /><br>
        <input type="hidden" name="url" th:value="${url}">
        <input type="submit" value="登录">
    </form>
    
    </body>
    </html>
    

    登录后才能访问的页面,放在客户端中,内容如下

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>员工列表</title>
    </head>
    <body>
    
        <h1>欢迎</h1>
        <ul>
            <li th:each="emp : ${emps}">姓名: [[${emp}]]</li>
        </ul>
    </body>
    </html>
    

    运行流程如下:

  • 相关阅读:
    JID 2.0 RC4 发布,高性能的 Java 序列化库
    FBReaderJ 1.6.3 发布,Android 电子书阅读器
    Arquillian 1.0.3.Final 发布,单元测试框架
    JavaScript 的宏扩展 Sweet.js
    Hypertable 0.9.6.5 发布,分布式数据库
    JRuby 1.7.0 发布,默认使用 Ruby 1.9 模式
    httppp 1.4.0 发布,HTTP响应时间监控
    Redis 2.6.0 正式版发布,高性能K/V服务器
    OfficeFloor 2.5.0 发布,IoC 框架
    XWiki 4.3 首个里程碑发布
  • 原文地址:https://www.cnblogs.com/balloon72/p/13257797.html
Copyright © 2011-2022 走看看