JWT相关概念介绍:
1、头部信息
2、载荷信息
3、签名信息
一、头部信息:头部信息由两部分组成1、令牌的类型,即JWT;2、使用的签名算法,例如HMASSHA256或RSA;
头部信息JSON代码如下:然后这个JSON被编码为Base64URL,形成JWT的第一部分
{
"alg": "HS256",
"typ": "JWT"
}
二、载荷信息:其中包含声明(claims),声明可以存放实体和其它数据的声明,声明包括三种类型
1、已注册声明:这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中一些是:** iss(发行人), exp(到期时间),sub(主题), aud**(观众)等
2、公开声明:可以参考 IANA JSON Web令牌注册表https://www.iana.org/assignments/jwt/jwt.xhtml) 查看公共的声明
3、私有声明:根据自己的业务需要自定义一些数据格式,示例如下
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
三、签名信息:这个部分需要 base64 加密后的 头部信息(header) 和 base64 加密后的载荷信息(payload),使用连接组成的字符串,然后通过头部信息(header)中声明的加密方式进行加盐 secret 组合在加密,然后就构成了 JWT 的第三部分。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
通过三种方法演示如何使用JWT
1、创建不携带自定义信息的token,方法createToken() ;
2、创建携带自定义信息的token,方法createTokenWithClaim();
3、创建携带自定义信息的token,并使用Base64再次处理,方法createTokenWithClaimWithBase();
3、验证token信息并解析token中的内容,方法verifyToken()
说明:JWT在生成Token的时候,如果需要自定义信息,会使用到.withClaim();如果不需要自定义,则不需要,下面代码都有详细注释说明。注意使用Gson需要引入对应依赖,依赖如下
<!-- gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
一、自定义两个注解,分别用于token验证和跳过token验证
1、自定义注解PassToken,用于跳过Token验证
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;
}
2、自定义注解UserLoginToken,用于Token验证
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 UserLoginToken {
boolean required() default true;
}
二、书写JWT方法,包括生成token和解密token
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.dream.fly.readbook.entity.User;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 生成token的方法
*/
public class JWTUtil {
/**
* 设置过期时间(一天)
*/
// private static final long EXPIRE_TIME = 24*60*60*1000;
private static final long EXPIRE_TIME = 60*1000;
/**
* 设置token私钥
*/
private static final String TOKEN_SECRET = "one_smile";
/**
* 创建不携带自定义信息的token
*/
public static String createToken(User user){
//构建头部信息
Map<String,Object> map = new HashMap<String,Object>();
map.put("alg","HS256");
map.put("typ","JWT");
//构建密钥信息。使用自定义token密钥还是用户密码都可以,选择不一样,在解密的时候会不一样。一般建议自定义,安全、方便
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //使用自定义的token密钥
// Algorithm algorithm = Algorithm.HMAC256(user.getRead_userpass()); //使用用户输入的用户密码作为密钥
//通过定义注册并组合头部信息和密钥信息生成jwt token
Date nowDate = new Date();
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); //过期时间预处理
String token = JWT.create()
.withHeader(map) //设置头部信息Header
.withIssuer("SERVICE") //设置 载荷 签名是有谁生成 例如 服务器
.withSubject("this is readbook token") //设置 载荷 签名的主题
// .withNotBefore(new Date()) //设置 载荷 定义在什么时间之前,该jwt都是不可用的.
.withAudience(user.getRead_username()) //设置 载荷 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) //设置 载荷 生成签名的时间
.withExpiresAt(expireDate) //设置 载荷 签名过期的时间
.sign(algorithm); //签名 Signature
return token;
}
/**
* 创建携带自定义信息和声明并存的的jwt token
*/
public static String createTokenWithClaim(User user){
//构建头部信息
Map<String,Object> map = new HashMap<>();
map.put("typ","JWT");
map.put("alg","HS256");
//构建密钥信息。使用自定义token密钥还是用户密码都可以,选择不一样,在解密的时候会不一样。一般建议自定义,安全、方便
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //使用自定义的token密钥
// Algorithm algorithm = Algorithm.HMAC256(user.getRead_userpass()); //使用用户输入的用户密码作为密钥
//通过自定义声明并组合头部细腻些和密钥信息生成jwt token
Date nowDate = new Date();
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); //过期时间预处理
String token = JWT.create().withHeader(map)
.withClaim("loginName",user.getRead_username()) //自定义,登录用户名
.withClaim("deptName","技术部") //自定义,部门
.withClaim("loginPass",user.getRead_userpass()) //自定义,登录用户密码
.withIssuer("SERVICE") // 声明,签名是有谁生成 例如 服务器
.withSubject("this is test token") //声明, 签名的主题
// .withNotBefore(new Date()) //声明,定义在什么时间之前,该jwt都是不可用的
.withAudience("APP") //声明, 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) //声明, 生成签名的时间
.withExpiresAt(expireDate)//声明, 签名过期的时间
.sign(algorithm); //签名signature
return token;
}
/**
* 创建携带自定义信息和声明并存的的jwt token
* 同时使用base64进一步加密
*/
public static String createTokenWithClaimWithBase(User user){
//构建头部信息
Map<String,Object> map = new HashMap<>();
map.put("typ","JWT");
map.put("alg","HS256");
Gson gson = new Gson();
String userJson = gson.toJson(user);
String userJsonBase64 = BaseEncoding.base64().encode(userJson.getBytes());
//构建密钥信息
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//通过自定义声明并组合头部细腻些和密钥信息生成jwt token
Date nowDate = new Date();
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); //过期时间预处理
String token = JWT.create()
.withClaim("user",userJsonBase64).withIssuer("SERVICE")// 签名是有谁生成
.withSubject("this is test token")// 签名的主题
// .withNotBefore(new Date())//该jwt都是不可用的时间
.withAudience("APP")// 签名的观众 也可以理解谁接受签名的
.withIssuedAt(nowDate) // 生成签名的时间
.withExpiresAt(expireDate)// 签名过期的时间
.sign(algorithm);//签名 Signature
return token;
}
/**
* 验证jwt token
* 如果在生成token的步奏中构建密钥信息使用了用户密码,则在解密的时候,同样构建密钥信息的时候需要用户密码
*/
public static void verifyToken(String token){
//构建密钥信息
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
//通过密钥信息和签名的发布者的信息生成JWTVerifier(JWT验证类)。不提那家发布者的信息也可以获取JWTVerifier
JWTVerifier verifier = JWT.require(algorithm)
// .withIssuer("SERVICE") //不添加.withIssuer("SERVICE") 也可以获取JWTVerifier
.build();
//通过JWTVerifier获取token中的信息
DecodedJWT jwt = verifier.verify(token);
//获取token中的声明和自定义声明
String subject = jwt.getSubject();
List<String> audience = jwt.getAudience();
Map<String, Claim> claims = jwt.getClaims();
for (Map.Entry<String, Claim> entry : claims.entrySet()){
String key = entry.getKey();
Claim claim = entry.getValue();
String value = claim.asString();
System.out.println("key:"+key+" value:"+claim.asString());
}
}
}
三、书写拦截器,定义拦截规则
import com.dream.fly.readbook.CustomAnno.PassToken;
import com.dream.fly.readbook.CustomAnno.UserLoginToken;
import com.dream.fly.readbook.service.ProjectService;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* 拦截器
* 获取token并验证token
*/
public class JWTAuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private ProjectService projectService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token"); //从http请求头中取出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("无token,请重新登录!");
}
//调用JWT解密方法,可以在解密方法中定义返回数据,这里可以继续根据返回值进行判断,也可以在解密方法中判断,具体自行定义
JWTUtil.verifyToken(token);
}
}
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 {
}
}
四、定义配置文件,将拦截器配置进系统,使其生效
import com.dream.fly.readbook.utils.JWTAuthenticationInterceptor;
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 JWTInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtAuthenticationInterceptor())
.addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
}
@Bean
public JWTAuthenticationInterceptor jwtAuthenticationInterceptor(){
return new JWTAuthenticationInterceptor();
}
}
五、项目接口中使用
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dream.fly.readbook.CustomAnno.UserLoginToken;
import com.dream.fly.readbook.entity.User;
import com.dream.fly.readbook.service.ProjectService;
import com.dream.fly.readbook.utils.JWTUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 项目初始化相关配置
* 例如:
* 1、首页
*/
@RestController
@Api(tags = "项目初始化接口")
public class ProjectCotroller {
@Autowired
private ProjectService projectService;
/**
* 用户登录
* @param username 用户名
* @param passwd 密码
* @return
*/
@PostMapping("/userLogin")
@ApiOperation(value = "用户登录",notes = "用户登录")
@ApiImplicitParams({@ApiImplicitParam(name = "username",value = "登录账号",required = true,paramType = "query")
,@ApiImplicitParam(name = "passwd",value = "登录密码",required = true,paramType = "query")})
public JSONObject userLogin(String username,String passwd){
Boolean loginInfo = this.projectService.userLogin(username, passwd);
Map map = new HashMap();
map.put("loginInfo",loginInfo);
if (loginInfo){ //如果登录成功需要把token带给前端
User user = new User();
user.setRead_username(username);
user.setRead_userpass(passwd);
//调用JWT生成Token方法,使其登录接口就生成token,返回给前端,便于后续接口使用
String token = JWTUtil.createTokenWithClaimWithBase(user);
map.put("token",token);
}
JSONObject jsonObject = new JSONObject(map);
return jsonObject;
}
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage(){
System.out.println("进入方法");
return "你已经通过验证";
}
}