Custom ConstraintValidator not working (Exception) - java

I have a problem with Spring, in particular with ConstraintValidator. I want to release custom validation for field contains email. It must be unique. OK. The task is clear, I do it like this:
UniqueEmail.java
#Constraint(validatedBy = UniqueEmailValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueEmailValidator.java
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
private final UserRepository userRepository;
public UniqueEmailValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return email != null && !userRepository.findByEmail(email).isPresent();
}
}
User.java
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
#NotEmpty(message = "Email should not be empty")
#Email(message = "Email should be valid")
#UniqueEmail(message = "Email address is already registered")
private String email;
#NotEmpty(message = "Password should not be empty")
private String password;
#NotEmpty(message = "Name should not be empty")
#Size(min = 2, max = 30, message = "Name should be between 2 and 30 characters")
private String username;
private boolean enabled = true;
#Enumerated(value = EnumType.STRING)
private Role role;
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
AuthController.java
#Controller
#RequestMapping("/auth")
public class AuthController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public AuthController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#GetMapping("/login")
public String login(Principal principal) {
if(principal != null)
return "redirect:/";
return "auth/login";
}
#GetMapping("/register")
public String register(#ModelAttribute("user") User user, Principal principal) {
if(principal != null)
return "redirect:/";
return "auth/register";
}
#PostMapping("/register")
public String newCustomer(Principal principal, Model model, #ModelAttribute("user") #Valid User user, BindingResult bindingResult) {
if(principal != null)
return "redirect:/";
if(bindingResult.hasErrors())
return "auth/register";
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole(Role.CUSTOMER);
userRepository.saveAndFlush(user);
model.addAttribute("success", true);
model.addAttribute("user", new User());
return "auth/register";
}
}
If i try input existing email everything works fine (got message "Email address is already registered"). But if I try input a new email, I get an error "Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: bla.bla.bla.validator.UniqueEmailValidator.] with root cause".
I'm trying do with #Component and #Autowired, but got the same result.
I'm trying do with noArgs constructor and got NullPointerException (UserRepository not injected).
Why? I don'n understand.

The error above is because your UniqueEmailValidator would have an empty constructor.
See also: javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator
The userRepository you can inject into your Constraint Validator using #Autowired annotation, as stated:
here: Inject Spring Beans into Annotation-based Bean Validator
and here: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation-beanvalidation-spring-constraints
Please pay also attention to put #Valid annotation before User user in all related controller methods

Solved the problem in this way. I understand that this is a crutch, but I have not yet found another solution.
#Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
private UserRepository userRepository;
public UniqueEmailValidator() {
}
#Autowired
public UniqueEmailValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if(userRepository == null)
return true;
return email != null && !userRepository.findByEmail(email).isPresent();
}
}
Added noArgs Constructor, and in isValid function added ckeck if null userRepository.

Related

User account is locked while signing in

I'm starting to learn Spring Security now and I got with trouble. I wrote configuration classes, getting data from DB and so on, but in my webpage I see the message "User account is locked" and error parameter in url after signing in.
MessengerApplication.java
#SpringBootApplication
public class MessengerApplication {
public static void main(String[] args) {
SpringApplication.run(MessengerApplication.class, args);
}
}
MainPageController.java
#RestController
public class MainPageController {
#RequestMapping("/")
public ModelAndView greeting() {
Map<String, Object> model = new HashMap<>();
model.put("data", "world");
return new ModelAndView("main_page", model);
}
}
SecurityConfig.java
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserServiceImpl userService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
UserServiceImpl.java
#Service
public class UserServiceImpl implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public MyUserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findUserByName(s);
if (user == null)
throw new UsernameNotFoundException(s);
return new MyUserDetails(user);
}
}
UserRepositoryImpl.java
#Repository
public class UserRepositoryImpl implements UserRepository {
#Autowired
JdbcTemplate template;
#Override
public User findUserByName(String name) {
return template.queryForObject("select * from users where name = ?", rowMapper, name);
}
private RowMapper<User> rowMapper = new RowMapper<User>() {
#Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setPassword(resultSet.getString("password"));
user.setName(resultSet.getString("name"));
user.setId(resultSet.getLong("id"));
return user;
}
};
}
UserRepository.java
public interface UserRepository {
User findUserByName(String name);
}
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String password;
// get(), set()
}
MyUserDetails.java
public class MyUserDetails implements UserDetails {
private User user;
public MyUserDetails(User user) {
this.user = user;
}
// ...
}
The method is isAccountNonLocked, emphasis on non. You need to return true from this method in order to have an 'unlocked' account. Same thing with the method that pertains to 'expired', etc. In this case true means allow it, false means reject it.

Get authenticated user

