SpringBoot整合JWT

SpringBoot整合JWT

JWT简介

JSON Web Token(JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象安全传输信息。 这些信息可以通过数字签名进行验证和信任。 可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,该token也可直接被用于认证,也可被加密。是目前最流行的跨域认证解决方案。

JWT优点

  • 体积小,传输速度更快
  • 多样化的传输方式,可以通过URL传输、POST传输、请求头Header传输(常用)
  • 简单方便,载荷包含有关用户的所有必需信息,服务端拿到jwt后无需再次查询数据库校验token可用性,避免了多次查询数据库。
  • 在分布式系统中,很好地解决了单点登录问题
  • 很方便的解决了跨域授权问题,因为跨域无法共享cookie

JWT结构

jwt有3个组成部分,每部分通过点号来分割 header.payload.signature

  • 头部(header) 是一个 JSON 对象,描述 JWT 的元数据,
  • 载荷(payload) 是一个 JSON 对象,用来存放实际需要传递的数据
  • 签证(signature) 是对header和payload使用密钥进行签名,防止数据篡改。

【Header】

Jwt的头部是一个JSON,然后使用Base64URL编码,承载两部分信息:

  1. 标头通常由两部分组成: 令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用Base64编码组成JWT结构的第一部分
  2. 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
"alg" : "HS256",
"typ" : "JWT" 
  • 声明类型typ,表示这个令牌(token)的类型(type),JWT令牌统一写为JWT
  • 声明加密的算法alg,通常直接使用HMACSHA256,就是HS256了,也可以使用RSA,支持很多算法(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384)
    var header = Base64URL({ “alg”: “HS256”, “typ”: “JWT”})

Base64URL:Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

【Payload】载荷
payload即有效负载,也是一个JSON字符串,是承载消息具体内容的地方,也需要使用Base64URL编码

"sub" : "1234578",
"name" : "John Doe"
"admin" : true 

payload中可以包含预定义的7个可用,它们不是强制性的,但推荐使用,也可以添加任意自定义的key

iss(issuer): jwt签发者
sub(subject): jwt所面向的用户
aud(audience): 接收jwt的一方, 受众
exp(expiration time): jwt的过期时间,这个过期时间必须要大于签发时间
nbf(Not Before): 生效时间,定义在什么时间之前.
iat(Issued At): jwt的签发时间
jti(JWT ID): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

// 该token签发给1234567890,姓名为John Doe(自定义的字段),签发时间为1516239022

var payload = Base64URL( {“sub”: “1234567890”, “name”: “John Doe”, “iat”: 1516239022})

注意:JWT中payload是不加密的,只是Base64URL编码一下,任何人拿到都可以进行解码,所以,在负载里面不要加入任何敏感的数据!!!

【Signature】签名

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

HMACSHA256(base64UrlEncode(header) + "." + base64Ur1Encode(payload) , secret) ; 
var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私钥";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature; 

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。

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

SpringBoot集成JWT

项目结构

在这里插入图片描述

添加Maven依赖

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

1. 创建JWT工具类

创建操作JWT的工具类

@Service
public class JwtUtil {

    /**
     * 生成token
     */
    public static String createToken(User user) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7); //默认令牌过期时间7天
        JWTCreator.Builder builder = JWT.create();
        builder.withClaim("uid", user.getId())
                .withClaim("username", user.getUsername())
                .withClaim("role", user.getRole())
                .withClaim("permission", user.getPermission());

        return builder.withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256(user.getPassword()));
    }

    /**
     * 解析token
     */
    public static DecodedJWT verifyToken(String token) {
        if (token==null){
            System.out.println("token不能为空");
        }
        //获取登录用户真正的密码假如数据库查出来的是123456
        String password = "admin";
        JWTVerifier build = JWT.require(Algorithm.HMAC256(password)).build();
        return build.verify(token);
    }
} 

2. 创建JWT拦截器

/**
 * @Description: token 拦截器
 */
@Slf4j
public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("token");
        log.info("token:" + token);

        if (token == null) {
            log.error("token为空");
        }
        try {
            JwtUtil.verifyToken(token);
        } catch (SignatureVerificationException e) {
            log.error("无效签名");
            return false;
        } catch (TokenExpiredException e) {
            log.error("token过");
            return false;
        } catch (AlgorithmMismatchException e) {
            log.error("token算法不一致");
            return false;
        } catch (Exception e) {
            log.error("token无效");
            return false;
        }
        return true;
    }
} 

3. 创建JWT配置类

IntercaptorConfig

package com.demo.jwt.config;

import com.demo.jwt.jwt.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Description: JWT拦截器
 * @Date: 2022/4/5 17:30
 * @Author: Yang Yezhuang
 */
@Configuration
public class IntercaptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                //拦截的路径
                .addPathPatterns("/info/*", "/test")

                //排除接口
                .excludePathPatterns("/", "/login", "/register");
    }
} 

4. 创建测试类

controller

@Slf4j
@RestController
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping("/login")
    public String login(@RequestBody User user) {
        // 查询数据库用户名,密码
        User u = userService.login(user.getUsername());
        // 判断用户名,密码是否正确
        if (user.getUsername().equals(u.getUsername()) & user.getPassword().equals(u.getPassword())) {
            String token = JwtUtil.createToken(u);
            return token;
        } else {
            return "用户名或密码错误";
        }
    }

    @GetMapping("/info/{username}")
    public User info(@PathVariable("username") String username) {
        User u = userService.login(username);
        log.info(u.toString());
        return u;
    }

    @GetMapping("/test")
    public String info(HttpServletRequest request) {
        String token = request.getHeader("token");
        log.info("This is test + " + token);
        return "This is test";
    }
} 

感谢大家的耐心阅读,如有建议请私信或评论留言

posted @ 2022-11-16 18:05  杨业壮  阅读(211)  评论(0编辑  收藏  举报