持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
作者平台:
| CSDN:blog.csdn.net/qq_41153943…
| 知乎:www.zhihu.com/people/1024…
| GitHub:github.com/JiangXia-10…
| 微信公众号:1024笔记
本文一共2144字,预计阅读9分钟
前言
在很多的登录注册的业务场景下都需要对用户登录身份进行校验,传统的用户身份验证是将服务无法与用户身份验证分开。通常过程如下:
-
首先用户向服务器发送用户名和密码,等到验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。然后服务器会向用户返回session_id,session信息都会写入到用户的Cookie,这样用户的每个后续请求都将通过在Cookie中取出session_id传给服务器,服务器收到session_id之后会对比之前保存的数据,从而确认用户的身份。但是这种策略存在一个最大的问题就是结构单一,没有采用分布式,所以无法进行横向的拓展,那么一旦系统比较大各个系统有关联,就无法使用。
-
所以目前一种比较灵活的解决方案就是采用自包含令牌,客户端保存数据,但服务器不保存会话数据。 今天要说的JWT是这种解决方案之一。
什么是JWT
JWT的全程是JSON Web Token,是一种加密的token,JWT也是目前流行的跨域认证解决方案,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT的规则
JWT的规则是在服务器在进行了身份验证之后,会生成一个JSON对象并将其发送回客户端,当该客户端与服务器进行通信时,客户端在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。并且为了防止用户篡改数据,服务器将在生成对象时添加签名。该过程中服务器并不会保存任何的会话数据,即服务器变为无状态,从而使其更容易扩展。生成的json对象如下所示:
{
"sub": "54457851524",
"name": "Jiangxia",
"admin": true
}
JWT的组成部分
JWT对象是由头部信息、载荷、签名哈希三段信息构成的,并且将该三段信息文本采用"."进行连接在一起,就构成了一个JWT字符串对象。在jtw加密解密网站:jwt.io/,有对于jwt进行加密…
JWT头部信息是一个描述JWT元数据的JSON对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示的是令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
载荷又叫有效载荷,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
并且除以上默认字段外,我们还可以自定义私有字段,如下例:
{
"sub": "696565623565",
"name": "jiangxia",
"admin": true
}
需要注意的是,JWT对象默认是不进行加密的,所以在默认的不加密的情况下,应该不要写入敏感数据 ,但是也可以在生成原始的Token之后,使用密钥再进行一次加密。JSON对象也使用Base64 URL算法转换为字符串保存。
签名哈希部分则是对上面两部分数据的签名,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
JWT在项目中的使用
首先需要在项目的pom文件中添加jwt相关的依赖:
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
然后需要创建一个jwt工具:JwtUtils首先需要设置有效期以及加密的密钥:
public static final long EXPIRE = 1000 * 60 * 60 * 24;
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
然后创建一个生成jwttoken的方法:
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
然后创建一个方法判断某个token是否存在:
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
在创建一个根据jwttoken获取会员id的方法:
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
总结
JWT是目前较为主流的一种灵活的身份验证解决方法。其使用是客户端接收服务器返回的JWT,并将其存储在Cookie或localStorage中。此后,该客户端将在与服务器交互中都会携带该JWT信息。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。