zoukankan      html  css  js  c++  java
  • 【分布式系列】session跨域及单点登录解决方案

    Cookie机制

    Cookie技术是客户端的解决方案,Cookie就是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息。让我们说得更具体一些:当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器;接着,服务器在向客户端回传相应的超文本的同时也会发回这些个人信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,而是存放于HTTP响应头(Response Header);当客户端浏览器接收到来自服务器的响应之后,浏览器会将这些信息存放在一个统一的位置,对于Windows操作系统而言,我们可以从: [系统盘]:Documents and Settings[用户名]Cookies目录中找到存储的Cookie;自此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回至服务器。而这次,Cookie信息则存放在HTTP请求头(Request Header)了。有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的。

    而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。

    Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。

    Cookie可以做是http协议的扩展,有两个http头是专门负责设置以及发送coolie的,他们分别是Set-Cookie以及Cookie。

    当服务器返回给客户端一个http响应信息时,其中如果包含Set-Cookie这个头部时,意思就是指示客户端建立一个cookie,并且在后续的http请求中自动发送这个cookie到服务器端,直到这个cookie过期。如果cookie的生存时间是整个会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会自动清除这个cookie。另外一种情况就是保存在客户端的硬盘中,浏览器关闭的话,该cookie也不会被清除,下次打开浏览器访问对应网站时,这个cookie就会自动再次发送到服务器端。

    除了cookies,客户端还可以将发送给服务器的数据包含在请求的url中,比如请求的参数或者请求的路径中。 比如经常使用的JSESSIONID。

    什么是Cookie

    Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。

    查看网站cookie方式:地址输入(javascript:alert (document. cookie))

    注意:Cookie可以被禁用

    Cookie具有不可跨域性:

    Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样,因此Google不能操作Baidu的Cookie。

    需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。

    注意:用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。

    Cookie的安全属性:

    HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。

    1 Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie
    2 cookie.setSecure(true); // 设置安全属性
    3 response.addCookie(cookie); // 输出到客户端

    案例:永久登录

    如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。

    保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中。

    还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。

    这几种方案验证账号时都要查询数据库。

    本例将采用另一种方案,只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。相关代码如下: loginCookie.jsp:

      1 <%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>
      2 <%!                                                  // JSP方法
      3     private static final String KEY =":cookie@helloweenvsfei.com"; // 密钥 
      4 
      5     public final static String calcMD1(String ss) { // MD1 加密算法
      6        String s = ss == null ? "" : ss; // 若为null返回空
      7        char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; // 字典
      8        try {
      9            byte[] strTemp = s.getBytes();                          // 获取字节
     10            MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 获取MD1
     11            mdTemp.update(strTemp);                                // 更新数据
     12            byte[] md =mdTemp.digest();                        // 加密
     13            int j =md.length;                                 // 加密后的长度
     14            char str[] = new char[j * 2];                       // 新字符串数组
     15            int k =0;                                         // 计数器k
     16            for (int i = 0; i< j; i++) {                       // 循环输出
     17                byte byte0 = md[i];
     18                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
     19                str[k++] = hexDigits[byte0 & 0xf];
     20            }
     21            return new String(str);                             // 加密后字符串
     22        } catch (Exception e){return null; }
     23     }
     24 %>
     25 <%
     26    request.setCharacterEncoding("UTF-8");          // 设置request编码
     27    response.setCharacterEncoding("UTF-8");        // 设置response编码
     28 
     29    String action =request.getParameter("action"); // 获取action参数
     30 
     31    if("login".equals(action)) {                       // 如果为login动作
     32         String account =request.getParameter("account"); // 获取account参数
     33         String password =request.getParameter("password"); // 获取password参数
     34         int timeout = new Integer(request.getParameter("timeout")); // 获取timeout参数
     35 
     36         String ssid =calcMD1(account + KEY); // 把账号、密钥使用MD1加密后保存
     37 
     38         Cookie accountCookie = new Cookie("account", account); // 新建Cookie
     39         accountCookie.setMaxAge(timeout);              // 设置有效期
     40 
     41         Cookie ssidCookie =new Cookie("ssid", ssid);   // 新建Cookie
     42        ssidCookie.setMaxAge(timeout);                 // 设置有效期
     43 
     44        response.addCookie(accountCookie);             // 输出到客户端
     45        response.addCookie(ssidCookie);            // 输出到客户端
     46 
     47        // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
     48        response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
     49        return;
     50     } else if("logout".equals(action)) {                  // 如果为logout动作
     51        CookieaccountCookie = new Cookie("account", ""); // 新建Cookie,内容为空
     52        accountCookie.setMaxAge(0); // 设置有效期为0,删除
     53 
     54        Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空
     55        ssidCookie.setMaxAge(0);                   // 设置有效期为0,删除
     56        response.addCookie(accountCookie);         // 输出到客户端
     57        response.addCookie(ssidCookie);         // 输出到客户端
     58        // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容
     59        response.sendRedirect(request.getRequestURI() + "?" + System.currentTimeMillis());
     60        return;
     61     }
     62     boolean login = false;                        // 是否登录
     63     String account = null;                        // 账号
     64     String ssid = null;                           // SSID标识
     65 
     66     if(request.getCookies() !=null) {               // 如果Cookie不为空
     67         for(Cookie cookie : request.getCookies()) {  // 遍历Cookie
     68            if(cookie.getName().equals("account"))  // 如果Cookie名为 account
     69                account = cookie.getValue();       // 保存account内容
     70            if(cookie.getName().equals("ssid")) // 如果为SSID
     71                ssid = cookie.getValue();          // 保存SSID内容
     72         }
     73     }
     74     if(account != null && ssid !=null) {    // 如果account、SSID都不为空
     75         login = ssid.equals(calcMD1(account + KEY)); // 如果加密规则正确, 则视为已经登录
     76     }
     77 %>
     78 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
     79        <legend><%= login ? "欢迎您回来" : "请先登录"%></legend>
     80         <% if(login){%>
     81             欢迎您, ${cookie.account.value }. &nbsp;&nbsp;
     82            <a href="${pageContext.request.requestURI }?action=logout">
     83             注销</a>
     84         <% } else { %>
     85         <form action="${ pageContext.request.requestURI }?action=login" method="post">
     86            <table>
     87                <tr><td>账号: </td>
     88                    <td><input type="text"name="account" style="
     89                    200px; "></td>
     90                </tr>
     91                <tr><td>密码: </td>
     92                    <td><inputtype="password" name="password"></td>
     93                </tr>
     94                <tr>
     95                    <td>有效期: </td>
     96                    <td><inputtype="radio" name="timeout" value="-1"
     97                    checked> 关闭浏览器即失效 <br/> <input type="radio" 
     98                    name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天
     99                    内有效 <br/><input type="radio" name="timeout" value= 
    100                    "<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td> </tr>
    101                <tr><td></td>
    102                    <td><input type="submit"value=" 登  录 " class= 
    103                    "button"></td>
    104                </tr>
    105            </table>
    106         </form>
    107         <% } %>

    登录时可以选择登录信息的有效期:关闭浏览器即失效、30天内有效与永久有效。通过设置Cookie的age属性来实现,注意观察代码。

    Session机制:

    除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。

    Session技术则是服务端的解决方案,它是通过服务器来保持状态的。由于Session这个词汇包含的语义很多,因此需要在这里明确一下 Session的含义。首先,我们通常都会把Session翻译成会话,因此我们可以把客户端浏览器与服务器之间一系列交互的动作称为一个 Session。从这个语义出发,我们会提到Session持续的时间,会提到在Session过程中进行了什么操作等等;其次,Session指的是服务器端为客户端所开辟的存储空间,在其中保存的信息就是用于保持状态。从这个语义出发,我们则会提到往Session中存放什么内容,如何根据键值从 Session中获取匹配的内容等。要使用Session,第一步当然是创建Session了。那么Session在何时创建呢?当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同创建Session的方法,而在Java中是通过调用HttpServletRequest的getSession方法(使用true作为参数)创建的。在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从而再次使用之。正式这样一个过程,用户的状态也就得以保持了。

    什么是session:

    Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

    如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

    cookie和session的区别和联系:

    1. cookie数据存放在客户的浏览器上,session数据放在服务器上;
    2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session;
    3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE;
    4. 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K;

    解决session跨域共享问题

    1、session sticky:会话保存在单机上,保证会话请求落在一台服务器上

    2、session replication:session复制,每一台服务器上都保持一份相同的session

    3、session集中存储:存储db redis

    4、基于cookie(主流)

    a)access_token

    将access_token存储在客户端的cookie中,每次客户端过来访问,服务器端拦截其中获取cookie中的access_token,更userid和timestamp判断是否有效

    b)基于JWT的解决方案

    json web Token客户端和服务端信息安全传递,身份认证的一种解决方案,用在登陆上

    jwt由三个组成:header,payload 载荷,signature

    header{

    typ:"jwt" //类型

    alg:"HS256" //加密算法

    }

    payload  :jwt本身规范提供的格式 claims

    iss:“签发者”

    iat:“签发时间”

    exp:“过期时间”

    sub:

    可以自己定一些claims,放入自定义的信息如 uid 等

    signature: 将 header+ payload 组合成为一个字符串

    Base64(header).Base64(payload)  +  head中定义的算法 +密钥  生成一个字符串    str.签名字符串  就是 JWT的token

     1  
     2 /**
     3  * @Auther: tengxiao
     4  * @Date: 2018/9/13 16:47
     5  * @Description:
     6  */
     7 public class JWTTokenUtil {
     8  
     9     private static final String JWT_KEY_USER_ID="JWT_KEY_USER_ID";
    10     private static final int EXPIRED_TIME=6000;
    11     private static final String SECRET_KEY="tengvincent_user";
    12  
    13     public static String generatorToken(Long userId)throws Exception{
    14         //header Map
    15         Map<String,Object> headerMap=new HashMap<>();
    16         headerMap.put("typ","JWT");
    17         headerMap.put("alg","HS256");
    18  
    19         String token=JWT.create()
    20                 .withHeader(headerMap)
    21                 .withClaim("iss","Service")//签发者
    22                 .withClaim("aud","APP")
    23                 .withClaim(JWT_KEY_USER_ID,userId)
    24                 .withIssuedAt(DateTime.now().toDate())//sign time
    25                 .withExpiresAt(DateTime.now().plusMinutes(EXPIRED_TIME).toDate())//expired time
    26                 .sign(Algorithm.HMAC256(SECRET_KEY));
    27  
    28         return token;
    29     }
    30  
    31     public static Map<String,Claim> varifyToken(String token){
    32         DecodedJWT jwt=null;
    33         try{
    34          JWTVerifier verifer= JWT.require(Algorithm.HMAC256(SECRET_KEY)).build();
    35          jwt=verifer.verify(token);
    36         }catch (Exception e){
    37             // e.printStackTrace();
    38             // token 校验失败, 抛出Token验证非法异常
    39         }
    40         return jwt.getClaims();
    41     }
    42  
    43  
    44     public static Long getTokenInfo(String token){
    45         Map<String, Claim> claims = varifyToken(token);
    46         Claim user_id_claim = claims.get("user_id");
    47         if (null == user_id_claim || StringUtils.isEmpty(user_id_claim.asString())) {
    48             // token 校验失败, 抛出Token验证非法异常
    49         }
    50         return Long.valueOf(user_id_claim.asString());
    51     }
    52  
    53 }

    1.token+redis与jwt的区别

        (1)简单的说,token只是一个标识,以token加redis为例,服务端将token保存在redis中,客服端访问时带上token,如果在redis中能够查到这个token,说明身份有效。

        (2)jwt不需要查库,本身已经包含了用户的相关信息,可以直接通过服务端解析出相关的信息,与session,token的最大区别就是服务端不保存任何信息。

    2.如何实现jwt续期

    在jwt中保存过期时间,解析时进行判定,如果即将超时则重新设置过期时间返回一个新的jwt给客户端。

    3.jwt登出失效

    登出时将相关的信息比如用户名存储在redis中,并设置过期时间。当再次访问时,从jwt中解析出用户名去redis中查找,如果存在则表示此jwt已登出失效。这里需要注意的是,如果用此方法,则验证jwt是否登出应该放在第一位。思考一个场景,如果redis中存储的是用户名,那么当用户登出后,redis中已经有了相应的用户名,当用户再次登录时,解析jwt发现此用户已登出,则jwt失效,所以在登录时要清空相关的登出缓存。


    参考:https://www.cnblogs.com/andy-zhou/p/5360107.html

  • 相关阅读:
    Linux进程管理工具Supervisor
    RSA加密传输代码示例
    静态网站创建工具Docusaurus
    Proactor和Reactor模型
    机器学习中的七宗罪
    Tokio internals: Understanding Rust's asynchronous I/O framework from the bottom up
    开源软件创建SOC的一份清单
    How to setup SOC using open-source tools
    彼得定律
    深入浅出通信原理连载
  • 原文地址:https://www.cnblogs.com/dream-to-pku/p/10361350.html
Copyright © 2011-2022 走看看