详解使用JWT实现单点登录(完全跨域方案)(2)

在安全方面,SWT只能使用HMAC算法通过共享密钥对称签名。但是,JWT和SAML令牌可以使用X.509证书形式的公钥/私钥对进行签名。与签名JSON的简单性相比,使用XML数字签名可能会存在安全漏洞;

JSON解析成对象相比XML更流行、方便。

以下是我实际项目中的应用分析

首先看一下大致的架构及流程图:


主要有以下三步:

项目一开始我先封装了一个JWTHelper工具包(GitHub下载),主要提供了生成JWT、解析JWT以及校验JWT的方法,其他还有一些加密相关操作,稍后我会以代码的形式介绍下代码。工具包写好后我将打包上传到私服,能够随时依赖下载使用;

接下来,我在客户端项目中依赖JWTHelper工具包,并添加Interceptor拦截器,拦截需要校验登录的接口。拦截器中校验JWT有效性,并在response中重新设置JWT的新值;

最后在JWT服务端,依赖JWT工具包,在登录方法中,需要在登录校验成功后调用生成JWT方法,生成一个JWT令牌并且设置到response的header中。

以下是部分代码分享:

JwtHelper工具类:

/** * @Author: Helon * @Description: JWT工具类 * 参考官网:https://jwt.io/ * JWT的数据结构为:A.B.C三部分数据,由字符点"."分割成三部分数据 * A-header头信息 * B-payload 有效负荷 一般包括:已注册信息(registered claims),公开数据(public claims),私有数据(private claims) * C-signature 签名信息 是将header和payload进行加密生成的 * @Data: Created in 2018/7/19 14:11 * @Modified By: */ public class JwtHelper { private static Logger logger = LoggerFactory.getLogger(JwtHelper.class); /** * @Author: Helon * @Description: 生成JWT字符串 * 格式:A.B.C * A-header头信息 * B-payload 有效负荷 * C-signature 签名信息 是将header和payload进行加密生成的 * @param userId - 用户编号 * @param userName - 用户名 * @param identities - 客户端信息(变长参数),目前包含浏览器信息,用于客户端拦截器校验,防止跨域非法访问 * @Data: 2018/7/28 19:26 * @Modified By: */ public static String generateJWT(String userId, String userName, String ...identities) { //签名算法,选择SHA-256 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //获取当前系统时间 long nowTimeMillis = System.currentTimeMillis(); Date now = new Date(nowTimeMillis); //将BASE64SECRET常量字符串使用base64解码成字节数组 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SecretConstant.BASE64SECRET); //使用HmacSHA256签名算法生成一个HS256的签名秘钥Key Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //添加构成JWT的参数 Map<String, Object> headMap = new HashMap<>(); /* Header { "alg": "HS256", "typ": "JWT" } */ headMap.put("alg", SignatureAlgorithm.HS256.getValue()); headMap.put("typ", "JWT"); JwtBuilder builder = Jwts.builder().setHeader(headMap) /* Payload { "userId": "1234567890", "userName": "John Doe", } */ //加密后的客户编号 .claim("userId", AESSecretUtil.encryptToStr(userId, SecretConstant.DATAKEY)) //客户名称 .claim("userName", userName) //客户端浏览器信息 .claim("userAgent", identities[0]) //Signature .signWith(signatureAlgorithm, signingKey); //添加Token过期时间 if (SecretConstant.EXPIRESSECOND >= 0) { long expMillis = nowTimeMillis + SecretConstant.EXPIRESSECOND; Date expDate = new Date(expMillis); builder.setExpiration(expDate).setNotBefore(now); } return builder.compact(); } /** * @Author: Helon * @Description: 解析JWT * 返回Claims对象 * @param jsonWebToken - JWT * @Data: 2018/7/28 19:25 * @Modified By: */ public static Claims parseJWT(String jsonWebToken) { Claims claims = null; try { if (StringUtils.isNotBlank(jsonWebToken)) { //解析jwt claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SecretConstant.BASE64SECRET)) .parseClaimsJws(jsonWebToken).getBody(); }else { logger.warn("[JWTHelper]-json web token 为空"); } } catch (Exception e) { logger.error("[JWTHelper]-JWT解析异常:可能因为token已经超时或非法token"); } return claims; } /** * @Author: Helon * @Description: 校验JWT是否有效 * 返回json字符串的demo: * {"freshToken":"A.B.C","userName":"Judy","userId":"123", "userAgent":"xxxx"} * freshToken-刷新后的jwt * userName-客户名称 * userId-客户编号 * userAgent-客户端浏览器信息 * @param jsonWebToken - JWT * @Data: 2018/7/24 15:28 * @Modified By: */ public static String validateLogin(String jsonWebToken) { Map<String, Object> retMap = null; Claims claims = parseJWT(jsonWebToken); if (claims != null) { //解密客户编号 String decryptUserId = AESSecretUtil.decryptToStr((String)claims.get("userId"), SecretConstant.DATAKEY); retMap = new HashMap<>(); //加密后的客户编号 retMap.put("userId", decryptUserId); //客户名称 retMap.put("userName", claims.get("userName")); //客户端浏览器信息 retMap.put("userAgent", claims.get("userAgent")); //刷新JWT retMap.put("freshToken", generateJWT(decryptUserId, (String)claims.get("userName"), (String)claims.get("userAgent"), (String)claims.get("domainName"))); }else { logger.warn("[JWTHelper]-JWT解析出claims为空"); } return retMap!=null?JSONObject.toJSONString(retMap):null; } public static void main(String[] args) { String jsonWebKey = generateJWT("123", "Judy", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"); System.out.println(jsonWebKey); Claims claims = parseJWT(jsonWebKey); System.out.println(claims); System.out.println(validateLogin(jsonWebKey)); }

AES加密工具类:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/d6fb65bb6e0438668e536e9991895d09.html