JWT介绍
JWTs是JSON对象的编码表示。JSON对象由零或多个名称/值对组成,其中名称为字符串,值为任意JSON值。JWT有助于在clear(例如在URL中)发送这样的信息,可以被信任为不可读(即加密的)、不可修改的(即签名)和URL - safe(即Base64编码的)。
使用场景
Authorization (授权) :一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
Information Exchange (信息交换):对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
JWT的组成
Header:标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型;
Claims:Claims包含想要签署的任何信息;
JSON Web Signature (JWS):在Header中指定的使用该算法的数字签名和声明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Header: { "alg": "HS256", "typ": "JWT" }
Claims: { "sub": "1234567890", "name": "John Doe", "admin": true }
Signature: base64UrlEncode(Header) + "." + base64UrlEncode(Claims),
|
JWT的框架:JJWT
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
开始上手
demo-jjwt
1 2 3 4 5 6
| <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jjwt.version}</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| @Slf4j public class JwtUtils { public static final String ACCESS_TOKEN_HEADER = "Authorization"; public static final String REFRESH_TOKEN_HEADER = "Refresh-Token"; public static final String TOKEN_PREFIX = "Bearer ";
private static final Long DEF_RENEWAL_TIMESTAMP = 7 * 24 * 60 * 60 * 1L;
private static final Long DEF_CHECK_INTERVAL = 1800L;
private static final Long DEF_EXPIRATION = 24 * 60 * 60 * 1000L;
private static final String SECRET = "demo.jjwt.token";
private static final String DEF_ISS = "https://www.keaijing.com";
private static final String U_DATA = "U";
public static String issueJwt(Long phoneNum) { Assert.notNull(phoneNum, "phoneNum is null"); return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(phoneNum + "") .setIssuer(DEF_ISS) .setIssuedAt(DateUtil.date()) .setExpiration(DateUtil.date(System.currentTimeMillis() + DEF_EXPIRATION)) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); }
public static boolean checkJwtExpiration(String jwt) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(jwt.trim()) .getBody(); } catch (RuntimeException e) { return true; } if (null == claims) { return true; } try { return claims.getExpiration().before(new Date()); } catch (RuntimeException ex) { log.warn("jwt 过期啦~~~~"); } return true; }
public static void main(String[] args) { String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzYzMzgyMzk4MSIsImlzcyI6Imh0dHBzOi8vd3d3LmtlYWlqaW5nLmNvbSIsImlhdCI6MTY0NDIyMjAwMSwiZXhwIjoxNjQ0MzA4NDAxfQ.WBX6S1HQZqZtUsjH7XGKlIRWoNCmSSLq7nn24jXSbo4PCTIqSj-Uo6XiEjhda5-Vo91_lhnwSXCb2qVctFNqjQ";
log.info("" + checkJwtExpiration(token)); } }
|
进阶操作
JwtAuthInfo
在用户Token里存储用户信息,方便解析后直接进行校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
@Data @Builder public class JwtAuthInfo {
private String usrId;
private String accountNumber;
private String nickName;
private Long checkInterval;
private Long expiration; }
|
JwtAuthManager
创建JwtAuthManager用户权限管理类,用于校验Token格式、签发Toekn等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
@Slf4j public class JwtAuthManager { public static final String ACCESS_TOKEN_HEADER = "Authorization"; public static final String TOKEN_PREFIX = "Bearer ";
private static final Long DEF_CHECK_INTERVAL = 1800L;
private static final Long DEF_EXPIRATION = 24 * 60 * 60 * 1L;
private static final String SECRET = "demo.jjwt.token";
private static final String DEF_ISS = "https://www.keaijing.com";
private static final String U_DATA = "U";
public static JwtAuthInfo getJwtAuthentication(HttpServletRequest request) { String token = getJwt(request); return getJwtAuthInfo(token); }
private static String getJwt(HttpServletRequest request) { String tokenHeader = request.getHeader(JwtAuthManager.ACCESS_TOKEN_HEADER); if (tokenHeader == null || !tokenHeader.startsWith(JwtAuthManager.TOKEN_PREFIX)) { return null; } String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, ""); if (Strings.isEmpty(token)) { return null; } return token.trim(); }
public static JwtAuthInfo getJwtAuthInfo(String token) { if (null == token) { log.error(" header 中未找到 jwt信息"); throw new BizException(MessageCodeEnum.NOT_LOGIN); } token = token.trim(); JwtAuthInfo JwtAuthInfo = JwtAuthInfo(token); if (null == JwtAuthInfo) { log.error(" jwt 解析失败"); throw new BizException(MessageCodeEnum.NOT_LOGIN); } return JwtAuthInfo; }
public static JwtAuthInfo JwtAuthInfo(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token.trim()) .getBody(); } catch (Exception e) { log.warn("token 检查失败,无法刷新token。"); return null; } Gson gson = new Gson(); Object obj = claims.get(U_DATA); if (null == obj) { return null; } return gson.fromJson(obj.toString(), JwtAuthInfo.class); }
public static String issueJwt(String userId, Long checkInterval, Long expiration) { Assert.notNull(userId, "userId is null"); Assert.notNull(checkInterval, "checkInterval is null"); Assert.notNull(expiration, "expiration is null"); JwtAuthInfo tokenInfo = JwtAuthInfo.builder() .usrId(userId) .checkInterval(checkInterval) .expiration(expiration) .build(); return issueJwt(tokenInfo); }
public static String issueJwt(JwtAuthInfo JwtAuthInfo) { Gson gson = new Gson(); String u = gson.toJson(JwtAuthInfo); HashMap<String, Object> map = new HashMap<>(); map.put(U_DATA, u); return Jwts.builder() .signWith(SignatureAlgorithm.HS512, SECRET) .setClaims(map) .setIssuer(DEF_ISS) .setSubject(String.valueOf(JwtAuthInfo.getUsrId())) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + JwtAuthInfo.getExpiration() * 1000)) .compact(); }
public static boolean checkJwtExpiration(String jwt) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(jwt.trim()) .getBody(); } catch (RuntimeException e) { return true; } if (null == claims) { return true; } try { return claims.getExpiration().before(new Date()); } catch (RuntimeException ex) { log.warn("jwt 过期啦~~~~"); } return true; }
public static String refresh(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token.trim()) .getBody(); } catch (Exception e) { log.warn("token 检查失败,无法刷新token。"); return null; } try { claims.getExpiration().before(new Date()); } catch (RuntimeException ex) { log.warn("jwt 过期啦~~~~"); return null; } Object u = claims.get(U_DATA); if (null == u) { log.warn("jwt 无效 ~~~~"); return null; } Gson gson = new Gson(); JwtAuthInfo tokenInfo = gson.fromJson(u.toString(), JwtAuthInfo.class); Long expiration = tokenInfo.getExpiration(); HashMap<String, Object> map = new HashMap<>(1); map.put(U_DATA, u); return Jwts.builder() .signWith(SignatureAlgorithm.HS512, SECRET) .setClaims(map) .setIssuer(DEF_ISS) .setSubject(String.valueOf(tokenInfo.getUsrId())) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .compact(); }
public static JwtAuthInfo createJwtAuthInfo(String usrId) { return JwtAuthInfo.builder() .usrId(usrId) .checkInterval(DEF_CHECK_INTERVAL) .expiration(DEF_EXPIRATION) .build(); } }
|
JwtAuthorizationManager
创建jwt认证类,用于校验账号在线状态以及解析Token里的用户信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
@Component @Slf4j public class JwtAuthorizationManager {
public static final String ACCOUNT_AUTH_ONLINE_KEY = "demo:jwt:";
@Autowired RedisCacheServiceI redisCacheService;
public String authorization(HttpServletRequest request) { String token = getJwt(request); if (null == token) { return null; } return authorization(token); }
private String getJwt(HttpServletRequest request) { String tokenHeader = request.getHeader(JwtAuthManager.ACCESS_TOKEN_HEADER); if (tokenHeader == null || !tokenHeader.startsWith(JwtAuthManager.TOKEN_PREFIX)) { return null; } String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, ""); if (Strings.isEmpty(token)) { return null; } return token.trim(); }
public String authorization(String tokenHeader) { String token = tokenHeader.replace(JwtAuthManager.TOKEN_PREFIX, ""); JwtAuthInfo jwtAuthInfo = getJwtAuthInfo(token); online(token, jwtAuthInfo); return jwtAuthInfo.getAccountNumber(); }
public void online(String token, JwtAuthInfo jwtAuthInfo){
String cacheKey = ACCOUNT_AUTH_ONLINE_KEY + jwtAuthInfo.getAccountNumber(); String online = redisCacheService.getStr(cacheKey);
if (null == online) { log.error("账号未登录"); throw new BizException(MessageCodeEnum.NOT_LOGIN); } if (!online.equals(token)) { log.error("账号其他设备登录"); throw new BizException(MessageCodeEnum.USER_OFFLINE); } }
private JwtAuthInfo getJwtAuthInfo(String token) { if (null == token) { log.error(" header 中未找到 jwt信息"); throw new ParamException(MessageCodeEnum.NOT_LOGIN); } token = token.trim(); JwtAuthInfo jwtAuthInfo = JwtAuthManager.getJwtAuthInfo(token); if (null == jwtAuthInfo) { log.error(" jwt 解析失败"); throw new ParamException(MessageCodeEnum.NOT_LOGIN); } return jwtAuthInfo; } }
|
鉴权思路
自定义注解;
请求进入到切面,获取请求参数,记录请求路径及请求参数;
获取方法中声明的注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
private MonitorAnnotation getDeclaredAnnotation(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Class<?> targetClass = joinPoint.getTarget().getClass(); Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); Method objMethod = null; try { objMethod = targetClass.getMethod(methodName, parameterTypes); } catch (NoSuchMethodException e) { return null; } return objMethod.getDeclaredAnnotation(MonitorAnnotation.class); }
|
如果注解鉴权属性为true,则解析用户Token中的信息;
1 2 3 4 5 6 7 8 9 10 11 12 13
| MonitorAnnotation monitorAnnotation = getDeclaredAnnotation(joinPoint); if (null != monitorAnnotation) { if (monitorAnnotation.auth()) { if (httpRequest) { String accountNumber = jwtAuthorizationManager.authorization(request); if (Validator.isNull(accountNumber)) { throw new ParamException(MessageCodeEnum.NOT_LOGIN); } } } }
|