JWT令牌实现跨域身份验证

JWT令牌实现跨域身份验证

  • JWT介绍
  • JWT结构
  • JWT验证过程
  • JWT实例

JWT介绍

JSON Web Token(JWT)是目前最流行的基于token的跨域身份验证解决方案。
在JWT极高的安全特性下保证了token的不可伪造和不可篡改,从而可实现易扩展、轻存储、高安全的无状态、分布式的Web应用授权。
本质上JWT是一个独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以及可以存储任何其他信息(自包含)。任何人都可以轻松读取和解析,并使用密钥来验证真实性。
JWT的特点:

  1. 存储性能
  2. JWT将用户的状态分散到了客户端中保存,可以远远减少服务端的压力。相比较于通过Session方式实现身份验证有许多弊端:Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态,一般还需借助nosql和缓存机制来实现session的存储,如果是分布式应用还需session共享。
  3. .兼容性
    支持移动设备,支持跨程序调用,Cookie 是不允许垮域访问的,而 Token 则不存在这个问题。
  4. 单点登录
    JWT能轻松的实现单点登录,因为用户的状态已经被传送到了客户端。token 可保存自定义信息,如:用户基本信息,web服务器用key去解析token,就获取到请求用户的信息了。我们也可以配置它以便包含用户拥有的任何权限。这意味着每个服务不需要与授权服务交互才能授权用户。
  5. 可拓展性
    因为JWT的信息是保存在客户端的,在分布式系统中JWT不存在进行用户身份验证时的Session共享问题,只要拿到用户的信息对其进行JWT方式的验证就可以判断他的身份合法性,那么就很方便进行服务端的扩展。
  6. 安全性
    因为JWT会通过数字签名实现身份验证的校验,所以他可以防止数据信息被篡改,达到很高的安全性。

JWT结构

JWT包含三个由点分隔的部分,它们分别是:

Header(头部)
Header通常由两部分组成:令牌的类型,即JWT。和用于签名加密的算法名称,如HMAC SHA256或RSA。Header部分的JSON被Base64Url编码,形成JWT的第一部分

{
“alg”: “HS256”,
“typ”: “JWT”
}

Payload(载荷)
Payload中放传输的信息,用户信息等用于传输的信息。Playload部分的JSON被Base64Url编码,形成JWT的第二部分。

{
“iss”: “发送人”,
“sub”: 主题”,
“aud”: “接受人”,
“exp”:”过期时间”,
“iat”:”签发时间”
“id”:”用户id”,
“name”:”用户姓名”
}

Signature(签名)
Signature用来验证发送请求者身份,由前两部分加密形成。要创建签名部分,您必须采用编码标头,编码有效载荷,秘钥,标头中指定的算法并签名。Signature部分的JSON被Base64Url编码,形成JWT的第三部分。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

其中secret时加密用的密钥

最后:将三者经过Base64Url编码后的内容通过点分隔后合成一个字符串放入token,并将token放入cookies中传给前端保存,服务器只要保存统一的密钥用于解密就可以。
JWT令牌实现跨域身份验证_第1张图片

JWT验证过程

  1. 初次登录:用户初次登录,输入用户名密码
  2. 密码验证:服务器从数据库取出用户名和密码进行验证
  3. 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT,保存密钥在服务端
  4. 返还JWT:服务器的将token放在cookie中将JWT返回,客户端保存JWT信息
  5. 带JWT的请求:以后客户端发起请求,带上cookie中的token信息,服务端获取JWT根据密钥校验身份,通过则往下走
    JWT令牌实现跨域身份验证_第2张图片

JWT实例

  1. 登陆方法
/**
 * 登录相关
 *
 * @author 杨强光
 * @version LoginController.java, v0.1 2019年3月01日 下午9:17
 */

