Skip to content

Commit 1e08c5c

Browse files
committed
Fixing several gaps with Role and Authority handling for OAuth2 based Users.
Add LoginHelperService and AuthorityService for user authentication and authority management
1 parent e50a5f2 commit 1e08c5c

File tree

7 files changed

+144
-73
lines changed

7 files changed

+144
-73
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,4 @@ application-local.yml
135135
/java_pid20864.hprof
136136
/java_pid23168.hprof
137137
/project_concatenated.txt
138+
/repomix-output.txt

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=3.1.0-SNAPSHOT
1+
version=3.1.0-SNAPSHOT
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.digitalsanctuary.spring.user.service;
2+
3+
import java.util.Collection;
4+
import java.util.stream.Collectors;
5+
import org.springframework.security.core.GrantedAuthority;
6+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.transaction.annotation.Transactional;
9+
import com.digitalsanctuary.spring.user.persistence.model.Privilege;
10+
import com.digitalsanctuary.spring.user.persistence.model.Role;
11+
import com.digitalsanctuary.spring.user.persistence.model.User;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
14+
15+
/**
16+
* The AuthorityService class provides helper methods for generating Spring Security's GrantedAuthority objects from a collection of roles and
17+
* privileges.
18+
*/
19+
@Slf4j
20+
@RequiredArgsConstructor
21+
@Service
22+
@Transactional
23+
public class AuthorityService {
24+
25+
/**
26+
* Generates the list of authorities for the given user from their roles and privileges.
27+
*
28+
* @param user The user whose authorities to generate.
29+
* @return The list of authorities for the user.
30+
*/
31+
public Collection<? extends GrantedAuthority> getAuthoritiesFromUser(User user) {
32+
return getAuthoritiesFromRoles(user.getRoles());
33+
}
34+
35+
/**
36+
*
37+
* Returns a collection of Spring Security's GrantedAuthority objects that corresponds to the privileges associated with the given collection of
38+
* roles.
39+
*
40+
* @param roles a collection of roles whose privileges should be converted into Spring Security's GrantedAuthority objects
41+
* @return a collection of Spring Security's GrantedAuthority objects that corresponds to the privileges associated with the given collection of
42+
* roles
43+
*/
44+
public Collection<? extends GrantedAuthority> getAuthoritiesFromRoles(Collection<Role> roles) {
45+
// flatMap streams the roles, and maps each Role to its privileges (a Collection of Privilege objects).
46+
// The stream of Collection<Privilege> objects is then flattened into a single stream of Privilege objects.
47+
// Finally, each Privilege is mapped to its name as a String, wrapped in a SimpleGrantedAuthority object,
48+
// and collected into a Set of GrantedAuthority objects.
49+
return roles.stream().flatMap(role -> role.getPrivileges().stream()).map(Privilege::getName).map(SimpleGrantedAuthority::new)
50+
.collect(Collectors.toSet());
51+
}
52+
53+
}

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package com.digitalsanctuary.spring.user.service;
22

3+
import java.util.Arrays;
4+
import org.springframework.context.ApplicationEventPublisher;
35
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
46
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
57
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
68
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
79
import org.springframework.security.oauth2.core.OAuth2Error;
810
import org.springframework.security.oauth2.core.user.OAuth2User;
911
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
13+
import com.digitalsanctuary.spring.user.audit.AuditEvent;
1014
import com.digitalsanctuary.spring.user.persistence.model.User;
15+
import com.digitalsanctuary.spring.user.persistence.repository.RoleRepository;
1116
import com.digitalsanctuary.spring.user.persistence.repository.UserRepository;
1217
import lombok.RequiredArgsConstructor;
1318
import lombok.extern.slf4j.Slf4j;
@@ -34,12 +39,26 @@
3439
*/
3540
@Slf4j
3641
@Service
42+
@Transactional
3743
@RequiredArgsConstructor
3844
public class DSOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
3945

4046
/** The user repository. */
4147
private final UserRepository userRepository;
4248

49+
/** The role repository. */
50+
private final RoleRepository roleRepository;
51+
52+
private final LoginHelperService loginHelperService;
53+
54+
55+
56+
/** The Event Publisher. */
57+
private final ApplicationEventPublisher eventPublisher;
58+
59+
/** The user role name. */
60+
private static final String USER_ROLE_NAME = "ROLE_USER";
61+
4362
DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
4463

