基于HTTP协议的软件认证方式有很多,鉴于HTTP协议无状态性,所有的认证方式必须解决HTTP请求有状态性问题,也就是如何创建一个HTTP上下文,将区分哪些HTTP请求来自同一用户操作。传统解决方式是生成HTTP会话,并将会话ID存放在客户端浏览器中的Cookie或者URL重写这两种方式,伴随系统实现方式变化,越来越多的系统使用前后端分离的架构,系统遭受XSS,XSRF攻击的可能性也越大。现在JWT认证被广泛应用。下面介绍JWT认证原理:
Json web token (JWT), 根据官网的定义,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),
JWT加密签名后形成类似aaa.bbb.ccc形式的字符串,是由三部分组成,客户端需要记录在浏览器的本到localStorage,每次请求时将JWT字符串放到HTTP Header中的Authorization里面,服务器单将解析JWT字符串,判断是否被篡改过、是否Token过期等,如果验证通过放行,如果不通过返回错误信息。
JWT由三部分组成
{
"alg": "HS256",//JWT加密算法(alg)
"typ": "JWT" // JWT Token类型(typ)
}
将头部进行base64加密,生成aaa.bbb.ccc的第一部分(aaa部分)
eyJhbGciOiJIUzI1NiJ9
这部分就是存放信息的地方了,可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。
{
"iss": "HK",
"iat": 1441593502,
"exp": 1441594722,
"aud": "",
"sub": "userid"
}
iss: 该JWT的签发者,一般是服务器或系统名称,是否使用是可选的;
iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
aud: 接收该JWT的一方,是否使用是可选的;
sub: 该JWT所面向的用户,userid,是否使用是可选的;
上述属性是JWT规范中定义的,用户还可以自定义属性,其数据结构是JSON
{
"iss": "HK",
"iat": 1441593502,
"exp": 1441594722,
"aud": "",
"sub": "userid",
"role":["admin","user"],
"username":"java"
}
载荷(payload)通过Base64加密后生成aaa.bbb.ccc的第二部分(bbb)
eyJqdGkiOiIxIiwic3ViIjoidXNlciIsImlzcyI6InVzZXIiLCJpYXQiOjE1OTMzMDczOTMsImV4cCI6MTU5MzMwNzQ5M30
HMACSHA256(
base64UrlEncode(header) + "." + //第一部分 aaa
base64UrlEncode(payload), //第二部分 bbb
SECREATE_KEY //服务器端存储的密钥
)
加密算法是在JWT第一部分中定义的加密算法
//加密内容
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
//签名,密钥是存储在服务器端的,不可泄漏给其他人
var signature = HMACSHA256(encodedString, '密钥');
加密后的JWT字符串
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoidXNlciIsImlzcyI6InVzZXIiLCJpYXQiOjE1OTMzMDczOTMsImV4cCI6MTU5MzMwNzQ5M30.RwsRLF1b3LbUvV_qJ7YGeht5JvpNXomijeKto1NI8Uw
本例程只演示生成JWT Token字符串和验证JWT Token字符串,没有集成到登录模块中。
POM.xml添加依赖
<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>
package com.bnzj.cloud.auth;
import java.sql.Date;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.util.encoders.Base64;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
public class JwtUtils {
/**
* 生成JWT
* @param id 唯一ID
* @param subject 主题
* @param ttlMillis 有效微秒数
* @return String 返回JWT JSON 字符串
*
*/
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("user") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact();
}
/**
* 验证JWT Token是否正确
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(Constant.JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode(Constant.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)
{
String id = "1";
String subject = "user";
long ttlMillis = 100000L;
//生成JWT字符串
String jwt = JwtUtils.createJWT(id, subject, ttlMillis);
System.out.println(jwt);
//验证JWT JSON是否合法
System.out.println(JwtUtils.validateJWT(jwt));
}
}
class CheckResult{
boolean isOk;
Claims claims;
String code;
public void setSuccess(boolean b)
{
this.isOk = b;
}
public void setClaims(Claims claims)
{
this.claims = claims;
}
public void setErrCode(String code)
{
this.code = code;
}
public String toString()
{
return "CheckResult[isOk="+isOk+",code="+code+",chaims="+claims+"]";
}
}
class Constant{
//错误代码定义
public static String JWT_ERRCODE_EXPIRE = "expire";
public static String JWT_ERRCODE_FAIL = "fail";
//密钥 Base64编码
public static String JWT_SECERT =
"MIICXAIBAAKBgQCsPS1u1r7pl3VsEK4Tvc2NjNvXhaO2ACh+R889bMo3rz9PQQur\n" +
"bj83pKfFDqcAbj/bhXbcQMG+Gs8WlUFmFNBVr651IL5h1gCrxfyivsQbWYbX5p4J\n" +
"fk52rCMr2QJjnCvyFh1layHZ2gg5/cLQsMR8SqGTHaQmSLCHkoaVq7u8IQIDAQAB\n" +
"AoGARSoH7YNkhI7igzOrg5frTBUtTr2GgRZNLHCPot3l0jiYVq4LDpsl3aVMDZCV\n" +
"tVGQaQmOMmH6quk/EZV7/o8LHUTGcj6i1p2vKBqmAz9M9eUFE/eFctf+k6PAiqKc\n" +
"kFa9HRSBukWne8SZw81qcxVxxw728dvzzahjP/n/mf9c/wUCQQDYjbZyrdK6QH86\n" +
"9P+VhZHeqEBa+rerSMxY3KiCYfRDh8v+C5CNZXdvWVXafu3B/o/U4e7eSyReBomw\n" +
"ybTlwdBrAkEAy5z+o0apRTsuUztAtJvhd0LGsuTyp8TzlI4nFywEHdLzJ47LNnQa\n" +
"yD9P7VBQO4ZeYb+sKC4Qayf6vMR5q94YowJBALSIkA3S89bqZidUkK6qiA1D30L5\n" +
"uZ1GN3Xtn13zI5wY3euQ4JXAfW2K4JQjNTuBaY9kO6t+oXbxpGCKCBFzHrkCQGFI\n" +
"3L2EmIH0mdi4udzRkfOamzeEfpA8YSl8lh7TMBBT50viRSP6a4V8AqNfuUYHmHbZ\n" +
"ztbP05ZvXrTspzm//0MCQHUcBARpGbzBnMI7j8xAjZJcrpmAhiMKjsix96sJPDjK\n" +
"ocy7JgQCH1252bIPiaqO3wjCy+F31IClFNNaw3JQRCA=\n";
}