From e28467f3fa8a490aa11486fa49096e38ff594976 Mon Sep 17 00:00:00 2001 From: wzp Date: Sun, 7 Apr 2024 17:20:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(all):=20=E6=B7=BB=E5=8A=A0=E4=BA=86JWT?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .idea/inspectionProfiles/Project_Default.xml | 12 +++ .idea/modules.xml | 1 + .idea/modules/clubs.iml | 10 +++ .../org/mmga/clubs/commands/UserCommands.java | 4 +- .../ECKeyProviderConfiguration.java | 81 +++++++++++++++++++ .../mmga/clubs/controller/UserController.java | 9 ++- .../mmga/clubs/entities/user/UserLoginVo.java | 5 +- .../mmga/clubs/entities/user/UserRegVo.java | 5 +- .../org/mmga/clubs/service/UserService.java | 27 +++++-- .../java/org/mmga/clubs/utils/JwtUtils.java | 26 +++++- .../java/org/mmga/clubs/utils/ShellUtils.java | 2 + src/main/resources/application.properties | 3 - 13 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules/clubs.iml create mode 100644 src/main/java/org/mmga/clubs/configuration/ECKeyProviderConfiguration.java diff --git a/.gitignore b/.gitignore index 50e85cf..075e000 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,5 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath +# Running +run/ \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..4ac183f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index ccfd51a..b1ac391 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,6 +2,7 @@ + diff --git a/.idea/modules/clubs.iml b/.idea/modules/clubs.iml new file mode 100644 index 0000000..f192152 --- /dev/null +++ b/.idea/modules/clubs.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/mmga/clubs/commands/UserCommands.java b/src/main/java/org/mmga/clubs/commands/UserCommands.java index a63a40e..7105c2b 100644 --- a/src/main/java/org/mmga/clubs/commands/UserCommands.java +++ b/src/main/java/org/mmga/clubs/commands/UserCommands.java @@ -20,12 +20,12 @@ public class UserCommands { } @ShellMethod("创建用户") public void createUser(String name, String password) { - BaseResponse user = this.userController.createUser(new UserRegVo(name, DigestUtils.md5Hex(password), 1)); + BaseResponse user = this.userController.createUser(new UserRegVo(name, DigestUtils.md5Hex(password), 1), null); ShellUtils.logToResult(log, user); } @ShellMethod("登录") public void login(String name, String password) { - BaseResponse user = this.userController.login(new UserLoginVo(name, DigestUtils.md5Hex(password))); + BaseResponse user = this.userController.login(new UserLoginVo(name, DigestUtils.md5Hex(password)), null); ShellUtils.logToResult(log, user); } } diff --git a/src/main/java/org/mmga/clubs/configuration/ECKeyProviderConfiguration.java b/src/main/java/org/mmga/clubs/configuration/ECKeyProviderConfiguration.java new file mode 100644 index 0000000..b0b0d6c --- /dev/null +++ b/src/main/java/org/mmga/clubs/configuration/ECKeyProviderConfiguration.java @@ -0,0 +1,81 @@ +package org.mmga.clubs.configuration; + +import lombok.SneakyThrows; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +@Configuration +public class ECKeyProviderConfiguration { + private KeyPair keyPair; + private ECPublicKey publicKey; + private ECPrivateKey privateKey; + @SneakyThrows + public KeyPair keyPair(){ + if (this.keyPair != null){ + return this.keyPair; + } + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("secp256r1"); + keyPairGenerator.initialize(ecGenParameterSpec); + this.keyPair = keyPairGenerator.generateKeyPair(); + return this.keyPair; + } + @SneakyThrows + @Bean + public ECPublicKey ecPublicKey(){ + if (this.publicKey == null){ + File file = new File("jwt_pub.pem"); + if (file.exists()){ + FileInputStream publicKeyInputStream = new FileInputStream(file); + byte[] publicKeyData = publicKeyInputStream.readAllBytes(); + publicKeyInputStream.close(); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + this.publicKey = (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyData)); + }else { + this.publicKey = (ECPublicKey) keyPair().getPublic(); + if (!file.createNewFile()) { + throw new RuntimeException("Can't create public key file"); + } + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(publicKey.getEncoded()); + fileOutputStream.close(); + } + } + return this.publicKey; + } + @SneakyThrows + @Bean + public ECPrivateKey ecPrivateKey(){ + if (this.privateKey == null){ + File file = new File("jwt_pri.pem"); + if (file.exists()){ + FileInputStream publicKeyInputStream = new FileInputStream(file); + byte[] publicKeyData = publicKeyInputStream.readAllBytes(); + publicKeyInputStream.close(); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + this.privateKey = (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(publicKeyData)); + }else { + this.privateKey = (ECPrivateKey) keyPair().getPrivate(); + if (!file.createNewFile()) { + throw new RuntimeException("Can't create private key file"); + } + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(privateKey.getEncoded()); + fileOutputStream.close(); + } + } + return this.privateKey; + } +} diff --git a/src/main/java/org/mmga/clubs/controller/UserController.java b/src/main/java/org/mmga/clubs/controller/UserController.java index f8b30d2..702e41c 100644 --- a/src/main/java/org/mmga/clubs/controller/UserController.java +++ b/src/main/java/org/mmga/clubs/controller/UserController.java @@ -3,6 +3,7 @@ package org.mmga.clubs.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.mmga.clubs.entities.BaseResponse; import org.mmga.clubs.entities.user.User; @@ -24,12 +25,12 @@ public class UserController { } @PostMapping("/login") @Operation(description = "用户登录", responses = {@ApiResponse(description = "返回是否登录成功", responseCode = "200")}) - public BaseResponse login(@RequestBody UserLoginVo user){ - return service.login(user); + public BaseResponse login(@RequestBody UserLoginVo user, HttpServletResponse response){ + return service.login(user, response); } @PutMapping("/create") @Operation(description = "创建用户", responses = {@ApiResponse(description = "返回创建后的用户")}) - public BaseResponse createUser(@RequestBody UserRegVo user){ - return service.createUser(user); + public BaseResponse createUser(@RequestBody UserRegVo user, HttpServletResponse response){ + return service.createUser(user, response); } } diff --git a/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java b/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java index b63125b..1174fcb 100644 --- a/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java +++ b/src/main/java/org/mmga/clubs/entities/user/UserLoginVo.java @@ -1,4 +1,7 @@ package org.mmga.clubs.entities.user; -public record UserLoginVo(String name, String password) { +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "用户登录参数类") +public record UserLoginVo(@Schema(description = "用户名") String username,@Schema(description = "用户密码(使用MD5摘要后的hex字符串)") String password) { } diff --git a/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java b/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java index 2f3175b..8bc3033 100644 --- a/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java +++ b/src/main/java/org/mmga/clubs/entities/user/UserRegVo.java @@ -1,4 +1,7 @@ package org.mmga.clubs.entities.user; -public record UserRegVo(String username, String password, int auth) { +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "用户注册参数") +public record UserRegVo(@Schema(description = "用户名") String username,@Schema(description = "密码(使用MD5摘要后的hex字符串)") String password,@Schema(description = "用户的权限组ID") int auth) { } diff --git a/src/main/java/org/mmga/clubs/service/UserService.java b/src/main/java/org/mmga/clubs/service/UserService.java index c24f631..c3373e4 100644 --- a/src/main/java/org/mmga/clubs/service/UserService.java +++ b/src/main/java/org/mmga/clubs/service/UserService.java @@ -1,9 +1,11 @@ package org.mmga.clubs.service; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.codec.digest.DigestUtils; import org.mmga.clubs.dao.UserDao; import org.mmga.clubs.entities.BaseResponse; import org.mmga.clubs.entities.user.*; +import org.mmga.clubs.utils.JwtUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -11,27 +13,36 @@ import org.springframework.stereotype.Service; public class UserService { private final UserDao userDao; private final AuthService authService; + private final JwtUtils jwtUtils; @Autowired - public UserService(UserDao userDao, AuthService authService){ + public UserService(UserDao userDao, AuthService authService, JwtUtils jwtUtils){ this.userDao = userDao; this.authService = authService; + this.jwtUtils = jwtUtils; } - public BaseResponse login(UserLoginVo user) { - UserVo userVo = userDao.getUser(user.name(), DigestUtils.sha1Hex(user.password())); + public BaseResponse login(UserLoginVo user, HttpServletResponse response) { + UserVo userVo = userDao.getUser(user.username(), DigestUtils.sha1Hex(user.password())); User u = packageUser(userVo); + if (response != null && u != null){ + response.addHeader("Set-Authorization", jwtUtils.createToken(u)); + } return u == null ? BaseResponse.failed(404, "无效用户") : BaseResponse.success(u); } - public BaseResponse createUser(UserRegVo user) { + public BaseResponse createUser(UserRegVo user, HttpServletResponse response) { String username = user.username(); if (userDao.countUser(username) > 0) { return BaseResponse.failed(409, "用户已存在"); } - UserRegResponseVo response = new UserRegResponseVo(user); - userDao.addUser(response); - int newUserId = response.getId(); - return BaseResponse.success(packageUser(userDao.getUserById(newUserId))); + UserRegResponseVo responseVo = new UserRegResponseVo(user); + userDao.addUser(responseVo); + int newUserId = responseVo.getId(); + User newUser = packageUser(userDao.getUserById(newUserId)); + if (newUser != null && response != null) { + response.addHeader("Set-Authorization", jwtUtils.createToken(newUser)); + } + return BaseResponse.success(newUser); } private User packageUser(UserVo vo) { if (vo == null){ diff --git a/src/main/java/org/mmga/clubs/utils/JwtUtils.java b/src/main/java/org/mmga/clubs/utils/JwtUtils.java index bbf54f1..b085014 100644 --- a/src/main/java/org/mmga/clubs/utils/JwtUtils.java +++ b/src/main/java/org/mmga/clubs/utils/JwtUtils.java @@ -1,8 +1,28 @@ package org.mmga.clubs.utils; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import lombok.extern.slf4j.Slf4j; +import org.mmga.clubs.entities.user.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +@Component +@Slf4j public class JwtUtils { - public static String createToken(int userId){ - //TODO 签名密钥创建 - return ""; + private final ECPublicKey ecPublicKey; + private final ECPrivateKey ecPrivateKey; + @Autowired + public JwtUtils(ECPublicKey ecPublicKey, ECPrivateKey ecPrivateKey){ + this.ecPublicKey = ecPublicKey; + this.ecPrivateKey = ecPrivateKey; + } + public String createToken(User user){ + String jwt = JWT.create().withClaim("uid", user.getId()).sign(Algorithm.ECDSA512(ecPublicKey, ecPrivateKey)); + log.debug("对用户:{},生成JWT:{}", user.getName(), jwt); + return jwt; } } diff --git a/src/main/java/org/mmga/clubs/utils/ShellUtils.java b/src/main/java/org/mmga/clubs/utils/ShellUtils.java index 886e8bf..5f3f4b5 100644 --- a/src/main/java/org/mmga/clubs/utils/ShellUtils.java +++ b/src/main/java/org/mmga/clubs/utils/ShellUtils.java @@ -2,7 +2,9 @@ package org.mmga.clubs.utils; import org.mmga.clubs.entities.BaseResponse; import org.slf4j.Logger; +import org.springframework.stereotype.Component; +@Component public class ShellUtils { public static void logToResult(Logger logger, BaseResponse response){ logger.info("返回代码:{},返回信息:{}", response.getCode(), response.getMsg()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fa2059e..58de871 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,2 @@ spring.application.name=ITZX-Clubs-Home-Server -spring.datasource.url=jdbc:mysql://wzpmc.cn:3306/itzx -spring.datasource.username=itzx -spring.datasource.password=jdy53QFMm3yATQk3 springdoc.swagger-ui.path=/docs \ No newline at end of file