public String login(HttpServletRequest request, HttpServletResponse response,
                        @RequestParam("username") String username,
                        @RequestParam("password") String password) {

        logger.info("[LoginController.login] staff login, username:{}", username);

        StaffEntity staffEntity = staffService.fetchStaffByUsername(username, null);

        //空处理
        if (username == null || "".equals(username) ) {
            String message = ErrorMessageFormat.getMessage("not.exist.staff", new String[] {username});
            throw new NotExistException(message);
        }

        if (staffEntity == null || StaffInfo.StaffStatus.DELETED.getStatus().equals(staffEntity.getIsDeleted()) ) {
            String message = ErrorMessageFormat.getMessage("not.exist.staff", new String[] {username});
            throw new NotExistException(message);
        }

        String pw = staffEntity.getPassword();
        boolean validatePassword;
        try {
            password = Utils.EncoderByMd5(password);
            validatePassword = PBKDF2WithHmacSHA1.validatePassword(password, pw);
        } catch (Exception e) {
            logger.error(String.format("[LoginController.login] validate password failed, username:%s", username), e);
            throw new CloudDeskServerException();
        }

        if (!validatePassword) {
            logger.error(String.format("[LoginController.login] password is not correct, username:%s", username));
            throw new LoginPwdException();
        }

        //初始化token
        JwtToken jwtToken = new JwtToken();
        //设置payload
        Map payload = new HashMap();
        payload.put(Constant.PAYLOAD_UID_KEY, staffEntity.getStaffId());
        payload.put(Constant.PATH_VARIABLE_KEY, staffEntity.getTenantId());
        jwtToken.setPayload(payload);
        //设置过期时间
        long expireMillis = DateUtils.getGMTTimeInMillis() + Constant.JWT_TOKEN_EXPIRE_TIME;
        jwtToken.setExpire(DateUtils.gmtTimeToDate(expireMillis));

        //加密token
        String token = Utils.encodeSignatureToken(jwtToken, cookieEncryptKey);

        //将token存入cookie
        CookieUtils.saveCookie(request, response, Constant.COOKIE_STAFF_TOKEN_KEY, token);

        return ReturnJacksonUtils.resultOk();
    }
  1. 生成数字签名
    public static String encodeSignatureToken(JwtToken jwtToken, String secret) {
        JWTCreator.Builder builder = JWT.create().withHeader(new HashMap());

        for (Map.Entry entry : jwtToken.getPayload().entrySet()) {
            builder.withClaim(entry.getKey(), entry.getValue());
        }

        try {
            return builder.withExpiresAt(jwtToken.getExpire()).sign(Algorithm.HMAC256(secret.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new CloudDeskException("400", "Token Expired", "200");
        }

    }
  1. 通过拦截器验证每次请求的合法性
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        if (handler instanceof ResourceHttpRequestHandler) {
            return true;
        }

        HandlerMethod handleMethod = (HandlerMethod)handler;
        StaffLoginRequired staffLoginRequired = handleMethod.getMethodAnnotation(StaffLoginRequired.class);
        if (staffLoginRequired == null) {
            return Boolean.TRUE;
        }

        String staffUId = null;
        try {
            //拦截器验证JWT
            staffUId = uIdService.getStaffUid(request, response);
        } catch (Exception e) {
            logger.error("getStaffUid, access path " + request.getRequestURI());
        }
        hostHolder.setUid(staffUId);
        org.slf4j.MDC.put("UID", hostHolder.getUid());
        return Boolean.TRUE;
    }
  public String getStaffUid(HttpServletRequest request, HttpServletResponse response) {
        String variableUrl = "modify-password";
        //获取租户id
        Map pathVariables = (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        if (pathVariables == null || pathVariables.get(Constant.PATH_VARIABLE_KEY) == null) {
            logger.error("[StaffLoginRequired.preHandle] tenantId is not exist in path.");
            throw new MissingParameter("tenantId");
        }
        String tenantId = (String)pathVariables.get(Constant.PATH_VARIABLE_KEY);

        String message = null;
        String token = CookieUtils.getCookie(request, Constant.COOKIE_STAFF_TOKEN_KEY);
        if (token == null) {
            logger.error("[StaffLoginRequired.preHandle] token is not exist in cookie.");
            message = ErrorMessageFormat.getMessage("not.exist.token", null);
            throw new NotExistException(message);
        }

        //解密token
        JwtToken jwtToken = Utils.decodeSignatureToken(token, cookieEncryptKey);
        Map payload = jwtToken.getPayload();
        if (!tenantId.equals(payload.get(Constant.PATH_VARIABLE_KEY))) {
            throw new InvalidParameter(Constant.PATH_VARIABLE_KEY);
        }

        //验证有效期
        long expire = jwtToken.getExpire().getTime();
        long now = DateUtils.getGMTTimeInMillis();
        if (now > expire) {
            logger.error("[StaffLoginRequired.preHandle] token expire.");
            message = ErrorMessageFormat.getMessage("not.exist.token", null);
            throw new NotExistException(message);
        } else if (now + Constant.JWT_TOKEN_REFRESH_TIME > expire) {
            //刷新token有效期
            expire += Constant.JWT_TOKEN_REFRESH_TIME;
            jwtToken.setExpire(DateUtils.gmtTimeToDate(expire));
            token = Utils.encodeSignatureToken(jwtToken, cookieEncryptKey);
            CookieUtils.saveCookie(request, response, Constant.COOKIE_STAFF_TOKEN_KEY, token);
        }

        //判断登陆者是否存在
        String uid = payload.get(Constant.PAYLOAD_UID_KEY);
        StaffInfo staffInfo = staffService.fetchStaffInfo(tenantId, uid);
        if (staffInfo == null) {
            logger.error("[StaffLoginRequired.preHandle] staff is not exist, staffId:{}, tenantId:{}", uid, tenantId);
            message = ErrorMessageFormat.getMessage("not.exist.staff", null);
            throw new NotExistException(message);
        }

        if (!checkModifyPassword || request.getRequestURI().contains(variableUrl)
            || staffInfo.getIsModifyPassword() == PasswordIsModify.YES.getTarget()) {
            return uid;
        } else {
            logger.error("[StaffLoginRequired.preHandle] staff password is not modified, staffId:{}, tenantId:{}",
                uid, tenantId);
            throw new PasswordNotModifiedException();
        }
    }

你可能感兴趣的:(信息安全)