简介:JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
用处:可以用于登录授权
.
优点
支持跨域
无状态,不需要存储session的信息,减轻服务器的压力。
更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时可以采用token认证方式
无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
JWT的结构
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
我们还可以在有效载荷中自定义自己想要存储的内容,下面的用法中可以看到
对payload进行Base64编码就得到JWT的第二部分
Signature
签名哈希。为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。这个密钥是在服务器端指定的一个密钥,该密钥仅仅为保存在服务器中,并且不能向用户公开。
签名公式:HMACSHA256(base64UrlEncode(header)+“.”+base64UrlEncode(payload),secret)
注意:token的长度是可以变化的,你的负载信息越多,token的长度一般就会越长,所以不会存在负载不同token相同的情况,所以尽量不要在token里面存放过多的信息。
推荐文章
引入依赖jar包
<!--jwt进行登录鉴权的依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
package com.dongmu.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定JWT_SEC秘钥
*
* @param jwtSec jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param ttlMillis jwt过期时间(毫秒)
* @param username 用户名 可根据需要传递的信息添加更多, 因为浏览器get传参url限制,不建议放置过多的参数
* @return
*/
public static String createJWT(String jwtSec, long ttlMillis, String username) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 创建payload的私有声明(根据特定的业务需要添加)
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("username", username);
// 添加payload声明
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
//设置头信息
.setHeaderParam("typ","JWT")
.setHeaderParam("alg","HS256")
//下面设置载荷信息
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(UUID.randomUUID().toString())
// iat: jwt的签发时间
.setIssuedAt(now)
// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串
.setSubject(username)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, jwtSec.getBytes(StandardCharsets.UTF_8));
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
// 设置过期时间
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* Token的解密
*
* @param jwtSec jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String jwtSec, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(jwtSec.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/* public static void main(String[] args) {
String jwt = createJWT("beihaidongmu", 1000 * 60, "dongmu");
System.out.println(jwt);
Claims beihaidongmu = parseJWT("beihaidongmu", jwt);
String id = beihaidongmu.getId();
//获取主体
Object dongmu = beihaidongmu.get("username");
System.out.println(dongmu);
//获取存留到的时间
Date expiration = beihaidongmu.getExpiration();
System.out.println(expiration);
//获取id
System.out.println(id);
}*/
}
在拦截器中对token进行解密。解密成功则可以通过拦截器,否则不予通过。
package com.dongmu.interceptor;
import com.dongmu.controller.LoginController;
import com.dongmu.util.JwtUtil;
import com.dongmu.util.TokenUtil;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Interceptor implements HandlerInterceptor {
Logger logger = LoggerFactory.getLogger(Interceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = TokenUtil.getToken(request);
if (token==null){
logger.info("未通过拦截器,身份验证失败了。");
return false;
}else {
try {
Claims claims = JwtUtil.parseJWT(LoginController.token_password, token);
logger.info("通过拦截器,身份验证成功了。");
return true;
}catch (Exception e){
logger.info("未通过拦截器,身份验证失败了。");
return false;
}
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
最后将拦截器注入到springioc的容器中
package com.dongmu.config;
import com.dongmu.interceptor.Interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InteceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
String path = "/**";
String excludePath="/login";
String swaggerExcludePath[] = {"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"};
HandlerInterceptor interceptor = new Interceptor();
registry.addInterceptor(interceptor).addPathPatterns(path).excludePathPatterns(excludePath).excludePathPatterns(swaggerExcludePath);
}
}