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 "你已经通过验证";
    }

}