JSON Web Token(代码篇)

@RestController
@RequestMapping("/api/v2/jwt")
public class JwtApi {

    private static Logger logger = LoggerFactory.getLogger(JwtApi.class);

    @Autowired
    private JwtService jwtService;
    @Autowired
    private AccountsService accountsService;

    /**
     * 创建jwt Token
     *
     * @return
     */
    @RequestMapping(path = "/getJwtToken", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public JSONObject getJwtToken(HttpServletRequest httpServletRequest) {
        JSONObject jb = new JSONObject();
        Long userId = Long.valueOf(httpServletRequest.getAttribute(WebStarterApplicationFilter.USER_ID_KEY).toString());
        String loginName = accountsService.findLoginNameByUserId(userId);
        if (StringUtils.isEmpty(loginName)) {
            jb.put("code", "500");
            jb.put("message", "用户未登录");
            return jb;
        }

        // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
        Map<String, Object> claims = new HashMap<String, Object>();

        logger.info("getJwtToken loginName is -> " + loginName);

        claims.put("loginName", loginName);

        return jwtService.createJwtToken(claims);
    }

    /**
     * 验证jwt Token
     *
     * @return
     * @throws Exception
     */
    @RequestMapping(path = "/checkJwtToken", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public JSONObject checkJwtToken(String param) throws Exception {
        JSONObject jb = new JSONObject();
        logger.info("/api/v2/jwt/checkJwtToken[MX-AUTHORIZATION]>>>" + param);
        if (StringUtils.isEmpty(param)) {
            ErrorJson errorJson = new ErrorJson("20000", "jwt token无效或不存在");
            logger.warn("/api/v2/jwt/checkJwtToken>>>" + errorJson.toJson());
        }
        Claims claims = jwtService.parseJWT(param);
        logger.info("/api/v2/jwt/checkJwtToken[MX-AUTHORIZATION]>>>" + JSONObject.toJSONString(claims));
        if (StringUtils.isEmpty(claims)) {
            ErrorJson errorJson = new ErrorJson("20000", "jwt token无效或不存在");
        }
        jb.put("code", "200");
        jb.put("message", "success");
        jb.put("claims", claims);
        return jb;
    }
}

 

 


@Service
public class JwtService {

    private static Logger logger = LoggerFactory.getLogger(JwtService.class);

    // 密钥所在文件夹路径
    private static String caUrl = ApplicationConfig.getConfig("jwt.ca.url") + "/";

    /**
     * jwt 实现
     * 基于需求实现 私钥加密 公钥解密,同时实现 RS256 算法, 注意:此算法要求加密密钥格式为 PKCS8
     * <p>
     * 生成的 jwt 可去 jwt.io 验证
     * <p>
     * 此类中提供 加密方法以及解密方法,解密方法中注意如果 jwt 过期会跑出异常 需要处理
     */
    public JSONObject createJwtToken(Map<String, Object> claims) {
        // 请求时间
        Long requestTime = System.currentTimeMillis();
        // 过期时长
        int overdue = Integer.valueOf(ApplicationConfig.getConfig("jwt.overdue"));
        Calendar ca = Calendar.getInstance();
        ca.add(Calendar.DATE, overdue);
        // 过期差值
        long dueTime = ca.getTimeInMillis() - requestTime;

        logger.info("createHrJwt is start -> requestTime : " + requestTime + "; overdue : " + overdue);
//        // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
//        Map<String, Object> claims = new HashMap<String, Object>();
//        // 生成用户的唯一标示,根据用户登录名MD5加密生成
//        String login_name = currentUserVo.getLogin_name();
//        String sub = AesHelper.doAES(login_name, Cipher.ENCRYPT_MODE);
//
//        claims.put("sub", sub);//用户中心中用户的唯一标示
//        claims.put("username", login_name);//用户中心中,用户的完整名称(包含所在域名称)
//        String preferred_username = login_name.substring(login_name.indexOf("\\") + 1);
//        claims.put("preferred_username", preferred_username);//用户名
//        claims.put("ds_uid", currentUserVo.getExt());//ENT_USER_DB中,用户的唯一标示
//        claims.put("name", currentUserVo.getName());//用户名称
//        claims.put("phone_number", currentUserVo.getPhone());//用户注册手机号码
//        claims.put("email", currentUserVo.getEmail());//用户注册邮箱
//        claims.put("exp", ca.getTimeInMillis());//过期时间
        claims.put("iat", requestTime);//签发日期

//        // 过网关 追加
//        claims.put("email_verified", true);//邮箱是否已验证
//        claims.put("roles", "");// jwt 常用的标准定义
//        claims.put("iss", "");//谁发的
//        claims.put("aud", "");//发给谁
//        claims.put("phone_number_verified", true);//电话是否已验证
//        claims.put("title", "");//职务id
//        claims.put("access_level", "");//谁请求的 客户端or扫码

        // 签发时间
        Date iat = new Date(requestTime);
        // 过期时间
        Date exp = new Date(ca.getTimeInMillis());

        // 生成JWT
        String jwt = null;

        logger.info("createHrJwt is start -> iat : " + iat + "; exp : " + exp + "; claims : " + claims.toString());
        try {
            jwt = createJWT(iat, exp, dueTime, claims);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 返回构造
        JSONObject jb = new JSONObject();
        jb.put("jwt", jwt);
        //jb.put("dueTime", dueTime / 1000);//过期差值,用于前端判断多久过期,单位:秒
        jb.put("iatTime", requestTime / 1000);//签发日期,单位:秒

        jb.put("code", "200");
        jb.put("message", "success");
        return jb;
    }

    /**
     * 创建 Jwt
     *
     * @param iat     签发时间
     * @param exp     过期时间
     * @param dueTime
     * @param claims
     * @return
     * @throws Exception
     */
    private static String createJWT(Date iat, Date exp, long dueTime, Map<String, Object> claims) throws Exception {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256; //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
//        long nowMillis = System.currentTimeMillis();//生成JWT的时间
//        Date now = new Date(nowMillis);

        Map<String, Object> header = new HashMap<String, Object>();
        header.put("kid", "JWT_HR");
        header.put("typ", "JWT");  //令牌统一写为JWT
        header.put("alg", "RS256");//签名算法。默认值是RS256。

        //获取私钥
        PrivateKey key = getPKCS8PrivateKey();
//        SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        //下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
                .setHeader(header)
                .setClaims(claims)          //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setIssuedAt(iat)           //iat: jwt的签发时间
                .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥

//        if (dueTime >= 0) {
//            long expMillis = nowMillis + dueTime;
//            Date exp = new Date(expMillis);
//            builder.setExpiration(exp);     //设置过期时间
//        }
        return builder.compact();           //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
    }

    /**
     * @return
     * @throws Exception
     */
    private static PrivateKey getPKCS8PrivateKey() throws Exception {
        // Remove markers and new line characters in private key
        String pem = initPem("");
        if (null != pem && !"".equals(pem)) {
            String realPK = pem.replaceAll("-----BEGIN PRIVATE KEY-----", "")
                    .replaceAll("-----END PRIVATE KEY-----", "")
                    .replaceAll("\n", "");
            byte[] b1 = Base64.decodeBase64(realPK);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(spec);
        }
        return null;
    }

    /**
     * 解密jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String jwt) throws Exception {
        //String publicKeyStr = initPem("publickey.pem");
        String publicKeyStr = "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhfJgMRPQwep1FlXE/RaYumSWWecqUJzSkqNgjAmIxXsyMNguSa0WBiCTAozMvnb55Ck32Mr67ueDu1yoysJIvvS/MSlLSF6nak0GRZA/Ng75Fp5AdNK5dXOyfFSxkzm8BUM96vDq2fpBzLfC\n" +
                "-----END PUBLIC KEY-----";
        logger.info("publicKeyStr is " + publicKeyStr);
        //获取公钥
        if (null != publicKeyStr && !"".equals(publicKeyStr)) {
            String pem = publicKeyStr.replaceAll("-----BEGIN PUBLIC KEY-----", "")
                    .replaceAll("-----END PUBLIC KEY-----", "")
                    .replaceAll("\n", "");
            logger.info("pem is " + pem);

            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(pem));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
            logger.info("publicKey is " + publicKey);

            //使用公钥,进行验签
            Claims claims = Jwts.parser()  //得到DefaultJwtParser
                    .setSigningKey(publicKey)         //设置签名的秘钥
                    .parseClaimsJws(jwt).getBody();//设置需要解析的jwt

            return claims;
        }
        return null;
    }

    /**
     * 如果传入的参数为空, 默认获取私钥加密
     * 如果传入的参数为空, 默认获取公钥解密
     */
    private static String initPem(String pemName) {
        if ("".equals(pemName)) {
            pemName = ApplicationConfig.getConfig("jwt.pemName");
        } else {
            pemName = caUrl + pemName;
        }
        logger.info("pemName is " + pemName);
        String pk = caUrl + pemName;
        logger.info("pk is " + pk);
        File file = new File(pk);

        String pem = null;
        // 私钥不存在
        if (!file.exists()) {
            return pem;
        }

        String encoding = "UTF-8";
        Long filelength = file.length();
        byte[] filecontent = new byte[filelength.intValue()];
        try {
            FileInputStream in = new FileInputStream(file);
            in.read(filecontent);
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            pem = new String(filecontent, encoding);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return pem;
    }
}

 


版权声明:本文为changhenshui1990原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。