jwt(json web token)是为了在网络应用应用环境间传递声明而执行的一种基于JSON的开放标准。JWT的声明一般备用来在身份提供者和服务提供者间传递被认证的用户信息,以便于从资源服务器获取资源。
cookie+session登录:http无状态,可以采用Session方式保持与客户端的回话。服务器保存session,并把sessionId传递给客户端, 客户端会把sessionId写入到cookie中,以后每次请求都会携带这个sessionId。这种模式保存在内存中,在分布式环境下面临session共享问题,当然spring-session或者cookie和redis可以实现共享session。
而JWT不是这样的,只需要在服务器端生成token(例如放到redis里),客户端保存token,每次请求携带这个token, 以便服务端认证解析。
jwt 分三部分构成:头部header,载荷payload,验证signature
(1)header:
jwt header承载两部分信息:声明类型,typ这里是jwt; 声明加密的算法,通常hmac sha256
{ 'typ':'JWT', 'alg':'HS256' }
(2)有效载荷(playload)
载荷就是存放有效信息的地方:token的签发者iss,签发时间iat,过期时间exp以及我们存储的信息。
(3)signature jwt的第三部分就是签证信息。签证信息由三部分组成header(base64后), payload(base64后) secret。将base64加密后的header和base64加密后的payload连接组合成字符串,然后通过header中声明的加密方式进行加盐secret组合加密后构成。
3.1:从jwt生成,jwt校验,jwt服务器存储,jwt客户端存储,访问校验五方面说明白
引入maven依赖
io.jsonwebtoken
jjwt
0.9.1
注意服务端要支持跨域:这个token必须要在每次请求时发送给服务器,它应该保存在请求头中,另外,服务器要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin:*
(1)jwt生成: 用uuid来唯一表示一个用户,同时将uuid放入map中进行加密后返回jwtToken
// loginUser.setToken(UUID.randomUUID().toString());
private String createJWTToken(LoginUser loginUser) {
Map claims = new HashMap<>();
claims.put(LOGIN_USER_KEY, loginUser.getToken());// 放入一个随机字符串,通过该串可找到登陆用户
String jwtToken = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance())
.compact();
return jwtToken;
}
private Key getKeyInstance() {
if (KEY == null) {
synchronized (TokenServiceJWTImpl.class) {
if (KEY == null) {// 双重锁
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(tokenConfig.getJwtSecret());
KEY = new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName());
}
}
}
return KEY;
}
@Getter
@Setter
@ToString
public class Token implements Serializable {
private String token;
/** 登陆时间戳(毫秒) */
private Long loginTime;
}
在用户登陆时将为每一个用户生成一个uuid,加密之后形成Jwttoken, 同时要把登陆信息以及过期时间赋值给LoginUser放到redis中,最后封装成token对象返回也页面。
private void cacheLoginUser(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + tokenConfig.getExpireSeconds() * 1000);
// 根据uuid将loginUser缓存
redisTemplate.boundValueOps(getTokenKey(loginUser.getToken())).set(loginUser, tokenConfig.getExpireSeconds(), TimeUnit.SECONDS);
}
(2)jwt校验
从请求头中获取token即(jwttoken),然后用jjwt解析得到标识用户的uuid。通过uuid去redis中获取loginUser信息。
private String getUUIDFromJWT(String jwtToken) {
if ("null".equals(jwtToken) || StringUtils.isBlank(jwtToken)) {
return null;
}
try {
Map jwtClaims = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwtToken).getBody();
return MapUtils.getString(jwtClaims, LOGIN_USER_KEY);
} catch (ExpiredJwtException e) {
log.error("{}已过期", jwtToken);
} catch (Exception e) {
log.error("{}", e);
}
return null;
}
(3)jwt服务器存储
jwt解析请求头的token,得到uuid, 通过uuid去redis中拿到用户。jwt解析token过程中会报已过期异常。
(4)jwt客户端存储
登陆后服务器返回token,将token保存到localStorage。即localStorage.setItem("token", data.token);
修改ajax请求以后每次请求都会带上token。
localStorage简介:
在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。
$.ajaxSetup({
cache : false,
headers : {
"token" : localStorage.getItem("token")
},
error : function(xhr, textStatus, errorThrown) {
var msg = xhr.responseText;
var response = JSON.parse(msg);
var code = response.code;
var message = response.message;
if (code == 400) {
layer.msg(message);
} else if (code == 401) {
localStorage.removeItem("token");
location.href = '/login.html';
} else if (code == 403) {
console.log("未授权:" + message);
layer.msg('未授权');
} else if (code == 500) {
layer.msg('系统错误:' + message);
}
}
});
(5)访问校验
在拦截器中根据请求头获取token,然后去redis中获取用户loginUser。若资源必须登陆或者需要判断权限, 那么根据实际情况进行封装redis中存放的loginUser信息,进行认证和权限校验。
如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。
XSS攻击代码过滤移除任何会导致浏览器做非预期执行的代码,这个可以采用一些库来实现(如:js下的js-xss,JAVA下的XSS HTMLFilter,PHP下的TWIG);如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;
采用HTTP-Only Cookies通过设置Cookie的参数: HttpOnly; Secure 来防止通过JavaScript 来访问Cookie;如何在Java中设置cookie是HttpOnly呢?Servlet 2.5 API 不支持 cookie设置HttpOnlyhttp://docs.oracle.com/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/建议升级Tomcat7.0,它已经实现了Servlet3.0http://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/http/Cookie.html
//设置cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
//设置多个cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");
//设置https的cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");