一、JWT的主要应用场景
身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登录(SSO)中比较广泛的使用了该技术。 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
优点
1.简洁(Compact): 可以通过URL
,POST
参数或者在HTTP header
发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token
是以JSON
加密的形式保存在客户端的,所以JWT
是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。
`
二、JWT的结构
JWT是由三段信息构成的,将这三段信息文本用.
连接一起就构成了JWT字符串。
就像这样:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)
Signature 签名/签证
Header
JWT的头部承载两部分信息:token类型和采用的加密算法。
{ "alg": "HS256","typ": "JWT"
}
声明类型:这里是jwt
声明加密的算法:通常直接使用 HMAC SHA256
加密算法是单向函数散列算法,常见的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议。用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。常用于接口签名验证
Payload
载荷就是存放有效信息的地方。
有效信息包含三个部分
1.标准中注册的声明
2.公共的声明
3.私有的声明
标准中注册的声明 (建议但不强制使用) :
iss
: jwt签发者sub
: 面向的用户(jwt所面向的用户)aud
: 接收jwt的一方exp
: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)nbf
: 定义在什么时间之前,该jwt都是不可用的.iat
: jwt的签发时间jti
: jwt的唯一身份标识,主要用来作为一次性token
,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64
是对称解密的,意味着该部分信息可以归类为明文信息。
Signature
jwt的第三部分是一个签证信息
这个部分需要base64
加密后的header
和base64
加密后的payload
使用.
连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt
的第三部分。
密钥secret
是保存在服务端的,服务端会根据这个密钥进行生成token
和进行验证,所以需要保护好。
三、下面来进行SpringBoot和JWT的集成
(1)application.yml
server:port: 7009
spring:application:name: ware-jwt-token
config:jwt:# 加密密钥secret: iwqjhda8232bjgh432[cicada-smile]# token有效时长expire: 3600# header 名称header: token
(2)通用工具类 JwtConfig
@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {/** 根据身份ID标识,生成Token*/public String getToken (String identityId){Date nowDate = new Date();//过期时间Date expireDate = new Date(nowDate.getTime() + expire * 1000);return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(identityId).setIssuedAt(nowDate).setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, secret).compact();}/** 获取 Token 中注册信息*/public Claims getTokenClaim (String token) {try {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}catch (Exception e){e.printStackTrace();return null;}}/** Token 是否过期验证*/public boolean isTokenExpired (Date expirationTime) {return expirationTime.before(new Date());}private String secret;private long expire;private String header;public String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public long getExpire() {return expire;}public void setExpire(long expire) {this.expire = expire;}public String getHeader() {return header;}public void setHeader(String header) {this.header = header;}
}
(3)通用工具类 WebConfig 拦截器注册
@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 拦截器注册*/@Resourceprivate TokenInterceptor tokenInterceptor ;public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");}
}
/*** Token 拦截器*/
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {@Resourceprivate JwtConfig jwtConfig ;@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {// 地址过滤String uri = request.getRequestURI() ;if (uri.contains("/login")){return true ;}// Token 验证String token = request.getHeader(jwtConfig.getHeader());if(StringUtils.isEmpty(token)){token = request.getParameter(jwtConfig.getHeader());}if(StringUtils.isEmpty(token)){throw new Exception(jwtConfig.getHeader()+ "不能为空");}Claims claims = jwtConfig.getTokenClaim(token);if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){throw new Exception(jwtConfig.getHeader() + "失效,请重新登录");}//设置 identityId 用户身份IDrequest.setAttribute("identityId", claims.getSubject());return true;}
}
(4)写一个Controller来测试
@RestController
public class TokenController {@Resourceprivate JwtConfig jwtConfig ;/** 返参格式* {* "userName": "ID123",* "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.* eyJzdWIiOiJJRDEyM3B3MTIzIiwiaWF0Ijox.* SqqaZfG_g2OMijyN5eG0bPmkIQaqMRFlUvny"* }*/// 拦截器直接放行,返回Token@PostMapping("/login")public Map<String,String> login (@RequestParam("userName") String userName,@RequestParam("passWord") String passWord){Map<String,String> result = new HashMap<>() ;// 省略数据源校验String token = jwtConfig.getToken(userName+passWord) ;if (!StringUtils.isEmpty(token)) {result.put("token",token) ;}result.put("userName",userName) ;return result ;}// 需要 Token 验证的接口@PostMapping("/info")public String info (){return "info" ;}
}
(5)运行结果 注册了token .
(5)运行结果 没携带请求头Headers
(5)运行结果 携带请求头Headers