一、什么是JWT ?

现在很多企业基本都是基于前后端分离进行开发,因此在这种情况下,后端只需提供API接口,那么就需要一种机制来做校验,于是就有了JWT。JWT全称 “Json web token”,特别适用于分布式站点的单点登录。

1、JWT的组成

JWT由三个部分组成,分别是:
 1.标头(Header)
 2.有效荷载(Payload)
 3.签名(Signature)
因此, JWT通常如下所示:xxxxx.yyyyy.zzzzz 三部分(Header.Payload.Signature)
{ }.{ }.{ } 三串JSON

2、Header 标头

标头通常由两部分组成: 令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用Base64 编码组成 JWT的第一部分。

注意:Base64是一种编码 ,也就是说。它是可以被翻译回原来的样子。它并不是一种加密过程。

例如:

  {
    "alg" : "HS256",   //算法可以有多种选择
    "typ" : "JWT"     //这个是唯一的
  }

3、.Payload (主要包含一些自己想包含的信息)

令牌的第二部分是有效负载,其中包含声明.声明是有关实体 (通常是用户信息,但是前端能解码,所以最好不要包含敏感信息:用户的密码等)和其他数据的声明,同样的,它会使用Base64 编码组成JWT结构的第二部分。
例如:

 {
   "sub":"123456",
   "name": "John Doe",
   "admin" :true
 }

4、Signature 签名

前面两部分都是使用Base64进行编码的,即前端可以解码知道里面的信息。Signature需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。
如:
如:
HMACSHA256(base64UrlEncode(header) +"."+ base64UrlEncode(payload),secret);

签名的目的:

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载行程的签名和JWT附带上的签名是不一样的,如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的.(所以密钥secret千万不能告诉别人)

JWT的三个部分总结如下:
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_请求头
放在一起
所组成的字符串如下所示 . 可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比。它更紧凑。
-简洁
可以通过URL,POST参数或者在HTTP header发送.因为数据量小,传输速度快
-自包含(Self-contained)
负载中包含了所有用户所需要的信息,避免了多次查询数据库
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_java_02

二、在JAVA中使用JWT

在springboot项目中导入以下坐标:

		<!-- java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

1、生成JWT

class SpringbootJwtApplicationTests {

    @Test
    void contextLoads() {
        Map<String, Object> map = new HashMap<>();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,20);
        
        //头部可要可不要,因为会有默认值,JWT的三部分如下
        String token = JWT.create().withHeader(map)//hearder设置头部 签名算法和type
                .withClaim("userId", 23) //payload
                .withClaim("username", "yangdan")
                .withClaim("sex", "女")
                .withExpiresAt(instance.getTime())  //指定;令牌的过期时间 20秒之后过期
                .sign(Algorithm.HMAC256("!@dsa&&"));//签名 算法和密钥
        System.out.println(token);
    }
}

2、校验JWT

我们可以拿到jwt负载中声明的数据


    /*令牌的验证*/
    @Test
    void authToken(){

        /*创建验证对象  注意:算法和密钥要去上面生产Token时保持一致*/
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!@dsa&&")).build();
        /*验证刚刚的token 然后拿到一个解密后的decodedJWT*/
        /*注意:如果JWT不正确,那么这下面这一步校验的过程将会报错*/
        DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXgiOiLlpbMiLCJleHAiOjE2MjI0NjY5ODgsInVzZXJJZCI6MjMsInVzZXJuYW1lIjoieWFuZ2RhbiJ9.a_IYS7HK0LLDRddq8jsbNrqC2eHYAd8LFCFdGKp-6Sc");
        Integer userId = decodedJWT.getClaim("userId").asInt();  //拿到负载中声明数据值
        String username = decodedJWT.getClaim("username").asString();
        System.out.println(userId); //23
        System.out.println(username); //yangdan
    }

3、JWT工具类的封装

为了在项目中更快捷的生成和校验JWT,我们需要封装一个JWT的工具类

public class JWTUtils {

    private static final String  secret="token!@18056112120";

    /*生成token*/
    public static String getToken(Map<String,String> map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7); //设置7天过期

