From 24a9db11c1a36093f757daa3548274ec854b414b Mon Sep 17 00:00:00 2001 From: none Date: Thu, 16 Feb 2023 17:52:45 +0800 Subject: [PATCH] =?UTF-8?q?jwt=E7=9A=84=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xyz/playedu/api/PlayEduThreadLocal.java | 44 +++++++++++++ .../controller/admin/AdminUserController.java | 3 + .../api/controller/admin/LoginController.java | 4 +- .../api/exception/JwtLogoutException.java | 23 +++++++ .../api/middleware/AuthMiddleware.java | 12 ++++ .../middleware/impl/AuthMiddlewareImpl.java | 34 ++++++++++ .../service/impl/ImageCaptchaServiceImpl.java | 4 +- .../api/service/impl/JwtServiceImpl.java | 56 ++++++++++++++-- .../xyz/playedu/api/types/JWTPayload.java | 2 - .../util/{ToolUtil.java => HelperUtil.java} | 8 ++- .../java/xyz/playedu/api/util/MD5Util.java | 12 ---- .../xyz/playedu/api/util/RequestUtil.java | 65 +++---------------- src/main/resources/application.yml | 1 + 13 files changed, 186 insertions(+), 82 deletions(-) create mode 100644 src/main/java/xyz/playedu/api/PlayEduThreadLocal.java create mode 100644 src/main/java/xyz/playedu/api/exception/JwtLogoutException.java create mode 100644 src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java create mode 100644 src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java rename src/main/java/xyz/playedu/api/util/{ToolUtil.java => HelperUtil.java} (95%) delete mode 100644 src/main/java/xyz/playedu/api/util/MD5Util.java diff --git a/src/main/java/xyz/playedu/api/PlayEduThreadLocal.java b/src/main/java/xyz/playedu/api/PlayEduThreadLocal.java new file mode 100644 index 0000000..b12b6dc --- /dev/null +++ b/src/main/java/xyz/playedu/api/PlayEduThreadLocal.java @@ -0,0 +1,44 @@ +package xyz.playedu.api; + +import java.util.LinkedHashMap; + +public class PlayEduThreadLocal { + + private static final java.lang.ThreadLocal> THREAD_LOCAL = new java.lang.ThreadLocal<>(); + + public PlayEduThreadLocal() { + + } + + /** + * 写入变量 + * + * @param key + * @param val + */ + public static void put(String key, Object val) { + LinkedHashMap hashMap = THREAD_LOCAL.get(); + if (hashMap == null) { + hashMap = new LinkedHashMap<>(); + } + hashMap.put(key, val); + THREAD_LOCAL.set(hashMap); + } + + public static Object get(String key) { + return THREAD_LOCAL.get().getOrDefault(key, null); + } + + public static Integer getAdminUserID() { + return (Integer) get("admin_user_id"); + } + + public static void setAdminUserId(Integer userId) { + put("admin_user_id", userId); + } + + public static void remove() { + THREAD_LOCAL.remove(); + } + +} diff --git a/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java b/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java index ee989b0..ed3be58 100644 --- a/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java +++ b/src/main/java/xyz/playedu/api/controller/admin/AdminUserController.java @@ -4,7 +4,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import xyz.playedu.api.constant.SystemConstant; import xyz.playedu.api.domain.AdminUser; +import xyz.playedu.api.middleware.AuthMiddleware; import xyz.playedu.api.service.AdminUserService; import xyz.playedu.api.types.PaginationResult; import xyz.playedu.api.types.JsonResponse; @@ -15,6 +17,7 @@ public class AdminUserController { @Autowired private AdminUserService adminUserService; + @AuthMiddleware(prv = SystemConstant.JWT_PRV_ADMIN_USER) @GetMapping("/admin/user/index") public JsonResponse List(@RequestParam(name = "page", defaultValue = "1") Integer page, @RequestParam(name = "size", defaultValue = "10") Integer size) { PaginationResult result = adminUserService.paginate(page, size, null); diff --git a/src/main/java/xyz/playedu/api/controller/admin/LoginController.java b/src/main/java/xyz/playedu/api/controller/admin/LoginController.java index e482320..06036a8 100644 --- a/src/main/java/xyz/playedu/api/controller/admin/LoginController.java +++ b/src/main/java/xyz/playedu/api/controller/admin/LoginController.java @@ -15,7 +15,7 @@ import xyz.playedu.api.service.AdminUserService; import xyz.playedu.api.service.JWTService; import xyz.playedu.api.types.JsonResponse; import xyz.playedu.api.types.JwtToken; -import xyz.playedu.api.util.MD5Util; +import xyz.playedu.api.util.HelperUtil; import xyz.playedu.api.util.RequestUtil; import java.util.HashMap; @@ -38,7 +38,7 @@ public class LoginController { if (adminUser == null) { return JsonResponse.error("邮箱不存在"); } - String password = MD5Util.md5(loginRequest.getPassword() + adminUser.getSalt()).toLowerCase(); + String password = HelperUtil.MD5(loginRequest.getPassword() + adminUser.getSalt()).toLowerCase(); if (!adminUser.getPassword().equals(password)) { return JsonResponse.error("密码错误"); } diff --git a/src/main/java/xyz/playedu/api/exception/JwtLogoutException.java b/src/main/java/xyz/playedu/api/exception/JwtLogoutException.java new file mode 100644 index 0000000..0e557ed --- /dev/null +++ b/src/main/java/xyz/playedu/api/exception/JwtLogoutException.java @@ -0,0 +1,23 @@ +package xyz.playedu.api.exception; + +public class JwtLogoutException extends Exception { + public JwtLogoutException() { + super(); + } + + public JwtLogoutException(String message) { + super(message); + } + + public JwtLogoutException(String message, Throwable cause) { + super(message, cause); + } + + public JwtLogoutException(Throwable cause) { + super(cause); + } + + protected JwtLogoutException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java b/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java new file mode 100644 index 0000000..b324d28 --- /dev/null +++ b/src/main/java/xyz/playedu/api/middleware/AuthMiddleware.java @@ -0,0 +1,12 @@ +package xyz.playedu.api.middleware; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthMiddleware { + String prv(); +} diff --git a/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java b/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java new file mode 100644 index 0000000..140641a --- /dev/null +++ b/src/main/java/xyz/playedu/api/middleware/impl/AuthMiddlewareImpl.java @@ -0,0 +1,34 @@ +package xyz.playedu.api.middleware.impl; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import xyz.playedu.api.middleware.AuthMiddleware; +import xyz.playedu.api.service.JWTService; + +@Aspect +@Component +@Slf4j +public class AuthMiddlewareImpl { + + @Autowired + private JWTService jwtService; + + @Pointcut("@annotation(xyz.playedu.api.middleware.AuthMiddleware)") + private void doPointcut() { + } + + @Around("doPointcut()") + public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + AuthMiddleware authMiddleware = methodSignature.getMethod().getAnnotation(AuthMiddleware.class); + log.info("prv的值:" + authMiddleware.prv()); + return joinPoint.proceed(); + } + +} diff --git a/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java index 5c8d3cb..a7bd4d3 100644 --- a/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java +++ b/src/main/java/xyz/playedu/api/service/impl/ImageCaptchaServiceImpl.java @@ -10,7 +10,7 @@ import xyz.playedu.api.service.ImageCaptchaService; import xyz.playedu.api.types.ImageCaptchaResult; import xyz.playedu.api.util.Base64Util; import xyz.playedu.api.util.RedisUtil; -import xyz.playedu.api.util.ToolUtil; +import xyz.playedu.api.util.HelperUtil; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -35,7 +35,7 @@ public class ImageCaptchaServiceImpl implements ImageCaptchaService { BufferedImage image; // 图形验证码的key[api是无状态的需要key来锁定验证码的值] - String randomKey = ToolUtil.randomString(16); + String randomKey = HelperUtil.randomString(16); imageCaptcha.setKey(randomKey); // 生成验证码 diff --git a/src/main/java/xyz/playedu/api/service/impl/JwtServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/JwtServiceImpl.java index e3b067d..8b5a933 100644 --- a/src/main/java/xyz/playedu/api/service/impl/JwtServiceImpl.java +++ b/src/main/java/xyz/playedu/api/service/impl/JwtServiceImpl.java @@ -1,15 +1,18 @@ package xyz.playedu.api.service.impl; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import xyz.playedu.api.exception.JwtLogoutException; import xyz.playedu.api.service.JWTService; import xyz.playedu.api.types.JWTPayload; import xyz.playedu.api.types.JwtToken; -import xyz.playedu.api.util.ToolUtil; +import xyz.playedu.api.util.RedisUtil; +import xyz.playedu.api.util.HelperUtil; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; @@ -25,25 +28,26 @@ public class JwtServiceImpl implements JWTService { @Value("${playedu.jwt.expire}") private Long ConfigExpire; + @Value("${playedu.jwt.cache-black-prefix}") + private String ConfigCacheBlackPrefix; + public JwtToken generate(Integer userId, String iss, String prv) { long curTime = System.currentTimeMillis(); JWTPayload payload = new JWTPayload(); payload.setPrv(prv); payload.setIss(iss); - payload.setJti(ToolUtil.uuid()); + payload.setJti(HelperUtil.uuid()); payload.setNbf(curTime); payload.setIat(curTime); payload.setExp(curTime + ConfigExpire); payload.setSub(userId); - SecretKey key = Keys.hmacShaKeyFor(ConfigKey.getBytes(StandardCharsets.UTF_8)); - JwtBuilder builder = Jwts.builder(); builder.setId(payload.getJti()).setIssuedAt(new Date(payload.getIat())).claim("prv", payload.getPrv()); builder.setExpiration(new Date(payload.getExp())).setIssuer(payload.getIss()); builder.setSubject(String.valueOf(payload.getSub())).setNotBefore(new Date(payload.getNbf())); - builder.signWith(key); + builder.signWith(getSecretKey()); JwtToken token = new JwtToken(); token.setToken(builder.compact()); @@ -52,8 +56,46 @@ public class JwtServiceImpl implements JWTService { return token; } - public JWTPayload parse(String token) { - return null; + public JWTPayload parse(String token, String prv) throws JwtLogoutException { + Claims claims = parseToken(token, prv); + JWTPayload payload = new JWTPayload(); + + payload.setSub(Integer.valueOf(claims.getSubject())); + payload.setIss(claims.getIssuer()); + payload.setPrv((String) claims.get("prv")); + payload.setNbf(claims.getNotBefore().getTime()); + payload.setExp(claims.getExpiration().getTime()); + payload.setIat(claims.getIssuedAt().getTime()); + payload.setJti(claims.getId()); + + return payload; + } + + public boolean isInBlack(String jti) { + return RedisUtil.exists(getBlackCacheKey(jti)); + } + + public void logout(String token, String prv) throws JwtLogoutException { + Claims claims = parseToken(token, prv); + String cacheKey = getBlackCacheKey(claims.getId()); + Long expire = (claims.getExpiration().getTime() - System.currentTimeMillis()) / 1000; + RedisUtil.set(cacheKey, 1, expire); + } + + private Claims parseToken(String token, String prv) throws JwtLogoutException { + Claims claims = (Claims) Jwts.parserBuilder().setSigningKey(getSecretKey()).require("prv", prv).build().parse(token).getBody(); + if (isInBlack(claims.getId())) { + throw new JwtLogoutException(); + } + return claims; + } + + private SecretKey getSecretKey() { + return Keys.hmacShaKeyFor(ConfigKey.getBytes(StandardCharsets.UTF_8)); + } + + private String getBlackCacheKey(String jti) { + return ConfigCacheBlackPrefix + jti; } } diff --git a/src/main/java/xyz/playedu/api/types/JWTPayload.java b/src/main/java/xyz/playedu/api/types/JWTPayload.java index e643bc8..22d7c47 100644 --- a/src/main/java/xyz/playedu/api/types/JWTPayload.java +++ b/src/main/java/xyz/playedu/api/types/JWTPayload.java @@ -45,6 +45,4 @@ public class JWTPayload { */ private String prv; - private HashMap claims; - } diff --git a/src/main/java/xyz/playedu/api/util/ToolUtil.java b/src/main/java/xyz/playedu/api/util/HelperUtil.java similarity index 95% rename from src/main/java/xyz/playedu/api/util/ToolUtil.java rename to src/main/java/xyz/playedu/api/util/HelperUtil.java index 1c2eab9..b8633ea 100644 --- a/src/main/java/xyz/playedu/api/util/ToolUtil.java +++ b/src/main/java/xyz/playedu/api/util/HelperUtil.java @@ -3,14 +3,20 @@ package xyz.playedu.api.util; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.google.gson.reflect.TypeToken; +import org.springframework.util.DigestUtils; import java.io.*; import java.lang.reflect.Type; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.util.*; -public class ToolUtil { +public class HelperUtil { + + public static String MD5(String text) { + return DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8)); + } /** * 制作UUID diff --git a/src/main/java/xyz/playedu/api/util/MD5Util.java b/src/main/java/xyz/playedu/api/util/MD5Util.java deleted file mode 100644 index c613098..0000000 --- a/src/main/java/xyz/playedu/api/util/MD5Util.java +++ /dev/null @@ -1,12 +0,0 @@ -package xyz.playedu.api.util; - -import org.springframework.util.DigestUtils; - -import java.nio.charset.StandardCharsets; - -public class MD5Util { - - public static String md5(String text) { - return DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/src/main/java/xyz/playedu/api/util/RequestUtil.java b/src/main/java/xyz/playedu/api/util/RequestUtil.java index 2c3dc1f..19651e2 100644 --- a/src/main/java/xyz/playedu/api/util/RequestUtil.java +++ b/src/main/java/xyz/playedu/api/util/RequestUtil.java @@ -84,85 +84,38 @@ public class RequestUtil { return 0; } - /** - * 获取请求域名 - * 示例: https://127.0.0.1 - * - * @return String - * @author fzr - */ public static String domain() { HttpServletRequest request = RequestUtil.handler(); if (request != null) { String requestUrl = request.getRequestURL().toString(); List urls = Arrays.asList(requestUrl.split("/")); - String agree = "http:"; - if (request.getServerPort() == 443) { - agree = "https:"; - } - - return agree + "//" + urls.get(2).split(":")[0]; + return urls.get(2).split(":")[0]; } return null; } - /** - * 判断是否是GET请求 - * - * @return Boolean - * @author fzr - */ public static Boolean isGet() { - ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (servletRequestAttributes != null) { - HttpServletRequest request = servletRequestAttributes.getRequest(); - return request.getMethod().equals("GET"); - } - return false; + return isMethod("GET"); } - /** - * 判断是否是POST请求 - * - * @return Boolean - * @author fzr - */ public static Boolean isPost() { - ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (servletRequestAttributes != null) { - HttpServletRequest request = servletRequestAttributes.getRequest(); - return request.getMethod().equals("POST"); - } - return false; + return isMethod("POST"); } - /** - * 判断是否是PUT请求 - * - * @return Boolean - * @author fzr - */ public static Boolean isPUT() { - ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (servletRequestAttributes != null) { - HttpServletRequest request = servletRequestAttributes.getRequest(); - return request.getMethod().equals("PUT"); - } - return false; + return isMethod("PUT"); } - /** - * 判断是否是DELETE请求 - * - * @return Boolean - * @author fzr - */ public static Boolean isDelete() { + return isMethod("DELETE"); + } + + public static boolean isMethod(String method) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (servletRequestAttributes != null) { HttpServletRequest request = servletRequestAttributes.getRequest(); - return request.getMethod().equals("DELETE"); + return request.getMethod().equals(method); } return false; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c37ef10..b985512 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,5 +41,6 @@ playedu: jwt: key: "eJTJSLPv13fw9twbuPoeicypLqnSfYWL" #32个字符,加密key用来加密jwt的数据[运行本系统之前请务必修改] expire: 1296000 #token有效期[单位:秒,默认15天] + cache-black-prefix: "jwt:blk:" #主动注销的token黑名单缓存前缀