4564
/**
@@ -68,7 +87,7 @@ public User handleOAuthLoginSuccess(String registrationId, OAuth2User oAuth2User
6887
"Sorry! An error occurred while processing your login request.");
6988
}
7089
log.debug("handleOAuthLoginSuccess: looking up user with email: {}", user.getEmail());
71-
User existingUser = userRepository.findByEmail(user.getEmail());
90+
User existingUser = userRepository.findByEmail(user.getEmail().toLowerCase());
7291
log.debug("handleOAuthLoginSuccess: existingUser: {}", existingUser);
7392
if (existingUser != null && registrationId != null) {
7493
log.debug("handleOAuthLoginSuccess: existingUser.getProvider(): {}", existingUser.getProvider());
@@ -97,12 +116,17 @@ public User handleOAuthLoginSuccess(String registrationId, OAuth2User oAuth2User
97116
* @param user The User object representing the authenticated user.
98117
* @return A User object representing the newly registered user.
99118
*/
119+
@Transactional
100120
private User registerNewOAuthUser(String registrationId, User user) {
101121
User.Provider provider = User.Provider.valueOf(registrationId.toUpperCase());
102122
user.setProvider(provider);
103-
// user.setRoles(Collections.singletonList(roleRepository.findByName(RoleName.ROLE_USER)));
123+
user.setRoles(Arrays.asList(roleRepository.findByName(USER_ROLE_NAME)));
104124
// We will trust OAuth2 providers to provide us with a verified email address.
105125
user.setEnabled(true);
126+
AuditEvent registrationAuditEvent = AuditEvent.builder().source(this).user(user).action("OAuth2 Registration Success").actionStatus("Success")
127+
.message("Registration Confirmed. User logged in.").build();
128+
129+
eventPublisher.publishEvent(registrationAuditEvent);
106130
return userRepository.save(user);
107131
}
108132

@@ -184,8 +208,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
184208
String registrationId = userRequest.getClientRegistration().getRegistrationId();
185209
log.debug("registrationId: " + registrationId);
186210
User dbUser = handleOAuthLoginSuccess(registrationId, user);
187-
DSUserDetails dsUserDetails = new DSUserDetails(dbUser);
188-
return dsUserDetails;
211+
DSUserDetails userDetails = loginHelperService.userLoginHelper(dbUser);
212+
return userDetails;
189213
}
190214

191215
}
Lines changed: 6 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
package com.digitalsanctuary.spring.user.service;
22