        //创建JWT builder
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        //sign
        builder.withExpiresAt(instance.getTime());
        String token = builder.sign(Algorithm.HMAC256(secret));
        return token;
    }
    /*验证token*/
    public static DecodedJWT verify(String token){
        //创建JWT验证对象 验证token
       return   JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
    }
}

三、基于JWT的认证

先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_jwt_03

四、SpringBoot整合JWT

创建springboot项目导入以下坐标:

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
 </dependencies>

项目具体结构如下:
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_java_04
编写用来统一返回的javabean



@Data
@NoArgsConstructor
@AllArgsConstructor
public class JSONResult {
    private Integer status;
    private String msg;
    private Object data;
    public static JSONResult ok(){
        JSONResult result = new JSONResult();
        result.setStatus(200);
        result.setMsg("ok");
        return result;
    }
    public static JSONResult ok(Object object){
        JSONResult result = new JSONResult();
        result.setStatus(200);
        result.setMsg("ok");
        result.setData(object);
        return result;
    }
    public static JSONResult error(String msg){
        JSONResult result = new JSONResult();
        result.setStatus(500);
        result.setMsg(msg);
        return result;
    }
    public static JSONResult build(Integer status,String msg,Object data){
        return new JSONResult(status,msg,data);
    }

}

Controller中编写登录的接口和用来测试的接口:

如果登录用户名和密码都正确,那么像前端返回JWT


@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {

    @GetMapping("/login")
    public JSONResult login(User user, HttpServletResponse response){
        log.info("接收到的信息:"+user);
        if (StringUtils.isBlank(user.getUsername())||StringUtils.isBlank(user.getPassword())){
          return JSONResult.error("参数传递错误");
        }
        if ("yangzihao".equals(user.getUsername())&&"18056112120".equals(user.getPassword())){
            /*生成JWT*/
            Map<String, String> map = new HashMap<>();
            map.put("username",user.getUsername());
            map.put("password",user.getPassword());
            String token = JWTUtils.getToken(map);
            /*认证成功返回token*/
            return JSONResult.ok(token);
        }else {
          return JSONResult.error("认证失败");
        }
     }


     /*后序如果想调用其他接口*/
    @GetMapping("/getOk")
    public JSONResult test(){
       return JSONResult.ok();
    }
  }

之后再访问其它端口我们需要使用拦截器进行请求的拦截,校验请求头中的JWT(前端一般将登录成功后拿到的JWT存入本地,然后每次发送请求的时候在请求头中带上JWT)

拦截器:

逻辑:拿到请求头中的JWT进行校验,前面说过,如果JWT错误会报错,那么报错向前端返回对应的错误信息。如果正确,即return true;放行。jwt一共有一下五种异常类型:


/*拦截器 用来判断用户是否授权*/
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        JSONResult jsonResult=null;
        try {
            //拿到请求头中的jwt
            String token = request.getHeader("auth");
            JWTUtils.verify(token);//验证令牌;
            return true;
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            jsonResult=JSONResult.error("无效签名");
        } catch (TokenExpiredException e){
            e.printStackTrace();
            jsonResult=JSONResult.error("token过期");
        } catch (AlgorithmMismatchException e){
            e.printStackTrace();
            jsonResult=JSONResult.error("token算法不一致");
        } catch (Exception e){
            e.printStackTrace();
            jsonResult = JSONResult.error("token无效");
        }
        /*转为json数据*/
        String json = new ObjectMapper().writeValueAsString(jsonResult);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(json);
        return false;
    }
}

在mvc配置类中配置拦截器

@Configuration
public class MyMVCConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /*除登录接口以外其余的都进行拦截*/
        registry.addInterceptor(new MyInterceptor()).excludePathPatterns("/user/login")
                .addPathPatterns("/**");
    }
}

五、测试

未登录状况下请求测试接口结果如下:
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_请求头_05
请求登录拿到JWT:
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_jwt_06
在请求头中携带JWT,请求测试接口:
先验签 RAS 加密 spring boot 后端加密解密 springboot接口加签验签_json_07

注意

就算客户端服务器重启了,只要token没过期,token始终是有效的,它不像session,如果服务器重启了session就会失效。这就是使用JWT的好处!!!

完结撒花