How to implement AuditorAware with Spring Data JPA and Spring Security? - spring

We use Hibernate/JPA, Spring, Spring Data and Spring Security in our application. I have a standard User entity which is mapped using JPA. Further, I have a UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUsername(String username);
}
which follows the Spring Data convention for naming query methods. I have an entity
#Entity
public class Foo extends AbstractAuditable<User, Long> {
private String name;
}
I want to use Spring Data auditing support. (As descripe here.) Hence I created a AuditorService as follows:
#Service
public class AuditorService implements AuditorAware<User> {
private UserRepository userRepository;
#Override
public User getCurrentAuditor() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
List<User> users = userRepository.findByUsername(username);
if (users.size() > 0) {
return users.get(0);
} else {
throw new IllegalArgumentException();
}
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
When I create a method
#Transactional
public void createFoo() {
Foo bar = new Foo();
fooRepository.save(foo);
}
Where everything is correctly wired and FooRepository is a Spring Data CrudRepository. Then a StackOverflowError is thrown since the the call to findByUsername seems to trigger hibernate to flush the data to the database which triggers AuditingEntityListener who calls AuditorService#getCurrentAuditor which again triggers a flush and so on.
How to avoid this recursion? Is there a "canonical way" to load the User entity? Or is there a way to prevent Hibernate/JPA from flushing?

I got the same issue and what I did was just change the propagation on the findByUsername(username) method to Propagation.REQUIRES_NEW, I suspected that was a problem with the transactions, so I changed to use a new transaction and that worked well for me. I hope this can help.
#Repository
public interface UserRepository extends JpaRepository<User, String> {
#Transactional(propagation = Propagation.REQUIRES_NEW)
List<User> findByUsername(String username);
}

The solution is not to fetch the User record in the AuditorAware implementation. This triggers the described loop, since a select query triggers a flush (this is the case since Hibernate/JPA wants to write the data to the database to commit the transaction before executing the select), which triggers a call to AuditorAware#getCurrentAuditor.
The solution is to store the User record in the UserDetails provided to Spring Security. Hence I created my own implementation:
public class UserAwareUserDetails implements UserDetails {
private final User user;
private final Collection<? extends GrantedAuthority> grantedAuthorities;
public UserAwareUserDetails(User user) {
this(user, new ArrayList<GrantedAuthority>());
}
public UserAwareUserDetails(User user, Collection<? extends GrantedAuthority> grantedAuthorities) {
this.user = user;
this.grantedAuthorities = grantedAuthorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return user.getSaltedPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
Further, I changed my UserDetailsService to load the User and create UserAwareUserDetails. Now it is possible to access the User instance through the SercurityContextHolder:
#Override
public User getCurrentAuditor() {
return ((UserAwareUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
}

It looks like you use a User entity for two different things:
authentication
audit
I think it will be better to prepare a special AuditableUser for audit purpose (it will have identical username field as original User).
Consider following case: you want to delete some User from database. If all your audit objects are linked to User then they will a) loose author b) may be deleted by cascade too (depends on how the link is implemented). Not sure that you want it.
So by using special AuditableUser you will have:
no recursion
ability to delete some User from the system and preserve all audit info about it

To be honest, You do not actually require one another entity.
For example, I had similar problem and I resolved it in following way:
public class SpringSecurityAuditorAware implements AuditorAware<SUser>, ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOGGER = getLogger(SpringSecurityAuditorAware.class);
#Autowired
SUserRepository repository;
private SUser systemUser;
#Override
public SUser getCurrentAuditor() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SUser principal;
if (authentication == null || !authentication.isAuthenticated()) {
principal = systemUser;
} else {
principal = (SUser) authentication.getPrincipal();
}
LOGGER.info(String.format("Current auditor is >>> %s", principal));
return principal;
}
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
if (this.systemUser == null) {
LOGGER.info("%s >>> loading system user");
systemUser = this.repository.findOne(QSUser.sUser.credentials.login.eq("SYSTEM"));
}
}
}
Where SUser is both the class which I use for auditing as well as for the security.
I had maybe different use case than Yours and my approach will be deleted after, but it can be resolved like this.

Related

Custom Principal with OAuth2 in existing form login application

