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。