3-
import java.util.Collection;
4-
import java.util.Date;
5-
import java.util.stream.Collectors;
6-
import org.springframework.security.core.GrantedAuthority;
7-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
83
import org.springframework.security.core.userdetails.UserDetailsService;
94
import org.springframework.security.core.userdetails.UsernameNotFoundException;
105
import org.springframework.stereotype.Service;
116
import org.springframework.transaction.annotation.Transactional;
12-
import com.digitalsanctuary.spring.user.persistence.model.Privilege;
13-
import com.digitalsanctuary.spring.user.persistence.model.Role;
147
import com.digitalsanctuary.spring.user.persistence.model.User;
158
import com.digitalsanctuary.spring.user.persistence.repository.UserRepository;
169
import lombok.RequiredArgsConstructor;
@@ -29,8 +22,7 @@ public class DSUserDetailsService implements UserDetailsService {
2922
/** The user repository. */
3023
private final UserRepository userRepository;
3124

32-
/** The login attempt service. */
33-
private final LoginAttemptService loginAttemptService;
25+
private final LoginHelperService loginHelperService;
3426

3527
/** The request. */
3628
// private final HttpServletRequest request;
@@ -44,44 +36,12 @@ public class DSUserDetailsService implements UserDetailsService {
4436
*/
4537
@Override
4638
public DSUserDetails loadUserByUsername(final String email) throws UsernameNotFoundException {
47-
log.debug("DSUserDetailsService.loadUserByUsername:" + "called with username: {}", email);
48-
49-
try {
50-
User user = userRepository.findByEmail(email);
51-
if (user == null) {
52-
throw new UsernameNotFoundException("No user found with email/username: " + email);
53-
}
54-
// Updating lastActivity date for this login
55-
user.setLastActivityDate(new Date());
56-
57-
// Check if the user account is locked, but should be unlocked now, and unlock it
58-
user = loginAttemptService.checkIfUserShouldBeUnlocked(user);
59-
60-
Collection<? extends GrantedAuthority> authorities = getAuthorities(user.getRoles());
61-
DSUserDetails userDetails = new DSUserDetails(user, authorities);
62-
return userDetails;
63-
} catch (final Exception e) {
64-
log.error("DSUserDetailsService.loadUserByUsername:" + "Exception!", e);
65-
throw new RuntimeException(e);
39+
log.debug("DSUserDetailsService.loadUserByUsername: called with username: {}", email);
40+
User dbUser = userRepository.findByEmail(email);
41+
if (dbUser == null) {
42+
throw new UsernameNotFoundException("No user found with email/username: " + email);
6643
}
67-
}
68-
69-
/**
70-
*
71-
* Returns a collection of Spring Security's GrantedAuthority objects that corresponds to the privileges associated with the given collection of
72-
* roles.
73-
*
74-
* @param roles a collection of roles whose privileges should be converted into Spring Security's GrantedAuthority objects
75-
* @return a collection of Spring Security's GrantedAuthority objects that corresponds to the privileges associated with the given collection of
76-
* roles
77-
*/
78-
private Collection<? extends GrantedAuthority> getAuthorities(Collection<Role> roles) {
79-
// flatMap streams the roles, and maps each Role to its privileges (a Collection of Privilege objects).
80-
// The stream of Collection<Privilege> objects is then flattened into a single stream of Privilege objects.
81-
// Finally, each Privilege is mapped to its name as a String, wrapped in a SimpleGrantedAuthority object,
82-
// and collected into a Set of GrantedAuthority objects.
83-
return roles.stream().flatMap(role -> role.getPrivileges().stream()).map(Privilege::getName).map(SimpleGrantedAuthority::new)
84-
.collect(Collectors.toSet());
44+
return loginHelperService.userLoginHelper(dbUser);
8545
}
8646

8747
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.digitalsanctuary.spring.user.service;
2+
3+
import java.util.Collection;
4+
import java.util.Date;
5+
import org.springframework.security.core.GrantedAuthority;
6+
import org.springframework.stereotype.Service;
7+
import org.springframework.transaction.annotation.Transactional;
8+
import com.digitalsanctuary.spring.user.persistence.model.User;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
12+
/**
13+
* The LoginHelperService class provides helper methods for authenticating users after login. This class is used by the DSUserDetailsService and
14+
* DSOAuth2UserService classes to authenticate users after they have been successfully authenticated.
15+
*/
16+
@Slf4j
17+
@RequiredArgsConstructor
18+
@Service
19+
@Transactional
20+
public class LoginHelperService {
21+
22+
/** The login attempt service. */
23+
private final LoginAttemptService loginAttemptService;
24+
25+
private final AuthorityService authorityService;
26+
27+
/**
28+
* Helper method to authenticate a user after login. This method is called from the DSUserDetailsService and DSOAuth2UserService classes after a
29+
* user has been successfully authenticated.
30+
*
31+
* @param dbUser The user to authenticate.
32+
* @return The user details object.
33+
*/
34+
public DSUserDetails userLoginHelper(User dbUser) {
35+
// Updating lastActivity date for this login
36+
dbUser.setLastActivityDate(new Date());
37+
38+
// Check if the user account is locked, but should be unlocked now, and unlock it
39+
dbUser = loginAttemptService.checkIfUserShouldBeUnlocked(dbUser);
40+
41+
Collection<? extends GrantedAuthority> authorities = authorityService.getAuthoritiesFromUser(dbUser);
42+
DSUserDetails userDetails = new DSUserDetails(dbUser, authorities);
43+
return userDetails;
44+
}
45+
}

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

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1111
import org.springframework.security.core.Authentication;
1212
import org.springframework.security.core.GrantedAuthority;
13-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1413
import org.springframework.security.core.context.SecurityContextHolder;
1514
import org.springframework.security.core.session.SessionRegistry;
1615
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -22,8 +21,6 @@
2221
import com.digitalsanctuary.spring.user.dto.UserDto;
2322
import com.digitalsanctuary.spring.user.exceptions.UserAlreadyExistException;
2423
import com.digitalsanctuary.spring.user.persistence.model.PasswordResetToken;
25-
import com.digitalsanctuary.spring.user.persistence.model.Privilege;
26-
import com.digitalsanctuary.spring.user.persistence.model.Role;
2724
import com.digitalsanctuary.spring.user.persistence.model.User;
2825
import com.digitalsanctuary.spring.user.persistence.model.VerificationToken;
2926
import com.digitalsanctuary.spring.user.persistence.repository.PasswordResetTokenRepository;
@@ -101,8 +98,7 @@
10198
* </p>
10299
* <ul>
103100
* <li>{@link #emailExists(String)}: Checks if an email exists in the user repository.</li>
104-
* <li>{@link #getAuthorities(User)}: Generates the list of authorities for a user.</li>
105-
* <li>{@link #authenticateUser(DSUserDetails, List)}: Authenticates a user by setting the authentication object in the security context.</li>
101+
* <li>{@link #authenticateUser(DSUserDetails, Collection)}: Authenticates a user by setting the authentication object in the security context.</li>
106102
* <li>{@link #storeSecurityContextInSession()}: Stores the current security context in the session.</li>
107103
* </ul>
108104
*
@@ -193,6 +189,8 @@ public String getValue() {
193189
/** The user verification service. */
194190
public final UserVerificationService userVerificationService;
195191

192+
private final AuthorityService authorityService;
193+
196194
/** The user details service. */
197195
private final DSUserDetailsService dsUserDetailsService;
198196

@@ -323,7 +321,8 @@ public void changeUserPassword(final User user, final String password) {
323321
* @return true, if successful
324322
*/
325323
public boolean checkIfValidOldPassword(final User user, final String oldPassword) {
326-
System.out.println(user.getPassword() + " " + oldPassword);
324+
// Removed System.out.println, using log.debug for minimal output (avoid logging passwords in production)
325+
log.debug("Verifying old password for user: {}", user.getEmail());
327326
return passwordEncoder.matches(oldPassword, user.getPassword());
328327
}
329328

@@ -334,7 +333,7 @@ public boolean checkIfValidOldPassword(final User user, final String oldPassword
334333
* @return true, if the email address is already in the user repository
335334
*/
336335
private boolean emailExists(final String email) {
337-
return userRepository.findByEmail(email) != null;
336+
return userRepository.findByEmail(email.toLowerCase()) != null;
338337
}
339338

340339
/**
@@ -395,7 +394,7 @@ public void authWithoutPassword(User user) {
395394
}
396395

397396
// Generate authorities from user roles and privileges
398-
List<GrantedAuthority> authorities = getAuthorities(user);
397+
Collection<? extends GrantedAuthority> authorities = authorityService.getAuthoritiesFromUser(user);
399398

400399
// Authenticate user
401400
authenticateUser(userDetails, authorities);
@@ -406,26 +405,13 @@ public void authWithoutPassword(User user) {
406405
log.debug("UserService.authWithoutPassword: authenticated user: {}", user.getEmail());
407406
}
408407

409-
/**
410-
* Generates the list of authorities for the given user from their roles and privileges.
411-
*
412-
* @param user The user whose authorities to generate.
413-
* @return The list of authorities for the user.
414-
*/
415-
416-
private List<GrantedAuthority> getAuthorities(User user) {
417-
List<Privilege> privileges =
418-
user.getRoles().stream().map(Role::getPrivileges).flatMap(Collection::stream).distinct().collect(Collectors.toList());
419-
return privileges.stream().map(p -> new SimpleGrantedAuthority(p.getName())).collect(Collectors.toList());
420-
}
421-
422408
/**
423409
* Authenticates the user by creating an authentication object and setting it in the security context.
424410
*
425411
* @param userDetails The user details.
426412
* @param authorities The list of authorities for the user.
427413
*/
428-
private void authenticateUser(DSUserDetails userDetails, List<GrantedAuthority> authorities) {
414+
private void authenticateUser(DSUserDetails userDetails, Collection<? extends GrantedAuthority> authorities) {
429415
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
430416
SecurityContextHolder.getContext().setAuthentication(authentication);
431417
}
@@ -448,4 +434,6 @@ private void storeSecurityContextInSession() {
448434
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
449435
}
450436

437+
438+
451439
}

0 commit comments

Comments
 (0)