From 6df855a8168ec364f8f425a3073dbdcec3155607 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:45:35 +0800 Subject: [PATCH] Add configurable password strength policy --- README.md | 3 +- .../com/openisle/model/PasswordStrength.java | 7 +++ .../openisle/service/PasswordValidator.java | 57 +++++++++++++++++++ .../com/openisle/service/UserService.java | 3 + src/main/resources/application.properties | 2 + 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/openisle/model/PasswordStrength.java create mode 100644 src/main/java/com/openisle/service/PasswordValidator.java diff --git a/README.md b/README.md index 2aa874958..ff6c21c71 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ OpenIsle 基于 Spring Boot 构建,提供社区后台常见的注册、登录 * **角色权限**:内置 `ADMIN` 与 `USER`,管理员接口以 `/api/admin/**` 提供 * **文章与评论**:支持分类、评论及多级回复 * **图片上传**:`ImageUploader` 可接入不同云存储,示例中实现了腾讯云 COS -* **灵活配置**:数据库、邮件、存储等信息均可通过环境变量或 `application.properties` 设置 +* **灵活配置**:数据库、邮件、存储及密码强度均可通过环境变量或 `application.properties` 设置 * **简洁架构**:业务、持久化与安全配置清晰分层,便于扩展 ## 🚀 快速开始 @@ -45,6 +45,7 @@ OpenIsle 基于 Spring Boot 构建,提供社区后台常见的注册、登录 - `COS_BASE_URL`:腾讯云 COS 访问域名 - `JWT_SECRET`:JWT 签名密钥 - `JWT_EXPIRATION`:JWT 过期时间(毫秒) + - `PASSWORD_STRENGTH`:密码强度(LOW、MEDIUM、HIGH) 2. 启动项目: ```bash diff --git a/src/main/java/com/openisle/model/PasswordStrength.java b/src/main/java/com/openisle/model/PasswordStrength.java new file mode 100644 index 000000000..3d60fac28 --- /dev/null +++ b/src/main/java/com/openisle/model/PasswordStrength.java @@ -0,0 +1,7 @@ +package com.openisle.model; + +public enum PasswordStrength { + LOW, + MEDIUM, + HIGH +} diff --git a/src/main/java/com/openisle/service/PasswordValidator.java b/src/main/java/com/openisle/service/PasswordValidator.java new file mode 100644 index 000000000..5c0b0e57a --- /dev/null +++ b/src/main/java/com/openisle/service/PasswordValidator.java @@ -0,0 +1,57 @@ +package com.openisle.service; + +import com.openisle.model.PasswordStrength; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class PasswordValidator { + private final PasswordStrength strength; + + public PasswordValidator(@Value("${app.password.strength:LOW}") PasswordStrength strength) { + this.strength = strength; + } + + public void validate(String password) { + if (password == null || password.isEmpty()) { + throw new IllegalArgumentException("Password cannot be empty"); + } + switch (strength) { + case MEDIUM: + checkMedium(password); + break; + case HIGH: + checkHigh(password); + break; + default: + // LOW, nothing beyond non-empty + } + } + + private void checkMedium(String password) { + if (password.length() < 8) { + throw new IllegalArgumentException("Password must be at least 8 characters long"); + } + if (!password.matches(".*[A-Za-z].*") || !password.matches(".*\\d.*")) { + throw new IllegalArgumentException("Password must contain letters and numbers"); + } + } + + private void checkHigh(String password) { + if (password.length() < 12) { + throw new IllegalArgumentException("Password must be at least 12 characters long"); + } + if (!password.matches(".*[A-Z].*")) { + throw new IllegalArgumentException("Password must contain uppercase letters"); + } + if (!password.matches(".*[a-z].*")) { + throw new IllegalArgumentException("Password must contain lowercase letters"); + } + if (!password.matches(".*\\d.*")) { + throw new IllegalArgumentException("Password must contain numbers"); + } + if (!password.matches(".*[^A-Za-z0-9].*")) { + throw new IllegalArgumentException("Password must contain special characters"); + } + } +} diff --git a/src/main/java/com/openisle/service/UserService.java b/src/main/java/com/openisle/service/UserService.java index 9367134a9..330e78e16 100644 --- a/src/main/java/com/openisle/service/UserService.java +++ b/src/main/java/com/openisle/service/UserService.java @@ -2,6 +2,7 @@ package com.openisle.service; import com.openisle.model.User; import com.openisle.model.Role; +import com.openisle.service.PasswordValidator; import com.openisle.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -15,9 +16,11 @@ import java.util.Random; @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final PasswordValidator passwordValidator; private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); public User register(String username, String email, String password) { + passwordValidator.validate(password); // ── 先按用户名查 ────────────────────────────────────────── Optional byUsername = userRepository.findByUsername(username); if (byUsername.isPresent()) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b68c3defa..fa0481c63 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,6 +7,8 @@ spring.jpa.hibernate.ddl-auto=update # for jwt app.jwt.secret=${JWT_SECRET:ChangeThisSecretKeyForJwt} app.jwt.expiration=${JWT_EXPIRATION:86400000} +# Password strength: LOW, MEDIUM or HIGH +app.password.strength=${PASSWORD_STRENGTH:LOW} # Post publish mode: DIRECT or REVIEW app.post.publish-mode=${POST_PUBLISH_MODE:DIRECT}