基于JWT的token认证机制
1. 一个JWT实际上就是一个字符串,由三部分组成 头部,载荷,签名
头部:事描述类型,签名,算法等 可以被表示成一个JSON对象
载荷:存放有效信息的地方 包含三个部分
(1)标准注册中的声明-建议但不强制使用
iss:jwt签发者
sub:jwt所面向的用户
aud:接收jwt的一方
exp:jwt的过期时间,时间必须大于签发时间
nbf:定义在什么时间以前,这个jwt都是不可用的
iat:jwt的签发时间
jti:jwt的唯一身份标识,用来作为token
(2)公共的声明
一般是公司相关的信息,由于这部分可以被解密,所以不要添加敏感信息
(3)私有的声明
可以被解密,,
可以自定义claim
载荷的例子 {"sub":"1233","name":"John","admin":true}
签证:三部分
header(base64后的)
payload(base64后的)
secret
2. 名词解释 Base64
64个可打印二进制数据 2^6为64 每6个比特是一个单元,打印一个字符,3个字节有24个比特
对应4个Base64 JDK中提供了BASE64Encoder BASE64Decoder 编码,解码
3. java的jjwt
JJWT是一个提供端到端的JWT创建和验证的Java库
4. 使用
(一)引入依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
(二)测试使用
package BigTest.jwt; import com.JwtBootApplication; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.text.SimpleDateFormat; import java.util.Date; @RunWith(SpringRunner.class) @SpringBootTest(classes = JwtBootApplication.class) public class CreateJwtTest { /* * java.lang.IllegalArgumentException: * secret key byte array cannot be null or empty. * 是因为secret太简单了 没有加时间 * */ @Test public void testJwt(){ JwtBuilder jwtBuilder = Jwts.builder().setId("888") .setSubject("小白") .setIssuedAt(new Date()) //setIssuedAt用于设置签发时间 .signWith(SignatureAlgorithm.HS256, "onetwothree"); //signWith用于设置签名秘钥 System.out.println(jwtBuilder.compact()); //eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NzMyOTAwOTh9.-lqlLqmAG2qQ15Ge7IlcgIAgj0V54L0GCA5dJfM6Lw4 } @Test public void ParseJwtTest(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NzMyOTAwOTh9.-lqlLqmAG2qQ15Ge7IlcgIAgj0V54L0GCA5dJfM6Lw4"; Claims claims = Jwts.parser().setSigningKey("onetwothree").parseClaimsJws(token).getBody(); System.out.println("id="+claims.getId()); System.out.println("subject="+claims.getSubject()); System.out.println("IssueAt="+claims.getIssuedAt()); } /** * 带过期时间的jwt-token * ExpiredJwtException: token过期报错 */ @Test public void testJwt_time(){ long now = System.currentTimeMillis(); long exp = now + 1000*60;//设置时间为1分钟 JwtBuilder jwtBuilder = Jwts.builder().setId("888") .setSubject("小白") .setIssuedAt(new Date()) //setIssuedAt用于设置签发时间 .setExpiration(new Date(exp))//setExpiration设置token过期时间 .signWith(SignatureAlgorithm.HS256, "onetwothree"); //signWith用于设置签名秘钥 System.out.println(jwtBuilder.compact()); } @Test public void ParseJwtTest_time(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NzMyOTg0MDUsImV4cCI6MTU3MzI5ODQ2NX0.Rx-0BrJ2032aUz_vrttXw-9_idfQe3LHzCnLOWpsaqs"; Claims claims = Jwts.parser().setSigningKey("onetwothree").parseClaimsJws(token).getBody(); System.out.println("id="+claims.getId()); System.out.println("subject="+claims.getSubject()); System.out.println("IssueAt="+claims.getIssuedAt()); SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss"); System.out.println("签发时间:"+sdf.format(claims.getIssuedAt())); System.out.println("过期时间:"+sdf.format(claims.getExpiration())); System.out.println("当前时间:"+sdf.format(new Date()) ); } /* * 自定义claims,上面的只存储了id和subject 自定义可以存储自己向存储的内容 * */ @Test public void testJwt_claims(){ long now = System.currentTimeMillis(); long exp = now + 1000*60; //设置过期时间为1分钟 JwtBuilder claim = Jwts.builder().setId("9768") .setSubject("zhangsan") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256,"onetwothree") .setExpiration(new Date(exp)) .claim("roles", "admin") .claim("address", "beijing"); System.out.println(claim.compact()); } @Test public void ParseJwtTest_claim(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5NzY4Iiwic3ViIjoiemhhbmdzYW4iLCJpYXQiOjE1NzMzMDg0ODUsImV4cCI6MTU3MzMwODU0NSwicm9sZXMiOiJhZG1pbiIsImFkZHJlc3MiOiJiZWlqaW5nIn0.Y-8hZIAlWzegSadQWSQGRc9gUBGlBznRT984jtqQZ-s"; Claims claims = Jwts.parser().setSigningKey("onetwothree").parseClaimsJws(token).getBody(); System.out.println("id:"+claims.getId()); System.out.println("subject:"+claims.getSubject()); System.out.println("roles:"+claims.get("roles")); System.out.println("logo:"+claims.get("address")); SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss"); System.out.println("签发时间:"+sdf.format(claims.getIssuedAt())); System.out.println("过期时间:"+sdf.format(claims.getExpiration())); System.out.println("当前时间:"+sdf.format(new Date()) ); } }
(三)在springboot中的使用
在application.yml添加配置
jwt: config: key: zhedoumeishenme ttl: 360000
编写utils
package com.xxy.server.common.utils; import io.jsonwebtoken.*; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Date; @ConfigurationProperties("jwt.config")//拿到 .yml中的参数 public class JwtUtils { private String key; private long ttl; //一个小时 public String getKey() { return key; } public void setKey(String key) { this.key = key; } public long getTtl() { return ttl; } public void setTtl(long ttl) { this.ttl = ttl; } /** * 生成jwt * @param id * @param subject * @param roles * @return */ public String createJWT(String id, String subject, String roles){ long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); JwtBuilder builder = Jwts.builder().setId(id) .setSubject(subject) .signWith(SignatureAlgorithm.HS256, key) .setIssuedAt(now) .claim("roles", roles); if(ttl > 0){ builder.setExpiration(new Date(nowMillis+ttl)); } return builder.compact(); } /** * 解析JWT * @param jwtStr * @return */ public Claims parseJWT(String jwtStr){ return Jwts.parser() .setSigningKey(key) .parseClaimsJws(jwtStr) .getBody(); } }
Jwt请求拦截器
package com.xxy.server.common.interceptors; import com.xxy.server.common.utils.JwtUtils; import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Description: Jwt请求拦截器 * @Author: xuxiaoyu * @Create: 2019-11-10 20:45 */ @Component public class JwtFilter extends HandlerInterceptorAdapter { @Autowired private JwtUtils jwtUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("经过了jwtfilter拦截器"); final String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { Claims claims = jwtUtils.parseJWT(authHeader); if(claims != null){ if("admin".equals(claims.get("roles"))){ request.setAttribute("admin_claims", claims); } if("user".equals(claims.get("roles"))){ request.setAttribute("user_claims", claims); } } } return true; } }
配置到mvc中
package com.xxy.server.common.config; import com.xxy.server.common.interceptors.JwtFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; /** * description:该类可以来扩展Spirng MVC的功能 */ @Configuration public class MvcConfig extends WebMvcConfigurationSupport { @Autowired private JwtFilter jwtFilter; /** * description:添加默认视图映射,当访问localhost:8080/时,跳转到index.html页面 * params : registry * @return void */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("index"); } /** * description: Jwt请求拦截器,任何请求都会经过这个拦截器 * @param registry */ @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtFilter) .addPathPatterns("/**") .excludePathPatterns("/login","/captcha"); } }
然后在方法中就可以判断使用了
Claims claims = (Claims) request.getAttribute("admin_claims"); if (claims == null) { return HttpResultUtil.error("权限不足"); }