JWT
前言:
jwt作为分布式的考点往往在面试中被聊到:
- 什么是jwt?
- jwt有什么优点呢?
- 都在哪些场景使用过?
这连环三问你能接得住吗?
本文从以上几个面试高频问题入手,带领大家认识和了解jwt。
jwt
什么是jwt?
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
简单点说就是基于json,用于签名和认证的token(令牌)。
结构
JWT通常由三部分组成: 头信息(header), 载体(payload)和签名(signature)。它们之间用圆点(.)连接,构成jwt。
例如:
xxxxx.yyyyy.zzzzz
头信息(header)
头信息描述了JWT使用的签名算法
header = '{"alg":"HS256","typ":"JWT"}'
- token(令牌)的类型是jwt
- HS256 表示使用了 HMAC-SHA256 来生成签名
- 算法可以用其他的,如rsa等
通过base64对json进行编码 获得jwt的第一部分header
载体(payload)
payload = '{"username":"w_rcss","iat":1422779638}'//iat表示令牌生成的时间
通过base64对json进行编码 获得jwt的第二部分payload
签名(signature)
签名需要通过私有的key计算而成
/**
*
* 私有key
*/
key = 'secretkey'
/**
*
* 未签名token
*/
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload) //base64编码过的header和payload
/**
*
* 签名(signature)
*/
signature = HMAC-SHA256(key, unsignedToken)
此时就获取到了signature,base64编码后再拼接(使用"."分隔)起来就是JWT了:
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
# token看起来像这样: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
签名是用于验证消息在传递过程中是否被篡改,对于使用私钥签名的token,还可以验证JWT的发送方是否真实。
聊到这里,相必大家已经对jwt的结构有了清晰的认识,但是要注意,
payload和header用base64进行编码,而不是加密,所以不要在JWT的payload或header中放置敏感信息(如果一定要放,请加密)。
优点
说了这么多,JWT到底有哪些优点呢?
- 相比于session,因为无需保存在服务器,所以不占用服务器内存
- 无状态、可扩展性强:Session无法跨域,比如有3台机器(A、B、C)组成服务器集群,若session存在机器A上,session只能保存在其中一台服务器,此时你便不能访问机器B、C,因为B、C上没有存放该Session。jwt能够验证用户请求合法性,对服务器的影响几乎没有,更有利于服务器扩展
- jwt支持了跨域访问
使用场景
虽然相比session有诸多优点,但是并不适合用jwt来代替session。接下来我们看看jwt适用场景有哪些。
JWT适合一次性操作的认证:
服务B你好, 服务A告诉我,我可以操作<JWT内容>, 这是我的凭证(即JWT)
- 服务A负责认证用户身份,并颁布一个很短过期时间的JWT给浏览器
- 浏览器在向服务B的请求中带上该JWT
- 服务B通过验证该JWT来判断用户是否有权执行该操作。这样,服务B就成为一个安全的无状态的服务了。
授权:
这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
信息交换:
JWT是在各个服务之间安全传输信息的好方法。
- 使用公钥/私钥对儿 - 可以确定请求方是合法的。
- 使用标头和有效负载计算签名,还可以验证内容是否未被篡改。
代码示例
那如何代码实现jwt呢?
- 编写JWT(Java Web Token)操作类
public class JavaWebToken {
private static Logger log = LoggerFactory.getLogger(JavaWebToken.class);
//该方法使用HS256算法和Secret:bankgl生成signKey
private static Key getKeyInstance() {
//将用ApiKey签署我们的JavaWebToken
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("bankgl");
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
return signingKey;
}
//使用HS256签名算法和生成的signingKey最终的Token,claims是有效载体
public static String createJavaWebToken(Map<String, Object> claims) {
return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact();
}
//解析Token,同时也能验证Token,当验证失败返回null
public static Map<String, Object> parserJavaWebToken(String jwt) {
try {
Map<String, Object> jwtClaims =
Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody();
return jwtClaims;
} catch (Exception e) {
log.error("jwt验证失败");
return null;
}
}
}
- 编写登录Conreoller,在服务器端给客户返回token
public LoginStatusMessage checkUserAndPassword(
@RequestParam(value = "username", required = true) String username,
@RequestParam(value = "password", required = true) String password,User user,HttpServletRequest request)throws Exception{
User u=new User();
//登录成功
if((u=userService.checkUsernameAndPassword(user))!=null){
Map<String, Object> m=new HashMap<String, Object>();
m.put("userid",user.getUserid());
String token=JavaWebToken.createJavaWebToken(m);
System.out.println(token);
LoginStatusMessage lsm=new LoginStatusMessage();
lsm.setUser(u);
lsm.setToken(token);
return lsm;
};
// 登录失败,返回Null
return null;
}
}
- 在拦截器中验证token
String token = request.getParameter("token");
if(JavaWebToken.parserJavaWebToken(token) != null){
//表示token合法
return true;
}else{
//token不合法或者过期
return false;JWT
}
总结
JWT相比Session减轻了服务器的内存压力,在分布式上可扩展性也更强,通过签名保证了传输数据的有效性和合法性,有诸多优点但是更适用于一次性的认证,并不能完全替代Session。具体的使用还需要根据具体的生产环境来具体分析。