一 JWT是什么
JWT是JSON Web Token的简称,是目前流行的跨域的认证解决方案,作为JSON对象在各方传递信息的凭证,它是由服务器端签发的,而且是带签名的,在不知道服务器端私钥的情况下,无法伪造的。服务器端通过数字签名来保证数据的完整性和有效性。
1.1 为什么需要
为什么需要这种token认证那,从http这个无状态的协议说起。早期http是短连接的,也就是一个报文一个tcp请求,得到返回的内容后就关闭了。(现在就算支持keep-alive的情况,如果用一个tcp代表一个用户的情况下,如果用户稍有断网,要重新登陆体验笔记差)这就造成一个问题,如果在网站上,比如购物网站上跟踪每个用户信息,就需要区分不同用户的请求内容,早期用session来实现,交互过程如下:
- 用户在浏览器登陆,发送username和password给服务器。
- 服务器收到后进行验证,验证成功后,将用户信息,比如用户对象保存到session中,且发送一个叫JSESSIONID的cookie给用户浏览器。
- 用户下次请求会带上这个JSESSIONID,服务器以此来识别不同的用户。
这种session认证缺点:
-
session 存在服务器上,意味着用户下次请求还必须请求在这台服务器上,才能拿到授权的资源,限制了负载均衡能力。如果多个系统认证无法实现单点登陆功能。
-
session 保存在服务器上,用户越来越多后,在服务器占的内存也会越来越多,甚至有内存溢出的风险。
1.2 JWT
JWT是一个字符串,用于鉴权和认证。包含头信息,有效荷载,签名三部分组成,中间用.号分隔,如下格式:
header.payload.signature
- header
header = Base64URL({"alg":"HS256","typ":"JWT"))
说明:
声明类型alg:表示令牌的类型,JWT令牌统一写JWT。
签名密玥算法alg:HMAC256,RSA等。
Base64URL:header头需要进行Base64URL处理,Base64URL使用"-"和"_"替换:"+"和"/",不使用"=" ,使用这个原因是因为URL中含有这些字符。
- payload
载荷信息,就是json格式封装的一些key和value,标准定义七个:
{
"iss": "John Wu JWT",
"iat": 1441593502,
"exp": 1441594722,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"from_user": "B",
"target_user": "A"
}
iss:JWT签发者
exp:到期时间UNIX时间戳
sub:JWT对应的用户
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT,防止回放攻击。
payload中一般保存非敏感信息,同样用Base64URL处理,如果需要保存敏感信息,需要对整个token进行加密。
- signature
签名,是通过上面选择的算法对上述两部分进行签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
secret 为密钥,签名这里面并不是为了保密,而是为了验证此token是本服务器签发的,而且还可以防止篡改,保证数据的完整性。
官网示意图如下:
encode和decode示意图
二 如何使用JWT
2.1 JWT的处理流程:
- 初次登陆,输入用户和密码请求发到服务器。
- 服务器取出数据库中的用户名和密码进行验证。
- 验证通过后,生成token,返回给客户端。
- 以后客户端所有请求都会在请求头中:
Authentication字段都要有值,为JWT。 - 服务器验证此token是否合法。
-
将用户访问的信息返回给客户端
JWT请求流程
注意,如果使用token,涉及到跨域的话,需要设置:
Access-Control-Allow-Origin: *
所谓的跨域,即一个请求访问不是同一个域名或端口,这些就属于跨域,为安全期间,一般默认不准许跨域的。
java使用JWT例子:
pom文件引入相关包:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
例子:
public class JWTUtil {
private static final long EXPIRE_TIME = SpringContextUtil.getBean(FebsProperties.class).getShiro().getJwtTimeOut() * 1000;
/**
* 校验 token是否正确
*
* @param token 密钥
* @param secret 用户的密码
* @return 是否正确
*/
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
verifier.verify(token);
return true;
} catch (Exception e) {
log.info("token is invalid{}", e.getMessage());
return false;
}
}
/**
* 从 token中获取用户名
*
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
log.error("error:{}", e.getMessage());
return null;
}
}
/**
* 生成 token
*
* @param username 用户名
* @param secret 用户的密码
* @return token
*/
public static String sign(String username, String secret) {
try {
username = StringUtils.lowerCase(username);
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
log.error("error:{}", e);
return null;
}
}
}
三 JWT有什么缺点
- 默认不加密,如果传私密信息不够安全。
- 如果采用cookie保存仍然可以被伪造,建议放在localstorage中。
- 如果token信息过长,会影响性能,因为每个请求都需要带token信息。
- token颁发后,不能通过服务器端让令牌失效,只能等过期,当然这个可以通过保存在redis中,超时日期设置过token过期时间,验证时候先从redis中获取,如果没有直接判断非法,不通过普通的token验证流程验证。
网友评论