Skip to content

Commit cd666a7

Browse files
authored
Merge pull request #194 from devondragon/fix/hibernate-entity-management-issue
Fix/hibernate entity management issue, and other improvements
2 parents b52eff6 + 030c9d0 commit cd666a7

File tree

9 files changed

+730
-16
lines changed

9 files changed

+730
-16
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ Check out the [Spring User Framework Demo Application](https://github.com/devond
8484
<dependency>
8585
<groupId>com.digitalsanctuary</groupId>
8686
<artifactId>ds-spring-user-framework</artifactId>
87-
<version>3.2.2</version>
87+
<version>3.3.0</version>
8888
</dependency>
8989
```
9090

9191
### Gradle
9292

9393
```groovy
94-
implementation 'com.digitalsanctuary:ds-spring-user-framework:3.2.2'
94+
implementation 'com.digitalsanctuary:ds-spring-user-framework:3.3.0'
9595
```
9696

9797
## Quick Start

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=3.2.4-SNAPSHOT
1+
version=3.3.0-SNAPSHOT
22
mavenCentralPublishing=true
33
mavenCentralAutomaticPublishing=true

src/main/java/com/digitalsanctuary/spring/user/listener/AuthenticationEventListener.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import org.springframework.context.event.EventListener;
44
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
55
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
6+
import org.springframework.security.oauth2.core.user.OAuth2User;
67
import org.springframework.stereotype.Component;
8+
import com.digitalsanctuary.spring.user.service.DSUserDetails;
79
import com.digitalsanctuary.spring.user.service.LoginAttemptService;
810
import lombok.RequiredArgsConstructor;
911
import lombok.extern.slf4j.Slf4j;
@@ -22,28 +24,74 @@ public class AuthenticationEventListener {
2224

2325
/**
2426
* This method listens for successful authentications and handles account lockout functionality.
27+
* It properly handles different authentication types including form login, OAuth2, and OIDC.
2528
*
2629
* @param success the success event
2730
*/
2831
@EventListener
2932
public void onSuccess(AuthenticationSuccessEvent success) {
30-
// Handle successful authentication, e.g. logging or auditing
31-
log.debug("Authentication success: " + success.getAuthentication().getName());
32-
String username = success.getAuthentication().getName();
33-
loginAttemptService.loginSucceeded(username);
33+
// Extract username/email based on the principal type
34+
String username = null;
35+
Object principal = success.getAuthentication().getPrincipal();
36+
37+
if (principal instanceof DSUserDetails) {
38+
// Form login or custom authentication
39+
username = ((DSUserDetails) principal).getUsername();
40+
log.debug("Authentication success for DSUserDetails: {}", username);
41+
} else if (principal instanceof OAuth2User) {
42+
// OAuth2/OIDC authentication - try to get email
43+
OAuth2User oauth2User = (OAuth2User) principal;
44+
username = oauth2User.getAttribute("email");
45+
if (username == null) {
46+
// Fallback to name if email is not available
47+
username = oauth2User.getName();
48+
}
49+
log.debug("Authentication success for OAuth2User: {}", username);
50+
} else if (principal instanceof String) {
51+
// Basic authentication or remember-me
52+
username = (String) principal;
53+
log.debug("Authentication success for String principal: {}", username);
54+
} else {
55+
// Fallback to getName() method for unknown principal types
56+
username = success.getAuthentication().getName();
57+
log.debug("Authentication success for unknown principal type {}: {}",
58+
principal != null ? principal.getClass().getName() : "null", username);
59+
}
60+
61+
// Only process login success if we have a valid username
62+
if (username != null && !username.trim().isEmpty()) {
63+
loginAttemptService.loginSucceeded(username);
64+
} else {
65+
log.warn("Could not extract valid username from authentication event - not tracking");
66+
}
3467
}
3568

3669
/**
3770
* This method listens for authentication failures and handles account lockout functionality.
71+
* It properly handles different authentication types including form login, OAuth2, and OIDC.
3872
*
3973
* @param failure the failure event
4074
*/
4175
@EventListener
4276
public void onFailure(AbstractAuthenticationFailureEvent failure) {
43-
// Handle unsuccessful authentication, e.g. logging or auditing
44-
log.debug("Authentication failure: " + failure.getException().getMessage());
77+
// For failures, try to get username from getName() first (the attempted username)
4578
String username = failure.getAuthentication().getName();
46-
loginAttemptService.loginFailed(username);
79+
80+
// If getName() is null/empty, try to extract from principal
81+
if (username == null || username.trim().isEmpty()) {
82+
Object principal = failure.getAuthentication().getPrincipal();
83+
if (principal instanceof String) {
84+
username = (String) principal;
85+
}
86+
}
87+
88+
// Only process login failure if we have a valid username
89+
if (username != null && !username.trim().isEmpty()) {
90+
log.debug("Authentication failure for user '{}': {}", username, failure.getException().getMessage());
91+
loginAttemptService.loginFailed(username);
92+
} else {
93+
log.warn("Could not extract valid username from authentication failure event - not tracking");
94+
}
4795
}
4896

4997
}

src/main/java/com/digitalsanctuary/spring/user/persistence/model/User.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.digitalsanctuary.spring.user.persistence.model;
22

3+
import java.util.ArrayList;
34
import java.util.Date;
5+
import java.util.HashSet;
46
import java.util.List;
7+
import java.util.Set;
58
import org.springframework.data.annotation.CreatedDate;
69
import org.springframework.data.annotation.LastModifiedDate;
710
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
811
import jakarta.persistence.*;
912
import lombok.Data;
13+
import lombok.EqualsAndHashCode;
14+
import lombok.ToString;
1015

1116
/**
1217
* The User Entity. Part of the basic User ->> Role ->> Privilege structure. This is the primary user data object. You can add to this, or add
@@ -92,11 +97,13 @@ public enum Provider {
9297
@Temporal(TemporalType.TIMESTAMP)
9398
private Date lockedDate;
9499

95-
/** The roles. */
100+
/** The roles - stored as Set to avoid Hibernate immutable collection issues */
101+
@ToString.Exclude
102+
@EqualsAndHashCode.Exclude
96103
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
97104
@JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
98105
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
99-
private List<Role> roles;
106+
private Set<Role> roles = new HashSet<>();
100107

101108
/**
102109
* Instantiates a new user.
@@ -122,4 +129,43 @@ public void setLastActivityDate() {
122129
public String getFullName() {
123130
return firstName + " " + lastName;
124131
}
132+
133+
/**
134+
* Gets the roles as a List for backward compatibility.
135+
*
136+
* @return the roles as a List
137+
*/
138+
public List<Role> getRoles() {
139+
return new ArrayList<>(roles);
140+
}
141+
142+
/**
143+
* Sets the roles from a List for backward compatibility.
144+
* Creates a new HashSet to ensure the collection is mutable.
145+
*
146+
* @param rolesList the roles to set
147+
*/
148+
public void setRoles(List<Role> rolesList) {
149+
this.roles = new HashSet<>(rolesList != null ? rolesList : new ArrayList<>());
150+
}
151+
152+
/**
153+
* Gets the roles as a Set (preferred method).
154+
* Returns a defensive copy to prevent external modification.
155+
*
156+
* @return the roles as a Set
157+
*/
158+
public Set<Role> getRolesAsSet() {
159+
return new HashSet<>(roles);
160+
}
161+
162+
/**
163+
* Sets the roles from a Set (preferred method).
164+
* Creates a new HashSet to ensure the collection is mutable.
165+
*
166+
* @param rolesSet the roles to set
167+
*/
168+
public void setRolesAsSet(Set<Role> rolesSet) {
169+
this.roles = new HashSet<>(rolesSet != null ? rolesSet : new HashSet<>());
170+
}
125171
}

src/main/java/com/digitalsanctuary/spring/user/security/WebSecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ public RoleHierarchy roleHierarchy() {
266266
log.error("WebSecurityConfig.roleHierarchy: rolesAndPrivilegesConfig.getRoleHierarchyString() is null!");
267267
return null;
268268
}
269-
RoleHierarchyImpl roleHierarchy = RoleHierarchyImpl.fromHierarchy(rolesAndPrivilegesConfig.getRoleHierarchyString());
269+
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
270+
roleHierarchy.setHierarchy(rolesAndPrivilegesConfig.getRoleHierarchyString());
270271
log.debug("WebSecurityConfig.roleHierarchy: roleHierarchy: {}", roleHierarchy.toString());
271272
return roleHierarchy;
272273
}

src/main/java/com/digitalsanctuary/spring/user/service/DSOidcUserService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.digitalsanctuary.spring.user.service;
22

3+
import java.util.Arrays;
34
import com.digitalsanctuary.spring.user.persistence.model.User;
5+
import com.digitalsanctuary.spring.user.persistence.repository.RoleRepository;
46
import com.digitalsanctuary.spring.user.persistence.repository.UserRepository;
57
import lombok.RequiredArgsConstructor;
68
import lombok.extern.slf4j.Slf4j;
@@ -41,6 +43,9 @@ public class DSOidcUserService implements OAuth2UserService<OidcUserRequest, Oid
4143

4244
/** The user repository. */
4345
private final UserRepository userRepository;
46+
47+
/** The role repository. */
48+
private final RoleRepository roleRepository;
4449

4550
OidcUserService defaultOidcUserService = new OidcUserService();
4651

@@ -100,7 +105,7 @@ public User handleOidcLoginSuccess(String registrationId, OidcUser oidcUser) {
100105
private User registerNewOidcUser(String registrationId, User user) {
101106
User.Provider provider = User.Provider.valueOf(registrationId.toUpperCase());
102107
user.setProvider(provider);
103-
// user.setRoles(Collections.singletonList(roleRepository.findByName(RoleName.ROLE_USER)));
108+
user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
104109
// We will trust OAuth2 providers to provide us with a verified email address.
105110
user.setEnabled(true);
106111
return userRepository.save(user);

0 commit comments

Comments
 (0)