登录那些事

Cookie

HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。

cookie 存储在客户端cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain

Cookie机制

cookie的设置以及发送会经历以下4个步骤:

  1. 客户端发送一个请求到服务器
  2. 服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部
  3. 客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部
  4. 服务器返回响应数据

Cookie机制

Cookie 重要属性

属性说明
name=value键值对,设置 Cookie 的名称及相对应的值,都必须是字符串类型
domain指定 cookie 所属域名,默认是当前域名
path指定 cookie 在哪个路径(路由)下生效,默认是 ‘/’
maxAgecookie 失效的时间,单位秒 如果为整数,则该 cookie 在 maxAge 秒后失效。如果为负数,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。如果为 0,表示删除该 cookie 。默认为 -1
expires过期时间,在设置的某个时间点后该 cookie 就会失效。
secure该 cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
httpOnly如果给某个 cookie 设置了 httpOnly 属性,则无法通过 JS 脚本 读取到该 cookie 的信息,但还是能通过 Application 中手动修改 cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全

Cookie 的Java操作

public void writeCookie(HttpServletRequest req, HttpServletResponse resp) {
        int time = 10 * 24 * 60 * 60;
        Cookie cookie = new Cookie("DAIHAO", System.currentTimeMillis() + "");
        cookie.setPath("/");
        cookie.setDomain(req.getHeader("Origin"));
        cookie.setMaxAge(time);
        cookie.setHttpOnly(true);

        // 设置生命周期为MAX_VALUE,永久有效
        // cookie.setMaxAge(Integer.MAX_VALUE);

        // MaxAge为负数,是一个临时Cookie,不会持久化
        // cookie.setMaxAge(-1);

        resp.setCharacterEncoding("UTF-8");
        // 修改Cookie只能使用一个同名的Cookie来覆盖原先的Cookie。如果要删除某个Cookie,则只需要新建一个同名的Cookie,并将maxAge设置为0,并覆盖原来的Cookie即可。
        resp.addCookie(cookie);
    }

Session

session 是另一种记录服务器和客户端会话状态的机制

session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie

Session机制

  1. 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session

  2. 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器

  3. 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名

  4. 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

Session机制

Session的Java操作

@WebServlet(name = "SessionServlet", urlPatterns = "/session")
public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取 Session 对象
        HttpSession session = req.getSession();
        // 将用户名存储到 Session 中
        session.setAttribute("username", req.getParameter("username"));
        // 重定向到另一个页面
        resp.sendRedirect("anotherPage.jsp");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

Cookie 和 Session 的区别

  1. 安全性: SessionCookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的

  2. 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型

  3. 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。

  4. 存储大小不同: 单个 Cookie 保存的数据不能超过 4KSession 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

Token

Token,访问资源接口(API)时所需要的资源凭证 ,简单来说就是类似 cookie 的一种验证信息,客户端通过登录验证后,服务器会返回给客户端一个加密的 token,然后当客户端再次向服务器发起连接时,带上token,服务器直接对token进行校验即可完成权限校验。

Token机制

1、客户端使用用户名跟密码请求登录

2、服务端收到请求,验证用户名与密码,验证成功后,服务端签发 token 发送给客户端

3、客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage

4、客户端每次向服务端请求资源的时候需要带着服务端签发的 token

5、服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据

Token机制

Token 和 Session 的区别

  1. Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。

  2. SessionToken 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。

JWT

JSON Web Token (JWT) 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT机制

JWT机制
1、用户使用账号、密码登录应用,登录的请求发送到服务端。

2、服务端进行用户验证,然后创建JWT字符串返回给客户端。

3、客户端请求接口时,在请求头带上JWT

4、服务端验证JWT合法性,如果合法则继续调用应用接口返回结果。

JWT数据结构

JWT 一般是这样一个字符串,分为三个部分,以 “.” 隔开:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header

JWT 第一部分是头部分,它是一个描述 JWT 元数据的 Json 对象,通常如下所示。

{
  "alg": "HS256",
  "typ": "JWT"
}

alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256)typ 属性表示令牌的类型,JWT 令牌统一写为JWT

最后,使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存。

Payload

