Java Spring Boot 自实现 JWT 认证

123 阅读2分钟

JWT 不多介绍,了解的都知道,在认证方面广泛应用,接下来将自己简单实现基于 JWT 的认证方案。

环境:

  • Spring Boot: 3.1.6
  • JDK: 17

JWT工具类:

package com.example.springbootauthdemo.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class JWTUtilV1 {
    private static final String typ = "JWT";

    private static final String alg = "HmacSHA256";

    private static final long ttl = 3600 * 1000; // 3600s

    private static final String secret = "iAmTheSecret";


    private static String encodeHeader() {
        Map<String, Object> header = new HashMap<>();
        header.put("typ", typ);
        header.put("alg", alg);

       return object2base64Encode(header);
    }

    private static String encodePayload(String iss) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("iss", iss);
        payload.put("expireAt", System.currentTimeMillis() + ttl);

        return object2base64Encode(payload);
    }

    private static String object2base64Encode(Map<String, Object> object) {
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonObject = "";
        try {
            jsonObject = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return Base64.getEncoder().encodeToString(jsonObject.getBytes());
    }

    private static String byteArrayToHexString(byte[] b) {
        StringBuilder sb = new StringBuilder();
        String stmp;
        for (int i = 0; i < b.length; i++) {
            stmp = Integer.toHexString(b[i] & 0XFF);
            if (stmp.length() == 1) sb.append('0');
            sb.append(stmp);
        }
        return sb.toString();
    }

    private static String hmacSha256Crypt(String msg) {
        String hash = "";
        String algorithm = "HmacSHA256";

        try {
            Mac instance = Mac.getInstance(algorithm);
            SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), algorithm);
            instance.init(secretKey);
            byte[] bytes = instance.doFinal(msg.getBytes());
            hash = byteArrayToHexString(bytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }

        return hash;
    }

    public static String createToken(String username) {
        List<String> items = new ArrayList<>();
        String encodedHeader = encodeHeader();
        String encodedPayload = encodePayload(username);
        items.add(encodedHeader);
        items.add(encodedPayload);

        String msg = String.join(".", items);
        String signature = hmacSha256Crypt(msg);
        // signature -> base64
        signature = Base64.getEncoder().encodeToString(signature.getBytes());
        items.add(signature);

        return String.join(".", items);
    }

    public static boolean verifyToken(String token) {
        String[] items = token.split("\."); // . 在regex中有特殊含义,需要转义
        String msg = items[0] + "." + items[1];

        String signature = hmacSha256Crypt(msg);
        // signature -> base64
        signature = Base64.getEncoder().encodeToString(signature.getBytes());

        return signature.equals(items[2]);
    }

    public static void main(String[] args) {
        String token = createToken("hello");
        System.out.println(token);
        System.out.println(verifyToken(token));
    }
}

用户类:

package com.example.springbootauthdemo.param;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;


public class User {
    @NotBlank
    private String username;

    @Min(value = 6)
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

登录控制器类:

package com.example.springbootauthdemo.controller;

import com.example.springbootauthdemo.param.User;
import com.example.springbootauthdemo.util.JWTUtilV1;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

    @PostMapping("/login")
    public Object login(@RequestBody User user) {
        // 第一次登陆,通常会通过拦截件先过滤下请求
        String token;
        if (user.getUsername().equals("admin") && user.getPassword().equals("12345678")) {
            token = JWTUtilV1.createToken(user.getUsername());
            return String.format("login success, token: %s", token);
        } else return "登录信息错误";
    }
}

后续访问类:

package com.example.springbootauthdemo.controller;

import com.example.springbootauthdemo.param.User;
import com.example.springbootauthdemo.util.JWTUtilV1;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @PostMapping("/user/update")
    public Object update(@RequestHeader("token") String token, @RequestBody User user) {
        if (JWTUtilV1.verifyToken(token)) {
            return "update user info success.";
        }
        return "请先认证";
    }
}

本文基于重点学习 token 的生成、校验、应用几方面,主要逻辑就是,用户需要先认证,认证成功后返回token,下次访问携带 token 校验。

总体来说,还是比较简单的,当然现成的工具类也可以直接用,比如后面我们学习的 io.jsonwebtoken com.auth0