I'm trying to add OAuth2 login to an existing form login application. So far I've added the required configuration to get Google auth working and the goal is to enable existing username/password users (or new users) to login both ways.
All my controllers rely on my UserDetails implementation:
public class User implements UserDetails {
private Long id;
private String email;
private String password;
private String googleAccessToken;
// ...
And I can get the logged user in controllers like this:
#GetMapping
public String index(#AuthenticationPrincipal User user, Model model) {
So what I've done is to implement my custom OAuth2UserService to fetch the existing user from the database but I can't find the way to set the User as the principal.
In previous versions of the OAuth2 integration it seemed to exist a simpler solution based on PrincipalExtractor but it is no longer available.
#Service
#RequiredArgsConstructor
public class OAuth2UserDetailsService implements OAuth2UserService<OidcUserRequest, OidcUser> {
private final UsersRepository usersRepository;
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
switch (userRequest.getClientRegistration().getClientName()) {
case "google":
user = usersRepository.findOneByGoogleAccessToken(userRequest.getAccessToken());
break;
default:
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token"));
}
// here I should return my user principal
return new DefaultOidcUser(null, null);
}
Any ideas?
Finally solved this returning an instance of OidcUser:
public class UserPrincipal implements UserDetails, OidcUser {
// ...
}
And in the OAuth2UserService:
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUserService delegate = new OidcUserService();
User user;
// Delegate to the default implementation for loading a user
OidcUser oidcUser = delegate.loadUser(userRequest);
// ...
return new UserPrincipal(user, oidcUser);
}

Hibernate validation - autowired returns null

After looking around, I couldn't find any good solution to this.
My autowired didn't work as expected where it returns null. I've autowired this particular class in other classes and it works so it only doesn't work in constraintvalidator classes.
UserService class
#Service
public class UserService {
#Autowired
private UserRepository userRep;
public void addUser(User user) {
userRep.save(user);
}
public void deleteUser(long userId) {
userRep.deleteById(userId);
}
public List<User> retrieveAllUsers(){
Iterable<User>temp =userRep.findAll();
List<User>allUsers = null;
temp.forEach(allUsers::add);
return allUsers;
}
public boolean searchByEmail(String email) {
return userRep.findByEmail(email);
}
public void updateUser(User user) {
userRep.save(user);
}
}
Annotation interface class
#Target(ElementType.FIELD)
//When will the annotation be processed compilation, runtime etc
#Retention(RetentionPolicy.RUNTIME)
//Where is the logic
#Constraint(validatedBy = EmailValidator.class)
#Documented
public #interface ValidEmail {
//Error message
String message() default "Invalid email";
//Required for annotation
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Annotation logic class. The autowired here returns null
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
#Autowired
private UserService service;
//Actual place to place the logic to check if the data is valid or not
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if (email == null) {
return false;
}
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email)
&& service.searchByEmail(email);
}
else {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email);
}
}
#Override
public void initialize(ValidEmail validEmail) {
validEmail.message();
}
}
Main
#SpringBootApplication
#ComponentScan(basePackages = {
"com.Alex.Mains", "com.Alex.UserPackage", "com.Alex.Flights", "com.Alex.Security"
})
#EntityScan( basePackages = {"com.Alex.UserPackage", "com.Alex.Flights"})
#EnableJpaRepositories({"com.Alex.UserPackage", "com.Alex.Flights"})
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
// #Bean
// public Validator validator(final AutowireCapableBeanFactory beanFactory) {
//
// ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
// .configure()
// .constraintValidatorFactory(new SpringConstraintValidatorFactory(beanFactory))
// .buildValidatorFactory();
//
// return validatorFactory.getValidator();
// }
}
Edit: Tried #Componenet
Fixed with adding the following to application.properties. No idea why but it works
spring.jpa.properties.javax.persistence.validation.mode=none
EDIT: My Suggestion
Instead of a custom validator, use the existing #EMail and a unique constraint:
#Entity
public class User {
// ...your properties
#Email
#Column(unique = true)
private String email.
// Rest of class...
}
OLD:
So, first off:
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
You are fetching all the Users from the database, just to check whether any users exists? This is very, very inefficient. If you are already using Spring Data, you can just do
#Query("SELECT COUNT(*) > 0 FROM Users")
boolean anyExists();
Furthermore, your Service does not get injected, because EmailValidator is a POJO (plain old java object) and not a Spring managed component. If you annotate it with #Component or #Service Spring will take care of injection.
But I would not recommend that. I'm not sure what your exact use case is, but validators are often used on Entities and as such, they get called when the entity is created or updated. You don't want to issue additional queries in those cases.
Like I said, I don't know what exactly you are trying to achieve, but you could use the existing #Email validator (you can even provide a custom regular expression with the regexp attribute).

Storing user only in mongo db if condition from webflux mono is true in kotlin

I have the following question. I want to save a user into a mongo db via reactive spring data repositories only if the e-mail of the user is not already present.
#Component("userService")
class UserService(private val repository: UserRepository){
fun checkIfEMailExists(email: String): Mono<Boolean> {
return repository.findByEMail(email).hasElement()
}
fun create(user: User): Mono<User> {
//not sure how to do this bit here
this.checkIfEMailExists(user.email)
.filter{ it -> it == true}
.map{repository.save(user)}
}
}
So, basically I am not sure how i can handle the Mono of boolean to do something only if it's value is true (and otherwise thrown an exception)
First, your MongoDB repository should be a reactive one and return a Mono<User> or Flux<User> for that kind of signature:
public interface UserRepository extends ReactiveMongoRepository<User, String> {
Mono<User> findByEmail(String email);
}
Then you can chain that reactive type with other operators like this:
#Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Mono<User> create(User user) {
return this.userRepository.findByEmail(user.getEmail())
.flatMap(existingUser -> Mono.error(new UserAlreadyPresentException(existingUser.getEmail())))
.then(this.userRepository.save(user));
}
class UserAlreadyPresentException extends RuntimeException {
public UserAlreadyPresentException(String email) {
super("User already present with email " + email);
}
}
}