JWT 第二部分是 Payload,也是一个 Json 对象,除了包含需要传递的数据,还有七个默认的字段供选择。

  • iss (issuer):签发人/发行人

  • sub (subject):主题

  • aud (audience):用户

  • exp (expiration time):过期时间

  • nbf (Not Before):生效时间,在此之前是无效的

  • iat (Issued At):签发时间

  • jti (JWT ID):用于标识该 JWT

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此一些敏感信息不要存放于此,以防信息泄露。

JSON 对象也使用 Base64 URL 算法转换为字符串后保存,是可以反向反编码回原样的,这也是为什么不要在 JWT 中放敏感数据的原因。

Signature

JWT 第三部分是签名。生成首先需要指定一个 secret,该 secret 仅仅保存在服务器中,保证不能让其他用户知道。这个部分需要 base64URL 加密后的 headerbase64URL 加密后的 payload 使用 . 连接组成的字符串,然后通过header 中声明的加密算法 进行加盐secret组合加密,然后就得出一个签名哈希,也就是Signature,且无法反向解密。

那么 Application Server 如何进行验证呢?可以利用 JWT 前两段,用同一套哈希算法和同一个 secret 计算一个签名值,然后把计算出来的签名值和收到的 JWT 第三段比较,如果相同则认证通过。

JWT的优点

  • json格式的通用性,所以JWT可以跨语言支持,比如JavaJavaScriptPHPNode等等。

  • 可以利用Payload存储一些非敏感的信息。

  • 便于传输,JWT结构简单,字节占用小。

  • 不需要在服务端保存会话信息,易于应用的扩展。

JWT的Java操作

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>4.3.0</version>
</dependency>

JwtUtils 工具类

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import org.apache.commons.lang.StringUtils;


