基于restful进行前后端分离的项目由于是基于json进行数据传输,前端既可以是web、android也可以是IOS,但是这样就产生了一个问题,http协议是无状态的,restful也是无状态的,用户状态信息该如何保存?
restful接口的用户认证有几种方案:
1.使用session+cookie进行用户认证。
2.使用token进行认证,如果用户登陆成功,则通过某种规则(比如userName+timestamp)生成一个token,服务端将这个token缓存起来,并设置失效时间,前端每次请求接口都通过http head带着这个token,并在服务端进行验证。
使用session存在的问题:
session和cookie是为了解决http无状态的方案。session是用户保存在服务器中的状态信息,cookie中则保存jsessionId,请求服务器时,服务器读取jsessionId从而确定用户的身份信息。
1.session+cookie用在restful接口上破坏了其“无状态”的特性,session运行时都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。这也是restful最致力于通过“无状态”解决的问题。如果使用session,那么restful也就没有什么意义了。
2.session降低了系统扩展性。用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
3.cookie不安全,很容易导致跨站请求伪造攻击(CSRF)。
token存在的问题:
但这种方式仍然存在问题,比如如何确定token的过期时间?如果token的有效期过短,很容易造成用户用着用着就掉线的情况,降低用户体验。但目前看来好像并没有太好的办法,只能尽量延长token的有效期,或者每隔一次前端自动向服务端请求一次token。
JWT(json web token)
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT构成
JWT由三部分构成:
head:头部
payload:负载信息
signature:认证签名
head
jwt的头部由两部分构成,一是jwt声明,二是加密算法声明。
{
'typ': 'JWT',
'alg': 'HS256'
}
将上面的json进行base64编码,即为jwt第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload
顾名思义,payload用于保存有效信息。
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
signature
signature由上面两部分组成,它需要base64加密后的header和base64加密后的payload连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。其中的secret为私钥,保存在服务端。
使用:
将生成的jwt放在http请求头上即可。
public class TokenUtil {
/**
* java io.jsonwebtoken框架
*/
public static SecretKey generalKey() {
/**
*生成私钥
*/
byte[] encodedKey = new byte[0];
encodedKey = Base64Util.decode(Constant.JWT_SECERT);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 签发JWT
*
* @param id
* @param subject
* @param ttlMillis 有效时间
* @return
* @throws Exception
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)
//签发用户
.setSubject(subject)
//签发时间
.setIssuedAt(now)
//签名算法,私钥
.signWith(signatureAlgorithm, secretKey);
//设置失效时间
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate);
}
return builder.compact();
}
/**
* 验证JWT
*
* @param jwtStr
* @return
*/
public static ResultInfo validateJWT(String jwtStr) {
ResultInfo checkResult = null;
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult = new ResultInfo(-100000);
} catch (ExpiredJwtException e) {
checkResult = new ResultInfo(-500);
} catch (SignatureException e) {
checkResult = new ResultInfo(-501);
} catch (Exception e) {
checkResult = new ResultInfo(-501);
}
return checkResult;
}
/**
* 解析JWT字符串
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}