参考博客:
JSON Web Token 入门教程
10分钟了解JSON Web令牌(JWT)
一、跨域认证的问题
互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
二、JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
三、jwt生成token
1.引入依赖:
<!-- JWT -->
<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>
2.相关工具类的创建 和 代码实现
/**
* 系统级静态变量
*/
public class SystemConstant {
/**
* token
*/
public static final int JWT_ERRCODE_NULL = 4000; //Token不存在
public static final int JWT_ERRCODE_EXPIRE = 4001; //Token过期
public static final int JWT_ERRCODE_FAIL = 4002; //验证不通过
/**
* JWT
*/
public static final String JWT_SECERT = "8677df7fc3a34e26a61c034d5ec8245d"; //密匙 加解密时用到
public static final long JWT_TTL = 60 * 60 * 1000; //token有效时间
}
import io.jsonwebtoken.Claims;
import lombok.Data;
/**
* jwt验证信息
*/
@Data
public class CheckResult {
private int errCode;
private boolean success;
private Claims claims; //理解为一个认证主体 可通过它获取id,subject主体等
}
下面JwtUtils是重点:
import com.auth0.jwt.interfaces.Claim;
import com.java1234.constant.SystemConstant;
import com.java1234.entity.CheckResult;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.net.ServerSocket;
import java.util.Date;
/**
* jwt加密和解密的工具类
*/
public class JwtUtils {
/** 比如用户输入用户名和密码 若登录合法就生成jwt的一个token 发送给前端 前端存储到localStorage
* 然后用户每次发起请求都从本地获取 携带着一个token送至后台 去验证 从而完成身份的验证
* 签发JWT
* @param id 一般是用户id
* @param subject 一般是用户名 可以是JSON数据 尽可能少
* @param ttlMillis 有效期 这里是1小时
* @return
*/
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) // 主题
.setIssuer("Java1234") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact();
}
/**
* 验证JWT 需要把jwt的token传过来
* @param jwtStr
* @return
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr); // 里面包含有id,subject(用户名),有效期等信息
checkResult.setSuccess(true); //封装进checkResult
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
/**
* 生成加密Key
* 解密时也要用到 因为在刷新token时要通过原来生成的token解密获取用户id 和用户名(subject) 重新生成token
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析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();
}
public static void main(String[] args) throws InterruptedException {
// 后端生成token
String sc=createJWT("1","jack",SystemConstant.JWT_TTL);
System.out.println(sc);
// 后端验证token
CheckResult checkResult = validateJWT(sc);
System.out.println(checkResult.isSuccess()); //true为验证成功
System.out.println(checkResult.getErrCode());// code为0 表示无异常
Claims claims=checkResult.getClaims();
System.out.println(claims);
System.out.println(claims.getId());
System.out.println(claims.getSubject());
// 刷新token 重新生成token 和之前的token不同
//刷新token时要通过原来生成的token解密获取用户id 和用户名(subject) 重新生成token
Claims claims2=validateJWT(sc).getClaims();
String sc2=createJWT(claims2.getId(),claims2.getSubject(),SystemConstant.JWT_TTL);
System.out.println(sc2);
}
}
3.执行结果