JWT
概念
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).
该token被设计为紧凑且安全的, 特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,
以便于从资源服务器获取资源,
也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT的主要应用场景
身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。
由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。
信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式。
由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
优点
1.简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。
JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:
JWT共有三部分组成,第一部分称之为头部(header),第二部分称之为荷载(payload),第三部分是签名(signature)。
header
{ 'typ': 'JWT', //声明类型,这里是jwt typ:声明了类型
'alg': 'HS256' alg:声明了加密方式
}
经过base64加密之后
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子
{
"sub": "123456",
"name": "admin",
"admin": true
}
JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
Signature是对前两部分进行的签名防止数据被篡改
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。
然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
如何使用JWT
一般是在请求头里加入Authorization,并加上Bearer标注:
headers: { 'Authorization': 'Bearer ' + token }
实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
自定义两个注解
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解,用来跳过验证
@Target({ElementType.METHOD,ElementType.TYPE})//作用在方法,类,接口等。。
@Retention(RetentionPolicy.RUNTIME)//注解的保留位置
public @interface PassToken {
boolean required()default true;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//自定义注解需要进行token验证
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required()default true;
}
自定义一个实体类User
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String id;
String userName;
String password;
}
定义生成token的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.stereotype.Service;
@Service
public class TokenService {
//自定义token的生成方法
public String getToken(User user) {
String token = "";
//Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。
//withAudience()存入需要保存在token的信息,这里我把用户ID存入token中
token = JWT.create().withAudience(user.getId()).
sign(Algorithm.HMAC256(user.password));
return token;
}
}
自定义一个拦截器去获取token并验证token
实现一个拦截器就需要实现HandlerInterceptor接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token=request.getHeader("token");
if(!(handler instanceof HandlerMethod))
return true; // 如果不是映射到方法直接通过
HandlerMethod handlerMethod=(HandlerMethod)handler;
Method method=handlerMethod.getMethod();
//检查是否有passtoken注解,有则跳过认证
if(method.isAnnotationPresent(PassToken.class)){
PassToken passToken=method.getAnnotation(PassToken.class);
if(passToken.required()){
return true;
}
}
//检查有没有需要用户权限的注解
if(method.isAnnotationPresent(UserLoginToken.class)){
UserLoginToken userLoginToken=method.getAnnotation(UserLoginToken.class);
if(userLoginToken.required()){
if(token==null){
throw new RuntimeException("无tokebn 请重新登录");
}
String userId;
try {//获取我们存入道token中的userId
userId= JWT.decode(token).getAudience().get(0);
}catch (JWTDecodeException ex){
throw new RuntimeException("401");
}
//在数据库中查询用户是否存在
User user=new User();
user.setPassword("111111");
//验证token
JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try{
jwtVerifier.verify(token);
}catch (JWTVerificationException e){
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}
HandlerInterceptor接口主要定义了三个方法
1.boolean preHandle ():
预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程
(如调用下一个拦截器或处理器)或者接着执行
postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。
2.void postHandle():
后处理回调方法,实现处理器的后处理(DispatcherServlet进行视图返回渲染之前进行调用),
此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,
modelAndView也可能为null。
3.void afterCompletion():
整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行,
也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。整个请求处理完毕回调方法。
如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,
类似于try-catch-finally中的finally,但仅调用处理器执行链中
主要流程:
1.从 http 请求头中取出 token,
2.判断是否映射到方法
3.检查是否有passtoken注释,有则跳过认证
4.检查有没有需要用户登录的注解,有则需要取出并验证
5.认证通过则可以访问,不通过会报相关错误信息
配置拦截器
在配置类上添加了注解@Configuration,标明了该类是一个配置类并且会将该类作为一个SpringBean添加到IOC容器内
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
package com.test.jwt;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//InterceptorRegistry内的addInterceptor需要一个实现HandlerInterceptor接口的拦截器实例,
// addPathPatterns方法用于设置拦截器的过滤路径规则
registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
参考:
JSON Web Token - 在Web应用间安全地传递信息