最开始是将登陆的用户信息存放在Session,用户在客户端的标识就存在COOKIE中,每次请求接口,在request的header携带cookie就OK。毕竟Cookie目的就是为每一个用户提供不同的身份认证。而使用Session存储用户信息其实也是利用Cookie(Session是利用Cookie进行信息处理的!),当用户首先进行了请求后,服务端就在用户浏览器上创建了一个Cookie,当这个Session结束时,其实就是意味着这个Cookie就过期了(而且Cookie数据保存在客户端,Session数据保存在服务器端。只不过Cookie和Session都是用来跟踪浏览器用户身份的会话方式而已啦!)。故事重点是Cookie跨域请求会失效(因为cookie 无法被自动携带至其他域名),然后老是被前端凶,这样的日子太悲伤了......那就换Token咯!谁让我这么喜新厌旧嘞。开工,因为是Java的项目,然后网上比较多的是JWT(json web token),那就先来个maven吧:
<!--jwt工具类-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
我找好几个maven尝试,可就这个比较简单,我这个人真的是怕麻烦!所以你不要老是哭!我不会哄你的(委屈就抬头看看天空吧,不是它这么大可以包容你所以的委屈,而是你抬头了眼泪就不会流出来了呀!)继续正题,来串代码(BUG):
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtDemo {
//HS256加密算法
private final static SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
//服务器端自定义的secret
private final static String secret = "secretKeyzbyjsonwebtoken";
// 过期时间(单位秒)/ 2小时
private final static Long access_token_expiration = 7200L;
//jwt签发者 乱写的
private final static String jwt_iss = "zby";
//jwt所有人
private final static String subject = "zby";
/**
* 创建jwt
* @param user_id 用户id
* @param user_phone 用户手机号
* @return 返回一个token
*/
public static String generateJwtToken(String user_id,String user_phone){
// Jwt的头部承载
// 可不设置 默认格式是{"alg":"HS256"}
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
//Jwt的载荷
Map<String,Object> claims = new HashMap<>();
//私有声明, 自定义数据
//我一般会携带用户的id或者phone
claims.put("id",user_id);
claims.put("phone", user_phone);
//标准中注册的声明 一旦写标准声明赋值之后,就会覆盖了那些标准的声明
/* iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
*/
claims.put("iss", jwt_iss);
//在payload添加各种标准声明和私有声明
// 这里其实就是new一个JwtBuilder,设置jwt的body
return Jwts.builder()
.setHeader(map) // 头部信息
.setClaims(claims) // 载荷信息
.setId(UUID.randomUUID().toString()) // 设置jti(JWT ID):是JWT的唯一标识,从而回避重放攻击。
.setIssuedAt(new Date()) // 设置iat: jwt的签发时间
.setExpiration(new Date(System.currentTimeMillis() + access_token_expiration * 1000)) // 设置exp:jwt过期时间 7200秒 2小时
.setSubject(subject) //设置sub:代表这个jwt所面向的用户,所有人
.signWith(SIGNATURE_ALGORITHM, secret)//设置签名:通过签名算法和秘钥生成签名
.compact(); // 开始压缩为xxxxx.yyyyy.zzzzz 格式的jwt token
}
/**
*
* @param jwt token
* @return 返回载荷信息
*/
private static Claims getClaimsFromJwt(String jwt) {
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
/**
*
* @param jwt token
* @return 返回jsonObject 包含获取token的状态
*/
public static JSONObject validateJWT(String jwt){
JSONObject jsonObject = new JSONObject();
Claims claims;
try {
claims = getClaimsFromJwt(jwt);
jsonObject.put("Success", true);
jsonObject.put("Claims", claims);
} catch (ExpiredJwtException e) {
// token失效 过期
jsonObject.put("Success", false);
jsonObject.put("ErrCode", 1005);
e.printStackTrace();
} catch (Exception e) {
// 未登录
jsonObject.put("Success", false);
jsonObject.put("ErrCode", 1004);
e.printStackTrace();
}
return jsonObject;
}
public static void main(String[] args) {
String jwtToken = generateJwtToken("1","15842961234");
System.out.println("JWT Token "+ jwtToken);
Claims claims = getClaimsFromJwt(jwtToken);
System.out.println(claims.get("phone"));
System.out.println(validateJWT(jwtToken));
}
}
这个一个Demo,嘻嘻嘻!签名算法备注:JWT的签名算法有HS256和RS256,都是用来加密的,只是RS256是一种非对称算法 (采用SHA-256 的 RSA 签名) ,HS256 是一种对称算法(采用SHA-256 的 HMAC 签名)
对称算法
当使用某个密钥加密数据时,也可以使用该密钥对数据进行解密。(这里只使用了一个密钥)
不对称算法
首先使用公钥来加密消息,然后再使用公钥对应的私钥来解密它。(这里使用了两个密钥)
所以这就是网上大部分说使用HS256安全性高一点,因为你可以控制谁使用这个密钥,但是如果你无法控制客户端,或者你无法保护密钥,RS256将更适合,因为用户只需要知道公共密钥就好了的原因。好像是乱说的.....
我现在使用的是HS256哦!会在用户登录分配一个token给用户,然后存Redis中,生成token时会设置过期时间,但是一般我也会在存Redis时设置相同的过期时间。下面是Interceptor的一段判断请求接口是否携带token
String token = request.getHeader("TOKEN");
//判断token
if (TextUtils.isEmpty(token)){
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("处于未登录状态,请先登录");
response.getWriter().close();
return false;
}else {
JSONObject jsonObject = JwtDemo.validateJWT(token);
if (jsonObject.getBooleanValue("Success")){
Claims claims = (Claims) jsonObject.get("Claims");
String id = claims.get("id").toString();
//这里已经拿到用户id,然后就可以继续你代码里面的正常操作
}else {
int RtnValue = jsonObject.getIntValue("ErrCode");
String msg = "会话已过期,请重新登录";
switch (RtnValue) {
case 1005:
msg = "签名已过期,请重新登录";
break;
case 1004:
msg = "正处于未登录状态,请先登录";
break;
default:
break;
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(msg);
response.getWriter().close();
return false;
}