I'm implementing a project using Spring security oauth2, everything works perfectly, now I want to start digging deeper beyond the basics. I want to check if the user making the request is the actual user owner of the resource, the end result would be for example:
/private/users/{uuid}/clients returns all clients for the specified user.
So my controller now looks like this:
#RestController
public class HomeController {
#Autowired
private UserService userService;
#GetMapping(value = "/")
public String index() {
return "Hello world";
}
#GetMapping(value = "/private")
public String privateTest(Principal principal) {
User user = userService.get(principal.getName());
return user.getUuid();
}
}
EDIT: The full security code (working) for a better explanation.
ResourceServerConfig
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable().and()
.authorizeRequests()
.antMatchers("/","/home","/register","/login").permitAll()
.antMatchers("/private/**").authenticated();
}
}
CustomUserDetails with getters and setters off course
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private String uuid;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.uuid = user.getUuid();
this.authorities = translate(user.getRoles());
}
}
AuthorizationServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT","ROLE_TRUSTED_CLIENT").scopes("read","write","trust")
.resourceIds("oauth2-resource").accessTokenValiditySeconds(5000).secret("secret");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
}
Main
#SpringBootApplication
public class DummyOauthApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(DummyOauthApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
if (repository.count() == 0) {
service.save(new User("user", "password", UUID.randomUUID().toString(), Arrays.asList(new Role("USER"), new Role("ACTUATOR"))));
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return username -> new CustomUserDetails(repository.findByUsername(username));
}
}
So, using the way I've implemented. I can get the actual user but it implies a database query every time an endpoint is called. Getting the user and match with the user uuid.
I want to find another way that I can get the user and then compare if the uuid = user.getUuid()
Thanks in advance.
After some time and a lot of mistakes, I've managed to find a solution that I leave here. The CustomUserDetails can be seen in the question and from there you can easily get the uuid and match with the requested one.
public static CustomUserDetails getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
return (CustomUserDetails) authentication.getPrincipal();
}
}
throw new IllegalStateException("User not found!");
}
EDIT: if you want to return the user you do something like this
public class CustomUserDetails implements UserDetails {
private Collection<? extends GrantedAuthority> authorities;
private String password;
private String username;
private User user;
public CustomUserDetails(User user) {
this.username = user.getUsername();
this.password = user.getPassword();
this.user = user;
this.authorities = translate(user.getRoles());
}
}
And then in a Utils or something,
public static User getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
if (authentication.getPrincipal() instanceof CustomUserDetails) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return userDetails.getUser();
}
}
throw new IllegalStateException("User not found!");
}
Thanks for all the effort.
Perhaps you could implement a custom AuthenticationProvider and store user details as Principal
Spring Security Authentication Provider

Spring security userdetails: org.springframework.security.authentication.DisabledException

i try to perform a login process with spring-boot, oauth2 and spring security. I implemented a custom userdetails service.
Here the code:
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserService userService;
#Autowired
public CustomUserDetailsService(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
return new UserRepositoryUserDetails(user);
}
private final static class UserRepositoryUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
private UserRepositoryUserDetails(User user) {
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles();
}
// another methods
#Override
public boolean isEnabled() { return super.isEnabled(); }
}
}
The user entity:
#Entity
#Table
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "id", columnDefinition = "VARCHAR(50)")
private String userUUId;
// another parametes
#Column(nullable = false, columnDefinition = "TINYINT DEFAULT false")
#Type(type = "org.hibernate.type.NumericBooleanType")
private boolean enabled;
public User() {
}
public User(User user) {
super();
this.userUUId = user.getUserUUId();
this.roles = user.getRoles();
this.name = user.getName();
this.email = user.getEmail();
this.enabled = isEnabled();
this.password = user.getPassword();
}
// ...
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
The security configuration:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And a part of an authorizationserver configuration:
#Configuration
#EnableAuthorizationServer
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean(name = "tokenStore")
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService);
}
Here the error log:
type=AUTHENTICATION_FAILURE, data={type=org.springframework.security.authentication.DisabledException, message=User is disabled}]
[2016-08-25 09:23:17.774] boot - 21158 INFO [http-nio-8443-exec-1] --- TokenEndpoint: Handling error: InvalidGrantException, User is disabled
[2016-08-25 09:23:17.832] boot - 21158 DEBUG [http-nio-8443-exec-1] --- OrderedRequestContextFilter: Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#6ea0e0af
[2016-08-25 09:23:17.837] boot - 21158 ERROR [http-nio-8443-exec-4] --- EndpointsAuthentification: org.springframework.web.client.HttpClientErrorException: 400 Bad Request
[2016-08-25 09:23:17.839] boot - 21158 DEBUG [http-nio-8443-exec-4] --- OrderedRequestContextFilter: Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#4afe7f7
[2016-08-25 09:23:17.840] boot - 21158 ERROR [http-nio-8443-exec-4] --- [dispatcherServlet]: Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException
at com.x.server.controller.LoginController.login(LoginController.java:76)
But i am sure, the user account is enabled. A call of user.isEnabled return true, but the framework cannot detect it.
Any ideas?
Cheers
Probably the enabled field in database is null or false
The org.springframework.security.authentication.DisabledException is thrown when the isEnabled() method of the UserDetails returns false.
From your implementation, User user = userService.findByUsername(username); in the CustomUserDetailsService is fetching from database a user whose enabled property is false.
Find a way to change it to true.
in your UserDetailsImpl class, isEnabled must be return true;
#Override
public boolean isEnabled() {
return true;
}

