一、前言
最近负责支付宝小程序后端项目设计,这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计,会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱,redis在业务高峰期不稳定,容易出现问题,总会出现用户会话丢失、超时的问题。之前听过JWT相关的设计,决定尝试一下。
二、什么是JWT
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。虽然JWT可以加密以在各方之间提供保密,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签名令牌时,签名还证明只有持有私钥的一方是签署它的一方。
更多参考:Introduction to JSON Web Tokens
三、JWT优势
JWT支持多种方式的信息加密,验证时并不需要依赖缓存。支持存储用户非敏感信息、超时、刷新等操作,JWT由前端在用户发送请求时自动放入header中,可以有效避免CSRF攻击,用来维护服务端和用户会话再好也不过了。
四、JWT工具类
public class JwtUtils { /** * 创建token * * @param claim claim中为userId * @param secret 创建token密钥 * @return token */ public static String createToken(Map claim, String secret) { long expirationDate = AlipayServiceAppletConstants.EXPIRATION_DATE; LocalDateTime nowTime = LocalDateTime.now(); return Jwts.builder().setClaims(claim) .setSubject("AlipayApplet") //设置token主题 .setIssuedAt(localDateTimeToDate(nowTime)) //设置token发布时间 .setExpiration(getExpirationDate(nowTime, expirationDate)) // 设置token过期时间 .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 将LocalDateTime转换为Date * * @param localDateTime * @return Date */ public static Date localDateTimeToDate(LocalDateTime localDateTime) { ZoneId zoneId = ZoneId.systemDefault(); ZonedDateTime zdt = localDateTime.atZone(zoneId); return Date.from(zdt.toInstant()); } /** * 获取token过期的时间 * * @param createTime token创建时间 * @param calendarInterval token有效时间间隔 * @return */ public static Date getExpirationDate(LocalDateTime createTime, long calendarInterval) { LocalDateTime expirationDate = createTime.plus(calendarInterval, ChronoUnit.MINUTES); return localDateTimeToDate(expirationDate); } /** * JWT 解析token是否正确 * * @param token * @return * @throws Exception */ public static Claims parseToken(String token) throws ExpiredJwtException { Claims claims = Jwts.parser() .setSigningKey(AlipayServiceAppletConstants.ALIPAY_APPLET_SECRET) .parseClaimsJws(token) .getBody(); return claims; } /** * token 刷新: * 1.小于TIME_OUT直接通过; * 2.大于TIME_OUT 小于FORBID_REFRES_HTIME需要刷新; * 3.超过FORBID_REFRES_HTIME 直接返回禁用刷新; * * @param oldToken * @return */ public static String refresh(String oldToken) { long tokenDurationTime = AlipayServiceAppletConstants.EXPIRATION_DATE;//token持续时间/分钟 long tokenRefreshDurationTime = AlipayServiceAppletConstants.ALIPAY_APPLET_FORBID_REFRES_HTIME;//token允许刷新时间/分钟 try { getExpirationDate(oldToken); } catch (ExpiredJwtException e) { try { long expirationTime = TimeUnit.MINUTES.convert(e.getClaims().getExpiration().toInstant().getEpochSecond(), TimeUnit.SECONDS); long nowTime = TimeUnit.MINUTES.convert(Instant.now().getEpochSecond(), TimeUnit.SECONDS); long tokenTimeout = nowTime - expirationTime; /*2.大于TIME_OUT 小于FORBID_REFRES_HTIME需要刷新*/ if (tokenTimeout >= tokenDurationTime &