From 7ce241cc1656a16eb44ef91f330c3650e8d71782 Mon Sep 17 00:00:00 2001 From: siujamo Date: Thu, 12 Mar 2026 13:56:06 +0800 Subject: [PATCH] feat: implement user credential management with new UserCredential entity and related components --- database/init.d/init-en_GB.sql | 20 ++++---- .../onixbyte/helix/domain/entity/User.java | 35 +------------ ...{UserIdentity.java => UserCredential.java} | 50 +++++++++---------- ...rIdentityId.java => UserCredentialId.java} | 32 ++++++------ .../helix/domain/view/UserIdentityView.java | 34 ++++++------- .../web/response/LoginSuccessResponse.java | 4 -- ...yProvider.java => CredentialProvider.java} | 24 ++++++++- .../filter/TokenAuthenticationFilter.java | 2 - .../onixbyte/helix/manager/UserManager.java | 20 +++++--- .../helix/mapper/UserCredentialMapper.java | 35 +++++++++++++ .../repository/UserCredentialRepository.java | 10 ++++ .../repository/UserIdentityRepository.java | 10 ---- ...sernamePasswordAuthenticationProvider.java | 19 +++++-- .../onixbyte/helix/service/AuthService.java | 22 ++++++-- .../onixbyte/helix/service/UserService.java | 16 +++--- .../resources/mapper/UserCredentialMapper.xml | 33 ++++++++++++ 16 files changed, 229 insertions(+), 137 deletions(-) rename src/main/java/com/onixbyte/helix/domain/entity/{UserIdentity.java => UserCredential.java} (78%) rename src/main/java/com/onixbyte/helix/domain/entity/embeddable/{UserIdentityId.java => UserCredentialId.java} (70%) rename src/main/java/com/onixbyte/helix/enumeration/{IdentityProvider.java => CredentialProvider.java} (58%) create mode 100644 src/main/java/com/onixbyte/helix/mapper/UserCredentialMapper.java create mode 100644 src/main/java/com/onixbyte/helix/repository/UserCredentialRepository.java delete mode 100644 src/main/java/com/onixbyte/helix/repository/UserIdentityRepository.java create mode 100644 src/main/resources/mapper/UserCredentialMapper.xml diff --git a/database/init.d/init-en_GB.sql b/database/init.d/init-en_GB.sql index 21c2396..9be7216 100644 --- a/database/init.d/init-en_GB.sql +++ b/database/init.d/init-en_GB.sql @@ -34,12 +34,12 @@ --- Type Definitions --- DROP TYPE IF EXISTS USER_STATUS CASCADE; DROP TYPE IF EXISTS STATUS CASCADE; -DROP TYPE IF EXISTS IDENTITY_PROVIDER CASCADE; +DROP TYPE IF EXISTS CREDENTIAL_PROVIDER CASCADE; DROP TYPE IF EXISTS SETTING_TYPE CASCADE; CREATE TYPE USER_STATUS AS ENUM ('ACTIVE', 'INACTIVE', 'LOCKED'); CREATE TYPE STATUS AS ENUM ('ACTIVE', 'INACTIVE'); -CREATE TYPE IDENTITY_PROVIDER AS ENUM ('LOCAL', 'OIDC', 'MICROSOFT_ENTRA_ID', 'GOOGLE_OIDC', 'SAML'); +CREATE TYPE CREDENTIAL_PROVIDER AS ENUM ('LOCAL', 'OIDC', 'MICROSOFT_ENTRA_ID', 'GOOGLE_OIDC', 'SAML'); CREATE TYPE SETTING_TYPE AS ENUM ('STRING', 'BOOLEAN', 'INT'); --- Departments Table --- @@ -186,15 +186,15 @@ SELECT 'johndoe', CURRENT_TIMESTAMP; --- User Identities Table --- -DROP TABLE IF EXISTS user_identities CASCADE; -CREATE TABLE user_identities +DROP TABLE IF EXISTS user_credentials CASCADE; +CREATE TABLE user_credentials ( - user_id BIGINT NOT NULL REFERENCES users (id), - provider IDENTITY_PROVIDER NOT NULL, - external_id VARCHAR(255) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, provider, external_id) + user_id BIGINT NOT NULL REFERENCES users (id), + provider CREDENTIAL_PROVIDER NOT NULL, + credential VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, provider, credential) ); --- Roles Table --- diff --git a/src/main/java/com/onixbyte/helix/domain/entity/User.java b/src/main/java/com/onixbyte/helix/domain/entity/User.java index f391a42..f6c60c4 100644 --- a/src/main/java/com/onixbyte/helix/domain/entity/User.java +++ b/src/main/java/com/onixbyte/helix/domain/entity/User.java @@ -48,12 +48,6 @@ public class User { @Column(nullable = false, length = 64) private String username; - /** - * The encrypted password for user authentication. - */ - @Column - private String password; - /** * The user's complete full name. */ @@ -124,10 +118,9 @@ public class User { public User() { } - public User(Long id, String username, String password, String fullName, String email, String regionAbbreviation, String phoneNumber, String avatarUrl, UserStatus status, Long departmentId, Long positionId, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String fullName, String email, String regionAbbreviation, String phoneNumber, String avatarUrl, UserStatus status, Long departmentId, Long positionId, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; - this.password = password; this.fullName = fullName; this.email = email; this.regionAbbreviation = regionAbbreviation; @@ -176,24 +169,6 @@ public class User { this.username = username; } - /** - * Gets the encrypted password. - * - * @return the encrypted password - */ - public String getPassword() { - return password; - } - - /** - * Sets the encrypted password. - * - * @param password the encrypted password (never plain text) - */ - public void setPassword(String password) { - this.password = password; - } - public String getFullName() { return fullName; } @@ -337,7 +312,6 @@ public class User { public static class UserBuilder { private Long id; private String username; - private String password; private String fullName; private String email; private String regionAbbreviation; @@ -362,11 +336,6 @@ public class User { return this; } - public UserBuilder password(String password) { - this.password = password; - return this; - } - public UserBuilder fullName(String fullName) { this.fullName = fullName; return this; @@ -423,7 +392,7 @@ public class User { * @return a new User instance */ public User build() { - return new User(id, username, password, fullName, email, regionAbbreviation, phoneNumber, avatarUrl, status, departmentId, positionId, createdAt, updatedAt); + return new User(id, username, fullName, email, regionAbbreviation, phoneNumber, avatarUrl, status, departmentId, positionId, createdAt, updatedAt); } } diff --git a/src/main/java/com/onixbyte/helix/domain/entity/UserIdentity.java b/src/main/java/com/onixbyte/helix/domain/entity/UserCredential.java similarity index 78% rename from src/main/java/com/onixbyte/helix/domain/entity/UserIdentity.java rename to src/main/java/com/onixbyte/helix/domain/entity/UserCredential.java index 8181256..243a3aa 100644 --- a/src/main/java/com/onixbyte/helix/domain/entity/UserIdentity.java +++ b/src/main/java/com/onixbyte/helix/domain/entity/UserCredential.java @@ -1,7 +1,7 @@ package com.onixbyte.helix.domain.entity; -import com.onixbyte.helix.enumeration.IdentityProvider; -import com.onixbyte.helix.domain.entity.embeddable.UserIdentityId; +import com.onixbyte.helix.enumeration.CredentialProvider; +import com.onixbyte.helix.domain.entity.embeddable.UserCredentialId; import jakarta.persistence.*; // 导入 Jakarta Persistence API import java.time.LocalDateTime; import java.util.Objects; @@ -19,14 +19,14 @@ import java.util.Objects; * @since 1.0 */ @Entity -@Table(name = "user_identities") -public class UserIdentity { +@Table(name = "user_credentials") +public class UserCredential { /** * The composite primary key for the entity, composed of userId, provider, and externalId. */ @EmbeddedId - private UserIdentityId id; + private UserCredentialId id; /** * The timestamp when this identity mapping was created. @@ -53,7 +53,7 @@ public class UserIdentity { * @param userId the user ID */ public void setUserId(Long userId) { - if (this.id == null) this.id = new UserIdentityId(); + if (this.id == null) this.id = new UserCredentialId(); this.id.setUserId(userId); } @@ -61,7 +61,7 @@ public class UserIdentity { * Gets the external identity provider from the composite primary key. * @return the provider */ - public IdentityProvider getProvider() { + public CredentialProvider getProvider() { return this.id != null ? this.id.getProvider() : null; } @@ -69,8 +69,8 @@ public class UserIdentity { * Sets the external identity provider within the composite primary key. * @param provider the provider */ - public void setProvider(IdentityProvider provider) { - if (this.id == null) this.id = new UserIdentityId(); + public void setProvider(CredentialProvider provider) { + if (this.id == null) this.id = new UserCredentialId(); this.id.setProvider(provider); } @@ -78,17 +78,17 @@ public class UserIdentity { * Gets the unique identifier from the external provider from the composite primary key. * @return the external ID */ - public String getExternalId() { - return this.id != null ? this.id.getExternalId() : null; + public String getCredential() { + return this.id != null ? this.id.getCredential() : null; } /** * Sets the unique identifier from the external provider within the composite primary key. - * @param externalId the external ID + * @param credential the external ID */ - public void setExternalId(String externalId) { - if (this.id == null) this.id = new UserIdentityId(); - this.id.setExternalId(externalId); + public void setCredential(String credential) { + if (this.id == null) this.id = new UserCredentialId(); + this.id.setCredential(credential); } public LocalDateTime getCreatedAt() { @@ -109,12 +109,12 @@ public class UserIdentity { // --- Constructors (Adjusted for EmbeddedId) --- - public UserIdentity() { - this.id = new UserIdentityId(); // Initialize ID object for safety + public UserCredential() { + this.id = new UserCredentialId(); // Initialize ID object for safety } - public UserIdentity(Long userId, IdentityProvider provider, String externalId, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = new UserIdentityId(userId, provider, externalId); + public UserCredential(Long userId, CredentialProvider provider, String externalId, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = new UserCredentialId(userId, provider, externalId); this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -125,7 +125,7 @@ public class UserIdentity { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - UserIdentity that = (UserIdentity) o; + UserCredential that = (UserCredential) o; return Objects.equals(id, that.id); // Entity equality based on primary key } @@ -139,7 +139,7 @@ public class UserIdentity { return "UserIdentity{" + "userId=" + getUserId() + ", provider=" + getProvider() + - ", externalId='" + getExternalId() + '\'' + + ", externalId='" + getCredential() + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; @@ -162,7 +162,7 @@ public class UserIdentity { */ public static class UserIdentityBuilder { private Long userId; - private IdentityProvider provider; + private CredentialProvider provider; private String externalId; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -175,7 +175,7 @@ public class UserIdentity { return this; } - public UserIdentityBuilder provider(IdentityProvider provider) { + public UserIdentityBuilder provider(CredentialProvider provider) { this.provider = provider; return this; } @@ -200,8 +200,8 @@ public class UserIdentity { * * @return a new UserIdentity instance */ - public UserIdentity build() { - return new UserIdentity(userId, provider, externalId, createdAt, updatedAt); + public UserCredential build() { + return new UserCredential(userId, provider, externalId, createdAt, updatedAt); } } diff --git a/src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserIdentityId.java b/src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserCredentialId.java similarity index 70% rename from src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserIdentityId.java rename to src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserCredentialId.java index 3ec828f..ff3c994 100644 --- a/src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserIdentityId.java +++ b/src/main/java/com/onixbyte/helix/domain/entity/embeddable/UserCredentialId.java @@ -1,6 +1,6 @@ package com.onixbyte.helix.domain.entity.embeddable; -import com.onixbyte.helix.enumeration.IdentityProvider; +import com.onixbyte.helix.enumeration.CredentialProvider; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.Enumerated; @@ -18,7 +18,7 @@ import java.util.Objects; * from that provider. */ @Embeddable -public class UserIdentityId implements Serializable { +public class UserCredentialId implements Serializable { @Serial private static final long serialVersionUID = 1L; @@ -37,23 +37,23 @@ public class UserIdentityId implements Serializable { @Column(nullable = false) @Enumerated @JdbcType(PostgreSQLEnumJdbcType.class) - private IdentityProvider provider; + private CredentialProvider provider; /** * The unique identifier from the external provider, corresponding to the 'external_id' column. */ @Column(nullable = false) - private String externalId; + private String credential; // --- Constructors --- - public UserIdentityId() { + public UserCredentialId() { } - public UserIdentityId(Long userId, IdentityProvider provider, String externalId) { + public UserCredentialId(Long userId, CredentialProvider provider, String credential) { this.userId = userId; this.provider = provider; - this.externalId = externalId; + this.credential = credential; } // --- Getters and Setters (Omitted for brevity, but should exist) --- @@ -65,32 +65,32 @@ public class UserIdentityId implements Serializable { this.userId = userId; } - public IdentityProvider getProvider() { + public CredentialProvider getProvider() { return provider; } - public void setProvider(IdentityProvider provider) { + public void setProvider(CredentialProvider provider) { this.provider = provider; } - public String getExternalId() { - return externalId; + public String getCredential() { + return credential; } - public void setExternalId(String externalId) { - this.externalId = externalId; + public void setCredential(String credential) { + this.credential = credential; } // --- equals and hashCode (REQUIRED for composite keys) --- @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - UserIdentityId that = (UserIdentityId) o; - return Objects.equals(userId, that.userId) && provider == that.provider && Objects.equals(externalId, that.externalId); + UserCredentialId that = (UserCredentialId) o; + return Objects.equals(userId, that.userId) && provider == that.provider && Objects.equals(credential, that.credential); } @Override public int hashCode() { - return Objects.hash(userId, provider, externalId); + return Objects.hash(userId, provider, credential); } } diff --git a/src/main/java/com/onixbyte/helix/domain/view/UserIdentityView.java b/src/main/java/com/onixbyte/helix/domain/view/UserIdentityView.java index aeab9d4..2abfa03 100644 --- a/src/main/java/com/onixbyte/helix/domain/view/UserIdentityView.java +++ b/src/main/java/com/onixbyte/helix/domain/view/UserIdentityView.java @@ -1,7 +1,7 @@ package com.onixbyte.helix.domain.view; -import com.onixbyte.helix.enumeration.IdentityProvider; -import com.onixbyte.helix.domain.entity.UserIdentity; +import com.onixbyte.helix.enumeration.CredentialProvider; +import com.onixbyte.helix.domain.entity.UserCredential; import java.time.LocalDateTime; import java.util.Objects; @@ -26,7 +26,7 @@ public class UserIdentityView { /** * The external identity provider. */ - private IdentityProvider provider; + private CredentialProvider provider; /** * The unique identifier from the external provider. @@ -52,8 +52,8 @@ public class UserIdentityView { /** * Constructor with all fields. */ - public UserIdentityView(Long userId, IdentityProvider provider, String externalId, - LocalDateTime createdAt, LocalDateTime updatedAt) { + public UserIdentityView(Long userId, CredentialProvider provider, String externalId, + LocalDateTime createdAt, LocalDateTime updatedAt) { this.userId = userId; this.provider = provider; this.externalId = externalId; @@ -64,19 +64,19 @@ public class UserIdentityView { /** * Creates a UserIdentityView from a UserIdentity entity. * - * @param userIdentity the UserIdentity entity + * @param userCredential the UserIdentity entity * @return the UserIdentityView object */ - public static UserIdentityView fromEntity(UserIdentity userIdentity) { - if (userIdentity == null) { + public static UserIdentityView fromEntity(UserCredential userCredential) { + if (userCredential == null) { return null; } return new UserIdentityView( - userIdentity.getUserId(), - userIdentity.getProvider(), - userIdentity.getExternalId(), - userIdentity.getCreatedAt(), - userIdentity.getUpdatedAt() + userCredential.getUserId(), + userCredential.getProvider(), + userCredential.getCredential(), + userCredential.getCreatedAt(), + userCredential.getUpdatedAt() ); } @@ -88,11 +88,11 @@ public class UserIdentityView { this.userId = userId; } - public IdentityProvider getProvider() { + public CredentialProvider getProvider() { return provider; } - public void setProvider(IdentityProvider provider) { + public void setProvider(CredentialProvider provider) { this.provider = provider; } @@ -162,7 +162,7 @@ public class UserIdentityView { */ public static class UserIdentityViewBuilder { private Long userId; - private IdentityProvider provider; + private CredentialProvider provider; private String externalId; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -175,7 +175,7 @@ public class UserIdentityView { return this; } - public UserIdentityViewBuilder provider(IdentityProvider provider) { + public UserIdentityViewBuilder provider(CredentialProvider provider) { this.provider = provider; return this; } diff --git a/src/main/java/com/onixbyte/helix/domain/web/response/LoginSuccessResponse.java b/src/main/java/com/onixbyte/helix/domain/web/response/LoginSuccessResponse.java index 94f1643..997c742 100644 --- a/src/main/java/com/onixbyte/helix/domain/web/response/LoginSuccessResponse.java +++ b/src/main/java/com/onixbyte/helix/domain/web/response/LoginSuccessResponse.java @@ -6,8 +6,4 @@ public record LoginSuccessResponse( String accessToken, User user ) { - - public LoginSuccessResponse { - user.setPassword(null); - } } diff --git a/src/main/java/com/onixbyte/helix/enumeration/IdentityProvider.java b/src/main/java/com/onixbyte/helix/enumeration/CredentialProvider.java similarity index 58% rename from src/main/java/com/onixbyte/helix/enumeration/IdentityProvider.java rename to src/main/java/com/onixbyte/helix/enumeration/CredentialProvider.java index 068ee13..50cc99b 100644 --- a/src/main/java/com/onixbyte/helix/enumeration/IdentityProvider.java +++ b/src/main/java/com/onixbyte/helix/enumeration/CredentialProvider.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2024-2026 OnixByte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package com.onixbyte.helix.enumeration; import com.onixbyte.helix.config.AuthenticationConfig; @@ -18,7 +40,7 @@ import com.onixbyte.helix.config.AuthenticationConfig; * @since 1.0.0 * @see AuthenticationConfig */ -public enum IdentityProvider { +public enum CredentialProvider { /** * Local identity provider using the application's internal user database. diff --git a/src/main/java/com/onixbyte/helix/filter/TokenAuthenticationFilter.java b/src/main/java/com/onixbyte/helix/filter/TokenAuthenticationFilter.java index 9f3e9e6..2ad79e9 100644 --- a/src/main/java/com/onixbyte/helix/filter/TokenAuthenticationFilter.java +++ b/src/main/java/com/onixbyte/helix/filter/TokenAuthenticationFilter.java @@ -67,8 +67,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { .map((authority) -> (GrantedAuthority) authority::getCode) .toList(); - user.setPassword(null); - var authentication = UsernamePasswordAuthentication.authenticated(user, authorities); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); diff --git a/src/main/java/com/onixbyte/helix/manager/UserManager.java b/src/main/java/com/onixbyte/helix/manager/UserManager.java index 2bf0e72..80dcbc1 100644 --- a/src/main/java/com/onixbyte/helix/manager/UserManager.java +++ b/src/main/java/com/onixbyte/helix/manager/UserManager.java @@ -1,6 +1,8 @@ package com.onixbyte.helix.manager; import com.onixbyte.helix.common.regex.Patterns; +import com.onixbyte.helix.domain.web.request.ResetPasswordRequest; +import com.onixbyte.helix.mapper.UserCredentialMapper; import com.onixbyte.helix.shared.CacheName; import com.onixbyte.helix.domain.database.query.wrapper.QueryUserWrapper; import com.onixbyte.helix.domain.entity.User; @@ -30,16 +32,19 @@ public class UserManager { private final UserMapper userMapper; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final UserCredentialMapper userCredentialMapper; @Autowired public UserManager( UserMapper userMapper, UserRepository userRepository, - PasswordEncoder passwordEncoder + PasswordEncoder passwordEncoder, + UserCredentialMapper userCredentialMapper ) { this.userMapper = userMapper; this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; + this.userCredentialMapper = userCredentialMapper; } /** @@ -87,11 +92,6 @@ public class UserManager { var userToUpdate = userRepository.findById(user.getId()) .orElseThrow(() -> new BizException(HttpStatus.BAD_REQUEST, "找不到 ID 为" + user.getId() + "的用户信息")); - Optional.ofNullable(user.getPassword()) - .filter(StringUtils::isNotBlank) - .map(passwordEncoder::encode) - .ifPresent(userToUpdate::setPassword); - Optional.ofNullable(user.getFullName()) .filter(StringUtils::isNotBlank) .ifPresent(userToUpdate::setFullName); @@ -126,4 +126,12 @@ public class UserManager { return userToUpdate; } + + @Transactional(rollbackFor = Throwable.class) + public void updateUserPassword(ResetPasswordRequest request) { + userCredentialMapper.updateUserCredential( + request.id(), + passwordEncoder.encode(request.password()) + ); + } } diff --git a/src/main/java/com/onixbyte/helix/mapper/UserCredentialMapper.java b/src/main/java/com/onixbyte/helix/mapper/UserCredentialMapper.java new file mode 100644 index 0000000..23099c6 --- /dev/null +++ b/src/main/java/com/onixbyte/helix/mapper/UserCredentialMapper.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024-2026 OnixByte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.onixbyte.helix.mapper; + +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserCredentialMapper { + + int updateUserCredential( + @Param("userId") Long userId, + @Param("encodedPassword") String encodedPassword + ); +} diff --git a/src/main/java/com/onixbyte/helix/repository/UserCredentialRepository.java b/src/main/java/com/onixbyte/helix/repository/UserCredentialRepository.java new file mode 100644 index 0000000..99825eb --- /dev/null +++ b/src/main/java/com/onixbyte/helix/repository/UserCredentialRepository.java @@ -0,0 +1,10 @@ +package com.onixbyte.helix.repository; + +import com.onixbyte.helix.domain.entity.UserCredential; +import com.onixbyte.helix.domain.entity.embeddable.UserCredentialId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserCredentialRepository extends JpaRepository { +} diff --git a/src/main/java/com/onixbyte/helix/repository/UserIdentityRepository.java b/src/main/java/com/onixbyte/helix/repository/UserIdentityRepository.java deleted file mode 100644 index 932c91c..0000000 --- a/src/main/java/com/onixbyte/helix/repository/UserIdentityRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onixbyte.helix.repository; - -import com.onixbyte.helix.domain.entity.UserIdentity; -import com.onixbyte.helix.domain.entity.embeddable.UserIdentityId; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface UserIdentityRepository extends JpaRepository { -} diff --git a/src/main/java/com/onixbyte/helix/security/provider/UsernamePasswordAuthenticationProvider.java b/src/main/java/com/onixbyte/helix/security/provider/UsernamePasswordAuthenticationProvider.java index dc20d46..35f72fc 100644 --- a/src/main/java/com/onixbyte/helix/security/provider/UsernamePasswordAuthenticationProvider.java +++ b/src/main/java/com/onixbyte/helix/security/provider/UsernamePasswordAuthenticationProvider.java @@ -1,13 +1,17 @@ package com.onixbyte.helix.security.provider; import com.onixbyte.helix.domain.entity.Authority; +import com.onixbyte.helix.domain.entity.UserCredential; +import com.onixbyte.helix.enumeration.CredentialProvider; import com.onixbyte.helix.exception.BizException; import com.onixbyte.helix.manager.AuthorityManager; import com.onixbyte.helix.manager.UserManager; +import com.onixbyte.helix.repository.UserCredentialRepository; import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; @@ -24,16 +28,19 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro private final UserManager userManager; private final PasswordEncoder passwordEncoder; private final AuthorityManager authorityManager; + private final UserCredentialRepository userCredentialRepository; @Autowired public UsernamePasswordAuthenticationProvider( UserManager userManager, PasswordEncoder passwordEncoder, - AuthorityManager authorityManager + AuthorityManager authorityManager, + UserCredentialRepository userCredentialRepository ) { this.userManager = userManager; this.passwordEncoder = passwordEncoder; this.authorityManager = authorityManager; + this.userCredentialRepository = userCredentialRepository; } @Override @@ -49,14 +56,20 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); } + // get user credentials from database + var userCredentials = userCredentialRepository.findOne(Example.of(UserCredential.builder() + .provider(CredentialProvider.LOCAL) + .userId(user.getId()) + .build())) + .orElseThrow(() -> new BizException(HttpStatus.UNAUTHORIZED, "您还没有配置密码,请使用第三方账号登录")); + // validate password - if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), user.getPassword())) { + if (!passwordEncoder.matches(usernamePasswordAuthentication.getCredentials(), userCredentials.getCredential())) { log.error("User {} is trying to authenticate but password is incorrect.", usernamePasswordAuthentication.getPrincipal()); throw new BizException(HttpStatus.UNAUTHORIZED, "用户名或密码错误。"); } // erase credentials - user.setPassword(null); usernamePasswordAuthentication.eraseCredentials(); // get authorities diff --git a/src/main/java/com/onixbyte/helix/service/AuthService.java b/src/main/java/com/onixbyte/helix/service/AuthService.java index fa71d9d..1837d30 100644 --- a/src/main/java/com/onixbyte/helix/service/AuthService.java +++ b/src/main/java/com/onixbyte/helix/service/AuthService.java @@ -3,7 +3,9 @@ package com.onixbyte.helix.service; import com.onixbyte.helix.domain.entity.Setting; import com.onixbyte.helix.domain.entity.User; import com.onixbyte.helix.domain.web.request.LoginRequest; +import com.onixbyte.helix.enumeration.ApplicationMode; import com.onixbyte.helix.exception.BizException; +import com.onixbyte.helix.manager.ApplicationManager; import com.onixbyte.helix.manager.CaptchaManager; import com.onixbyte.helix.manager.SecurityManager; import com.onixbyte.helix.manager.SettingManager; @@ -30,18 +32,20 @@ public class AuthService { private final AuthenticationManager authenticationManager; private final SettingManager settingManager; private final SecurityManager securityManager; + private final ApplicationManager applicationManager; @Autowired public AuthService( CaptchaManager captchaManager, AuthenticationManager authenticationManager, SettingManager settingManager, - SecurityManager securityManager - ) { + SecurityManager securityManager, + ApplicationManager applicationManager) { this.captchaManager = captchaManager; this.authenticationManager = authenticationManager; this.settingManager = settingManager; this.securityManager = securityManager; + this.applicationManager = applicationManager; } /** @@ -94,10 +98,22 @@ public class AuthService { public ResponseCookie buildCookie(String cookieName, String token) { var cookieBuilder = ResponseCookie.from(cookieName, token) - .httpOnly(true) .maxAge(securityManager.getTokenValidDuration()) + .secure(true) .path("/"); + var applicationMode = applicationManager.getApplicationMode(); + switch (applicationMode) { + case PRODUCTION -> { + cookieBuilder.httpOnly(true); + } + case DEVELOPMENT -> { + cookieBuilder.sameSite("NONE"); + } + case null, default -> { + } + } + return cookieBuilder.build(); } diff --git a/src/main/java/com/onixbyte/helix/service/UserService.java b/src/main/java/com/onixbyte/helix/service/UserService.java index 361aa7d..d536cd5 100644 --- a/src/main/java/com/onixbyte/helix/service/UserService.java +++ b/src/main/java/com/onixbyte/helix/service/UserService.java @@ -12,6 +12,8 @@ import com.onixbyte.helix.domain.web.request.ResetPasswordRequest; import com.onixbyte.helix.domain.web.request.EditUserRequest; import com.onixbyte.helix.domain.web.response.UserDetailResponse; import com.onixbyte.helix.manager.*; +import com.onixbyte.helix.mapper.UserCredentialMapper; +import com.onixbyte.helix.repository.UserCredentialRepository; import com.onixbyte.identitygenerator.IdentityGenerator; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +37,8 @@ public class UserService { private final ApplicationManager applicationManager; private final DepartmentManager departmentManager; private final PositionManager positionManager; + private final UserCredentialRepository userCredentialRepository; + private final UserCredentialMapper userCredentialMapper; @Autowired public UserService( @@ -45,8 +49,8 @@ public class UserService { PasswordEncoder passwordEncoder, ApplicationManager applicationManager, DepartmentManager departmentManager, - PositionManager positionManager - ) { + PositionManager positionManager, + UserCredentialRepository userCredentialRepository, UserCredentialMapper userCredentialMapper) { this.userManager = userManager; this.userIdentityGenerator = userIdentityGenerator; this.roleManager = roleManager; @@ -55,6 +59,8 @@ public class UserService { this.applicationManager = applicationManager; this.departmentManager = departmentManager; this.positionManager = positionManager; + this.userCredentialRepository = userCredentialRepository; + this.userCredentialMapper = userCredentialMapper; } public Page queryUserDetailsPage(Pageable pageable, QueryUserRequest request) { @@ -98,7 +104,6 @@ public class UserService { var user = userManager.save(User.builder() .id(userIdentityGenerator.nextId()) .username(request.username()) - .password(passwordEncoder.encode(request.password())) .fullName(request.fullName()) .email(request.email()) .regionAbbreviation(request.regionAbbreviation()) @@ -168,10 +173,7 @@ public class UserService { @Transactional(rollbackFor = Throwable.class) public void resetPassword(ResetPasswordRequest request) { - userManager.updateUser(User.builder() - .id(request.id()) - .password(request.password()) - .build()); + userManager.updateUserPassword(request); } @Transactional(rollbackFor = Throwable.class) diff --git a/src/main/resources/mapper/UserCredentialMapper.xml b/src/main/resources/mapper/UserCredentialMapper.xml new file mode 100644 index 0000000..1166a0e --- /dev/null +++ b/src/main/resources/mapper/UserCredentialMapper.xml @@ -0,0 +1,33 @@ + + + + + + + UPDATE user_credentials + SET credential = #{encodedPassword} + WHERE user_id = #{userId} + AND provider = 'LOCAL'::credential_provider + + \ No newline at end of file