什么是JWT?
Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519),该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
为什么需要JWT?
当我们开发前后端分离项目时,要求我们对用户会话状态要进行一个无状态处理,那我们知道普通的web项目用于管理用户会话的往往是 session,用户每次用服务器认证成功后,服务器都会发送一个 sessionid 给用户,session 是保存在服务端的,服务器通过 session 分辨用户,进行权限认证等一系列操作。每次请求完后,都会在 响应头中返回 sessionid 给浏览器,浏览器会将 sessionid 存储在 cookie 中,以后的每次请求都会在请求头中带上这个 sessionid 信息,服务器就会根据这个 sessionid 作为索引获取到具体的 session。
那上面讲述的场景就会出现一个痛点,当我们前后端分离后,我们的前端项目和后端项目分开部署,甚至会用到 nginx 来代理转发,也就是说前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用 session 每次携带 sessionid 到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。还有就是 CSRF(跨站伪造请求攻击)攻击,session 是基于 cookie 进行用户识别的, cookie 如果被截获,用户就会很容易受到跨站请求伪造的攻击。还有就是 sessionid 就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现 session 共享机制。不方便集群应用。
JWT的应用场景
JWT 就是上述痛点的解决方案之一,客户端在请求服务端进行登录操作时,服务端验证用户的账号和密码,验证成功后生成 token 返回给客户端,之后浏览器的每一次操作都会在请求头中带上这个 token,服务器会验证该 token 信息,验证成功后才会返回资源给浏览器。
JWT 的开销非常小,可以轻松在不同的域名中传递,所以在单点登录(SSO)中用到比较广泛,信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
整合JWT
1、引入 JWT 依赖
com.auth0
java-jwt
3.8.2
2、JWT 工具类
- 工具类中包含:创建 token,验证 token,获取用户 id 等
package com.asurplus.common.jwt; import cn.hutool.core.collection.CollectionUtil; import com.asurplus.common.utils.ResponseResult; import com.asurplus.common.utils.SpringContextUtils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; / * Jwt工具类,生成JWT和认证 * * @author dongk * @date 2021-02-05 11:10:08 */ @Slf4j public class JwtUtil { / * 密钥 */ private static final String SECRET = "asurplus_secret"; / * 过期时间(单位:秒) / private static final long EXPIRATION = 3600L; / * 生成用户token,设置token超时时间 * * @param userId * @param password * @return */ public static String createToken(Integer userId, String account, String password) { Map
map = new HashMap<>(); map.put("alg", "HS256"); map.put("typ", "JWT"); String token = JWT.create() // 添加头部 .withHeader(map) // 放入用户的id .withAudience(String.valueOf(userId)) // 可以将基本信息放到claims中 .withClaim("account", account) .withClaim("password", password) // 超时设置,设置过期的日期 .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION * 1000)) // 签发时间 .withIssuedAt(new Date()) // SECRET加密 .sign(Algorithm.HMAC256(SECRET)); return token; } / * 获取用户id */ public static Integer getUserId() { HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); // 从请求头部中获取token信息 String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { return null; } try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); if (null != jwt) { // 拿到我们放置在token中的信息 List
audience = jwt.getAudience(); if (CollectionUtil.isNotEmpty(audience)) { return Integer.parseInt(audience.get(0)); } } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTVerificationException e) { e.printStackTrace(); } return null; } / * 校验token并解析token */ public static ResponseResult verity() { HttpServletRequest request = SpringContextUtils.getHttpServletRequest(); // 从请求头部中获取token信息 String token = request.getHeader("Authorization"); if (StringUtils.isBlank(token)) { return ResponseResult.error(401, "用户信息已过期,请重新登录"); } try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(token); if (null != jwt) { // 拿到我们放置在token中的信息 List
audience = jwt.getAudience(); if (CollectionUtil.isNotEmpty(audience)) { return ResponseResult.success("认证成功", audience.get(0)); } } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (JWTVerificationException e) { e.printStackTrace(); } return ResponseResult.error(401, "用户信息已过期,请重新登录"); } }
我们可以看出 token 的创建用到了如下参数:
- 算法:HS256
- 类型:jwt
- withAudience:向有效负载添加特定的受众(“aud”)声明,我们可以在这里放入一些用户的信息,例如:用户 id
- withClaim:添加自定义索赔值,我们使用用户的账户和密码进行一起加密生成 jwt
- withExpiresAt:超时时间设置,超时 token 将失效
- withIssuedAt:签发时间,一般设置为当前时间
- sign:签名,我们可以自定义签名和算法
JWT 的验证:
- 首先我们先要获取 HttpServletRequest 请求对象,工具类放在下面了
- 从请求头中获取 token 信息,根据 key(Authorization)获取 value 值
- 然后使用签名进行算法加密得到 jwt 的验证对象,JWTVerifier.verify(token) 用来验证 token 的正确性
- 我们还可以从验证得到的 DecodedJWT 对象中获取我们创建 token 的时候放入的信息,例如:用户 id
获取 HttpServletRequest 对象工具类
package com.asurplus.common.utils; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; / * SpringContext工具类 * * @Author Lizhou */ @Component public class SpringContextUtils implements ApplicationContextAware { / * 上下文对象实例 */ private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtils.applicationContext = applicationContext; } / * 获取applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } / * 获取HttpServletRequest */ public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } public static String getDomain() { HttpServletRequest request = getHttpServletRequest(); StringBuffer url = request.getRequestURL(); return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString(); } public static String getOrigin() { HttpServletRequest request = getHttpServletRequest(); return request.getHeader("Origin"); } }
自定义 JSON 对象工具类
package com.asurplus.common.utils; import com.asurplus.common.enums.BaseEnums; import com.asurplus.common.enums.StatusEnums; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; / * 单例模式返回接口相应数据 * * @Author Lizhou */ @Data public class ResponseResult implements Serializable { @ApiModelProperty(value = "状态码") private Integer code; @ApiModelProperty(value = "提示信息") private String msg; @ApiModelProperty(value = "返回数据") private Object data; private static ResponseResult resultData(Integer code, String msg, Object data) { ResponseResult res = new ResponseResult(); res.setCode(code); res.setMsg(msg); res.setData(data); return res; } / * 成功 */ public static ResponseResult success() { return resultData(200, "操作成功", null); } / * 成功 */ public static ResponseResult success(String msg) { return resultData(200, msg, null); } / * 成功 */ public static ResponseResult success(Object data) { return resultData(200, "操作成功", data); } / * 成功 */ public static ResponseResult success(String msg, Object data) { return resultData(200, msg, data); } / * 失败 */ public static ResponseResult error() { return resultData(500, "操作失败", null); } / * 失败 */ public static ResponseResult error(Integer code) { return resultData(code, null, null); } / * 失败 */ public static ResponseResult error(Integer code, String msg) { return resultData(code, msg, null); } / * 失败 */ public static ResponseResult error(String msg) { return resultData(500, msg, null); } / * 失败 */ public static ResponseResult error(Object data) { return resultData(500, "操作失败", data); } / * 失败 */ public static ResponseResult error(Integer code, String msg, Object data) { return resultData(code, msg, data); } / * 失败 */ public static ResponseResult error(BaseEnums enums) { return resultData(enums.getCode(), enums.getMsg(), null); } }
至此,我们在 SpringBoot 中整合 JWT 实现 Token 验证已经完成。
如您在阅读中发现不足,欢迎留言!!!
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/223218.html原文链接:https://javaforall.net