Spring Security Ouath2 : Extended UserDetails not returned by the Principal object

Last week I started on extending the UserDetails class to be able to support a custom field. The special thing about this field is that it gets filled with a value that depends an a request parameter. I managed to implement this correctly (so the question does not focus on that).
Now the thing is that after a successfull login the UserDetails object gets filled correctly (I was able to see this using a AuthenticationSuccessHandler) and client recieves a JWT token from the OAuth2 provider. The client then tries to fetch more details on the user by visiting the "/uaa/user" endpoint. This is set to return the Principal object. But after checking the contents of the Principal object I was supprised that the UserDetails object was missing. The method getPrincipal() only returned the username instead of the UserDetails object.
According to this question this is the result of a failed login. The AuthenticationToken (in this case a UsernamePasswordAuthenticationToken) gets rejected by the AuthenticationManager. I have no idea why it should do such a thing. The authentication with the default UserDetails object seems to work just fine. Can someone help me solve this problem?
Some details on the implemented classes (as mentioned above). Some code has been left out here for reasons.
CustomUserDetails
public class CustomUserDetails extends User {
private final Integer custom;
public CustomUserDetails (...default params..., Integer custom) {
super(...default params...);
this.custom = custom;
}
}
CustomUserDetailsService
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Override
public CustomUserDetails loadUserByUsername(String username) throw UsernameNotFoundException {
return new CustomUserDetails(...default params..., 12345);
}
}
Configuration
#Autowired
private CustomUserDetails userDetails;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetails);
}
User Endpoint
#RequestMapping(value = "/user", method = RequestMethod.GET)
#ResponseBody
public Principal getDetails(Principal user) {
return user;
}
The Principal object returned here should have the UserDetails object inside of it and should return this to the client. But instead of that it only returns a String with the username when you call getPrincipal();
In the end I want the JSON returned by the User endpoint (which returns the Principle object) to contain the custom field I added to the UserDetails.
Thanks in advance.
Generally, you need the annotation #AuthenticationPrincipal, but I will suggest you to build your own annotation, something like this:
/**
* Our own {#link AuthenticationPrincipal} annotation as suggested by
* http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#mvc-authentication-principal
*
*/
#Target({ElementType.PARAMETER, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#AuthenticationPrincipal
public #interface CurrentUser {}
Then you can have this Principal in this way:
#RequestMapping(..)
public Principal test(#CurrentUser Principal principal){..}
BUT, IMHO you should have your own Impl of Principal, or rather extends the existing impl. something like this:
public MyPrincipal extends org.springframework.security.core.userdetails.User {..}
In this case you can return values whatever you want to.
You can use this method, in any case to get extended user details object, in controller or anywhere you need. There can be cons in this method, but its effective.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
MyUserDetails myUser = (MyUserDetails) auth.getPrincipal();
public class MyUserDetails implements
org.springframework.security.core.userdetails.UserDetails {
private User user; //This is the user domain.
private String firstName;
private String lastName;
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(user.getRole().getName()));
return authList;
}
public String getPassword() {
return user.getPassword();
}
public String getUsername() {
return user.getEmail();
}
public boolean isAccountNonExpired() {
return ((user.getAccountState() == AccountState.InActive) || (user.getAccountState() == AccountState.Blocked) ? false : true);
}
public boolean isAccountNonLocked() {
return (user.getAccountState() == AccountState.Locked) ? false : true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return ((user.getAccountState() == AccountState.Active)
|| (user.getAccountState() == AccountState.PasswordReset)
|| (user.getAccountState() == AccountState.UnVerified) ? true
: false);
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

How can I get user's logged Id from SecurityContextHolder using Spring Social?

So, how can I get the user's id from a current logged user from any social providers?
Well I know I can build a custom SocialUser, the same I do for User, but in the case there is no getter on SocialUserDetails and the method I got just accepts userDetails, instead of a normal "Person" entity.
public class SocialUsersDetailServiceImpl implements SocialUserDetailsService {
private UserDetailsService service;
public SocialUsersDetailServiceImpl(UserDetailsService service) {
this.service = service;
}
#Override
public CSocialUserDetails loadUserByUserId(String username) throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = (UserDetails) service.loadUserByUsername(username);
return new CustomSocialUser(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities(), userDetails.getUserIdentifier()??);
}
}
But there is no ".getUserIdentifier()" method on UserDetails, there is some workaround for this?
The way I do for User:
#Service
public class UserDetailsServiceImpl implements CUserDetailsService {
#Resource
private PersonRepository respository;
#Override
public CUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Person p = repository.findByUsername(username);
return new CUser(p.getUsername(), p.getPassword(), p.grantedAuthorities(), p.getIdPerson());
}
}
and the CUser:
public class CUser extends User{
private Number identifier;
public CUser(String username, String password, Collection<? extends GrantedAuthority> authorities, Number identifier) {
super(username, password, authorities);
this.identifier = identifier;
}
public Number getUserIdentifier() {
return identifier;
}
}

Resources