import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class JwtUtils {
    //过期时间 15分钟
    private static final long EXPIRE_TIME = 15 * 60 * 1000;
    //私钥
    private static final String TOKEN_SECRET = "DAIHAO";
    //会话连接的有效时长,单位分钟
    public static final int SCOKET_EFFECTIVE_TIME_MINUTE = 120;

    /**
     * 生成签名
     */
    public static String sign(Map<String, Object> map) {
        try {
            // 设置过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("typ", "jwt");
            // 返回token字符串
            JWTCreator.Builder builder = JWT.create()
                    .withHeader(header)
                    .withIssuedAt(new Date()) //发证时间
                    .withExpiresAt(date);  //过期时间
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (entry.getValue() instanceof Integer) {
                    builder.withClaim(entry.getKey(), (Integer) entry.getValue());
                } else if (entry.getValue() instanceof Long) {
                    builder.withClaim(entry.getKey(), (Long) entry.getValue());
                } else if (entry.getValue() instanceof Boolean) {
                    builder.withClaim(entry.getKey(), (Boolean) entry.getValue());
                } else if (entry.getValue() instanceof String) {
                    builder.withClaim(entry.getKey(), String.valueOf(entry.getValue()));
                } else if (entry.getValue() instanceof Double) {
                    builder.withClaim(entry.getKey(), (Double) entry.getValue());
                } else if (entry.getValue() instanceof Date) {
                    builder.withClaim(entry.getKey(), (Date) entry.getValue());
                }
            }
            return builder.sign(algorithm);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 检验token是否正确
     *
     * @param **token**
     * @return
     */
    public static boolean verify(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        verifier.verify(token);
        return true;
    }

    /**
     * 获取用户自定义Claim集合
     *
     * @param token
     * @return
     */
    public static Map<String, Claim> getClaims(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        Map<String, Claim> jwt = verifier.verify(token).getClaims();
        return jwt;
    }

    /**
     * 获取过期时间
     *
     * @param token
     * @return
     */
    public static Date getExpiresAt(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        return JWT.require(algorithm).build().verify(token).getExpiresAt();
    }

    /**
     * 获取jwt发布时间
     */
    public static Date getIssuedAt(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        return JWT.require(algorithm).build().verify(token).getIssuedAt();
    }

    /**
     * 验证token是否失效
     *
     * @param token
     * @return true:过期   false:没过期
     */
    public static boolean isExpired(String token) {
        try {
            final Date expiration = getExpiresAt(token);
            return expiration.before(new Date());
        } catch (TokenExpiredException e) {
            // e.printStackTrace();
            return true;
        }

    }

    /**
     * 直接Base64解密获取header内容
     *
     * @param token
     * @return
     */
    public static String getHeaderByBase64(String token) throws Exception {
        if (StringUtils.isEmpty(token)) {
            return null;
        } else {
            byte[] header_byte = decryBASE64(token);
            return new String(header_byte);
        }

    }

    /**
     * 直接Base64解密获取payload内容
     *
     * @param token
     * @return
     */
    public static JSONObject getPayloadByBase64(String token) throws Exception {
        if (StringUtils.isEmpty(token)) {
            return null;
        } else {
            byte[] payload_byte = decryBASE64(token);
            String decode = new String(payload_byte, "UTF-8");

            int i = decode.lastIndexOf("{");
            int j = decode.lastIndexOf("}");
            decode = decode.substring(i, j + 1);

            return JSONObject.parseObject(decode);
        }
    }

    public static Boolean refreshToken(String token, HttpServletResponse response) {
        try {
            String[] tokenSplit = token.split("\\.");

            JSONObject tokenPayloadJson = getPayloadByBase64(tokenSplit[1]);

            Long expL = tokenPayloadJson.getLong("exp");


            Date date = new Date();
            Date expDate = new Date(expL);

            double millisecond = date.getTime() - expDate.getTime() * 1000;
            double time = millisecond / (60 * 1000);

            if (time > SCOKET_EFFECTIVE_TIME_MINUTE) {
                return false;
            }

            Map<String, Object> map = new HashMap<>();
            for (Map.Entry entry : tokenPayloadJson.entrySet()) {
                if (!"exp".equals(entry.getKey()))
                    map.put((String) entry.getKey(), entry.getValue());
            }
            String newToken = sign(map);

            response.setHeader("DAIHAO_TOKEN", newToken);
            response.setHeader("Access-Control-Expose-Headers", "DAIHAO_TOKEN");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /***
     * BASE64解密
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryBASE64(String key) throws Exception {
        return (new Base64Decoder()).decode(key);
    }

    /***
     * BASE64加密
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new Base64Encoder()).encode(key);
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("userId", "1");
        map.put("rose", "DAIHAO");
        map.put("integer", 1111);
        map.put("double", 112.222);
        map.put("Long", 112L);
        map.put("bool", true);
        map.put("date", new Date());
        String token = sign(map); //生成token
        System.out.println("token:" + token);

        System.out.println("校验token:" + verify(token));//验证token是否正确

        String rose = getClaims(token).get("rose").asString(); //使用方法
        System.out.println("rose:" + rose);

        System.out.println("获取签发token时间:" + getIssuedAt(token));
        System.out.println("获取过期时间:" + getExpiresAt(token));

        //Thread.sleep(1000*40);
        System.out.println("检查是否已过期:" + isExpired(token));

        System.out.println("获取头:" + getHeaderByBase64(token));

        System.out.println("获取负荷:" + getPayloadByBase64(token));
    }
}

Token刷新

拦截器中判断失效,返回token

public class TokenInteceptor extends HandlerInterceptorAdapter {

  
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }

 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            R<?> r = null;//自定义返回结果
            String token = request.getHeader("DAIHAO_TOKEN");
            try {
                JwtUtils.verify(token);
            } catch (SignatureVerificationException e) {
                e.printStackTrace();
                r = R.error(401, "invalid signature");
            } catch (TokenExpiredException e) {
                e.printStackTrace();
                if (JwtUtils.refreshToken(token, response)) {
                    return super.preHandle(request, response, handler);
                }
                r = R.error(401, "token expired");
            } catch (AlgorithmMismatchException e) {
                e.printStackTrace();
                r = R.error(401, "inconsistent token algorithm");
            } catch (Exception e) {
                e.printStackTrace();
                r = R.error(401, "token invalidation");
            }
            if (r != null) {
                String json = new ObjectMapper().writeValueAsString(r);
                response.setContentType("application/json;charset=UTF-8");
                Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
                out.write(json);
                out.flush();
                out.close();
                return false;
            }

        return super.preHandle(request, response, handler);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值