Why can't I save my #Entity User with it's UserRepository in Spring Boot

Problem
Invoking repository.save(user); where repository is an instance of UserRepository and user.toString is User[id=0, userName='asd', password='asd'], my controller gives the following error:
nested exception is java.lang.IllegalArgumentException: Invoked method public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object) is no accessor method!] with root cause
java.lang.IllegalArgumentException: Invoked method public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object) is no accessor method!
Details
I know repository.save(new User("user1", "pass1")); works because I tested my repository using:
public CommandLineRunner demo(UserRepository repository) {
return (args) -> {
// save a couple of users
repository.save(new User("user1", "pass1"));
repository.save(new User("user2", "pass2"));
}
}
Here's the full Request mapping of the page:
RequestMapping(value = "/register", method = RequestMethod.GET)
public String showRegistrationForm(WebRequest request, Model model) {
User user = new User();
model.addAttribute("user", user);
return "registrations/register";
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUserAccount(WebRequest request,
#ModelAttribute("user") #Valid User user,
BindingResult result,
UserRepository repository) {
if (!result.hasErrors()) {
System.out.println(user);
repository.save(user);
}
return "redirect:/";
}
And here's the UserRepository:
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByUserName(String UserName);
}
And here's the User #Entity:
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String userName;
private String password;
protected User() {}
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
// standard getters and setters
#Override
public String toString() {
return String.format(
"User[id=%d, userName='%s', password='%s']",
id, userName, password);
}
}
It seems the UserRepository repository in code of Controller should not be used as parameter:
...
public String registerUserAccount(WebRequest request,
#ModelAttribute("user") #Valid User user,
BindingResult result,
UserRepository repository)
...
Can you just use #Autowired it like:
#Autowired
private UserRepository repository;
And then try again?!
Hope this help.

I have an error in testing my simple spring mvc service

I create a new service with spring boot and spring mvc .
UserEntity.class:
#Entity
#Table(name = "users")
public class UserEntity {
private long id;
private String username;
private String password;
private boolean active;
private boolean login;
public UserEntity(UserDto dto) {
this.id = dto.getId();
this.username = dto.getUsername();
this.password = dto.getPassword();
this.active = dto.isActive();
}
// getters&setters...
}
UserDto.class:
public class UserDto {
private long id;
private String username;
private String password;
private boolean active;
public UserDto(long id, String username, String password, boolean active) {
this.id = id;
this.username = username;
this.password = password;
this.active = active;
}
// getters&setters...
}
UserRepository:
#Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
}
UserServiceImpl.class: (and UserService Interface)
#Service
#Transactional
public class UserServiceImpl implements UserService {
private final UserRepository repo;
#Autowired
public UserServiceImpl(UserRepository repo) {
this.repo = repo;
}
#Override
public boolean saveUser(UserDto dto) {
UserEntity user = new UserEntity(dto);
repo.save(user);
return true;
}
}
UserController.class:
#RestController
public class UserController {
private final UserService service;
#Autowired
public UserController(UserService service) {
this.service = service;
}
#RequestMapping(value = "/users", method = RequestMethod.POST)
public void createUser(#RequestBody UserDto userDto) {
service.saveUser(userDto);
}
}
Application.class:
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
my Spring Boot project starts correctly. But when I test my service with IntelliJ Test Restful Web Service Tool I encounter an error:
Response:
{"timestamp":1464066878392,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/users"}
What is the problem?
My suggestion would be to remove the constructors from the UserController and UserServiceImpl classes, there's no need for them. Then, assign the #Autowired annotation to the declarations instead. Also, I don't think you need to make them final.
UserServiceImpl.class:
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository repo;
#Override
public boolean saveUser(UserDto dto) {
UserEntity user = new UserEntity(dto);
repo.save(user);
return true;
}
}
UserController.class:
#RestController
public class UserController {
#Autowired
private UserService service;
public UserController(UserService service) {
this.service = service;
}
#RequestMapping(value = "/users", method = RequestMethod.POST)
public void createUser(#RequestBody UserDto userDto) {
service.saveUser(userDto);
}
}

Categories