Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的, 特别适用于分布式站点的单点登陆(SSO)场景。JWT的声明通常被用来在身份提供者和服务提供者间传递被认证的用户身份信息, 以便于从资源服务器获取资源,也能够增长一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。java
JWT是由三段信息构成的,将这三段信息文本用.连接一块儿就构成了Jwt字符串。就像这样:web
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLnlKjmiLciLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlzcyI6IuetvuWPkeiAhSIsImlkIjoic2xtMTIzIiwiZXhwIjoxNTU1MDQ5NzI1LCJ1c2VybmFtZSI6InNsbSJ9.x5ZoTW5NS4wBCIK61v4YCGi8bsveifBwnsMNBQz8s_s
JWT共有三部分组成,第一部分称之为头部(header),第二部分称之为荷载(payload),第三部分是签名(signature)。算法
header:安全
{ 'typ': 'JWT',//声明类型,这里是jwt 'alg': 'HS256'}
typ:声明了类型服务器
alg:声明了加密方式网络
header通过Base64加密后:分布式
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload:ide
Payload 部分也是一个 JSON 对象,用来存放实际须要传递的数据。JWT 规定了7个官方字段,供选用post
iss (issuer):签发人exp (expiration time):过时时间sub (subject):主题aud (audience):受众nbf (Not Before):生效时间iat (Issued At):签发时间jti (JWT ID):编号
除了官方字段,你还能够在这个部分定义私有字段,下面就是一个例子。ui
{ "sub": "123456", "name": "admin", "admin": true}
Payload通过Base64加密后:
eyJzdWIiOiLnlKjmiLciLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlzcyI6IuetvuWPkeiAhSIsImlkIjoic2xtMTIzIiwiZXhwIjoxNTU1MDQ5NzI1LCJ1c2VybmFtZSI6InNsbSJ9
JWT 默认是不加密的,任何人均可以读到,因此不要把秘密信息放在这个部分。
Signature是对前两部分进行的签名防止数据被篡改
首先,须要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。而后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名之后,把 Header、Payload、Signature 三个部分拼成一个字符串,每一个部分之间用"点"(.)分隔,就能够返回给用户。
怎么使用JWT?
通常是在请求头里加入Authorization,并加上Bearer标注:
headers: { 'Authorization': 'Bearer ' + token }
JWT如何应用在Springboot中?
首先引入pom依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
实现WebMvcConfigurer接口配置拦截全部URl
/** * @Author: slm * @CreateTime: 2019-04-11 10:07 * @Description: 拦截设置 */public class InterceptorConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**"); } public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); }}
我在项目中使用了全局异常处理和加入了两个自定义注解
LoginToken:表明须要对接口进行验证
PassToken:表明不须要进行
建立AuthenticationInterceptor实现HandlerInterceptor接口对请求进行拦截
public class AuthenticationInterceptor implements HandlerInterceptor { private UserService userService;
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token // 若是不是映射到方法直接经过 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //检查是否有passtoken注释,有则跳过认证 if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } //检查有没有须要用户权限的注解 if (method.isAnnotationPresent(LoginToken.class)) { LoginToken userLoginToken = method.getAnnotation(LoginToken.class); if (userLoginToken.required()) { // 执行认证 if (token == null) { throw new AppException("9999","无token,请从新登陆"); } // 获取 token 中的 user id Boolean userId; try { userId = JWTUtil.verifyToken(token,"123456"); } catch (JWTDecodeException j) { throw new AppException("401","无权操做"); } if(userId){ return true; }else { throw new AppException("401","无权操做"); } } } return true; }
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { }}
配置完成之后简单模拟一下用户数据:
建立JWTUtil类对token进行生成和校验
4jpublic class JWTUtil { private static final String EXP = "exp";
private static final String PAYLOAD = "payload";
/** * 加密生成token * * @param object 载体信息 * @param maxAge 有效时长 * @param secret 服务器私钥 * @param <T> * @return */ public static String createToken(User object, long maxAge, String secret) { try { final Algorithm signer = Algorithm.HMAC256(secret);//生成签名 String token = JWT.create() .withIssuer("签发者") .withSubject("用户")//主题,科目 .withClaim("username", object.getUsername()) .withClaim("id", object.getId()) .withClaim("password",object.getPassword()) .withExpiresAt(new Date(System.currentTimeMillis() + maxAge)) .sign(signer); System.out.println(token); return Base64.getEncoder().encodeToString(token.getBytes("utf-8")); } catch (Exception e) { log.error("生成token异常:", e); return null; } }
/** * 解析验证token * * @param token 加密后的token字符串 * @param secret 服务器私钥 * @return */ public static Boolean verifyToken(String token, String secret) { try { Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); DecodedJWT jwt = verifier.verify(new String(Base64.getDecoder().decode(token),"utf-8")); return true; } catch (IllegalArgumentException e) { throw new AppException("9999",e.getMessage()); } catch (JWTVerificationException e) { throw new AppException("9999",e.getMessage()); } catch (UnsupportedEncodingException e) { throw new AppException("9999",e.getMessage()); } }}
建立接口调用接口
注意:登陆接口加入了PassToken表示不进行校验
最后启动项目,调用登陆接口
调用getMsg进行验证
登陆成功!
https://mp.weixin.qq.com/s/6XzkhVta4QT88fPSdRIwdw