I want to validate the password field length before it's hashed.
Model class:
#Entity
#Table(name = "users")
public class UserInfo {
/* other code */
#NotBlank(message = "Password is required")
#Size(min = 6, message = "Password should have min 6 characters")
private String password;
/* other code */
}
Controller file simply calls the service method.
Service class:
#Component
public class UserInfoServiceImpl implements UserInfoService {
#Autowired
private UserInfoRepository userInfoRepository;
public UserInfo register(UserRegisterRequest request) {
UserInfo user = new UserInfo();
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone());
// Password hashing
user.setPassword(new BCryptPasswordEncoder().encode(request.getPassword()));
user.setIsActive(0);
user.setStatus(1);
return userInfoRepository.save(user);
}
}
I suppose the password is validated after it's hashed in this line:
user.setPassword(new BCryptPasswordEncoder().encode(request.getPassword()));
How can I validate this password before hashing and saving?
Any help would be appreciated. Thanks in advance.
You can validate the input of any Spring bean. In order to to this, you use a combination of the #Validated and #Valid annotations, like this:
#Component
#Validated
public class UserInfoServiceImpl implements UserInfoService {
#Autowired
private UserInfoRepository userInfoRepository;
public UserInfo register(#Valid UserRegisterRequest request) {
UserInfo user = new UserInfo();
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone());
// Password hashing
user.setPassword(new BCryptPasswordEncoder().encode(request.getPassword()));
user.setIsActive(0);
user.setStatus(1);
return userInfoRepository.save(user);
}
}
If you want a better control, you can validate programmatically:
#Component
public class UserInfoServiceImpl implements UserInfoService {
#Autowired
private UserInfoRepository userInfoRepository;
public UserInfo register(UserRegisterRequest request) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<UserRegisterRequest> violations = validator.validate(input);
if (!violations.isEmpty()) {
// Do something on invalid input;
}
UserInfo user = new UserInfo();
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setEmail(request.getEmail());
user.setPhone(request.getPhone());
// Password hashing
user.setPassword(new BCryptPasswordEncoder().encode(request.getPassword()));
user.setIsActive(0);
user.setStatus(1);
return userInfoRepository.save(user);
}
}
Alternatively, a pre-configured validator instance can be injected like this:
#Autowired
Validator validator;
For these to work, you need spring-boot-starter-validation in your Maven/Gradle config file.
You can use #Valid.
#RestController
public class UserController {
#PostMapping("/users")
ResponseEntity<String> register(#Valid #RequestBody User user) {
userService.register(user);
}
}
When Spring Boot finds an argument annotated with #Valid, it automatically bootstraps the default JSR 380 implementation — Hibernate Validator — and validates the argument.
When the target argument fails to pass the validation, Spring Boot throws a MethodArgumentNotValidException exception.
You can handle and customize message using #ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
Reference
Related
#RestController()
#RequestMapping(path = "/users")
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam Integer pageSize, UserRequest userRequest) {
//TODO: some implementation
}}
public class UserRequest{
public String name;
public String age;
}
send the request with invalid parameter, like localhost:8800/users?name1=1234, I want to return error. but in fact it ignore the invalid parameter name1.
I tried to add the user defined annotation on the method parameter and on the class , codes like below
#RestController()
#RequestMapping(path = "/users")
#Validated
public class UserController {
#GetMapping()
public #ResponseBody Page<User> getAllUsers(#RequestParam #Validated Integer pageSize, #Validated UserRequest userRequest} {
//TODO: some implementation
}
}
But it does not working.
I think it is happened because framework has ignore the invalid parameter before the method was called.
where did framework handle the url and how can I do to make it return error instead of ignore?
You can reject parameters that are not valid. You can do so in a HandlerInterceptor class.
Reference: Rejecting GET requests with additional query params
In addition to what is done in the above reference, in your addInterceptors, you can specify the path that is intercepted.
Like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
private String USERS_PATH = "/users";
// If you want to cover all endpoints in your controller
// private String USERS_PATH = List.of("/users", "/users/**");
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FooHandlerInterceptor()).addPathPatterns(USERS_PATH);
}
}
I learning Spring Boot web application with .jsp, and I'm struggling a lot with the testing concepts. From the SO and YT guides I implemented the Mockito thing, but honestly I do not clearly undesrtand how does it work.
I have a Registration form with 4 fields for name, lastname, email and password. This POST request is handled by the registerAction method in RegisterController. In this method I have two self-written validators for email and password. The tests should handle the cases when User data are given properly and if the errors are sent when inputs are not correct.
I tried to write tests for the controller but I'm constantly getting an exception NullPointerExpection. Looking into the debugger, the User object sent from the testing class has null attributes, which probably is the reason the exceptions.
Testing class:
#SpringBootTest
#AutoConfigureMockMvc
class RegisterControllerTest {
#Autowired
private WebApplicationContext wac;
#MockBean
private UserService userService;
#Autowired
private Gson gson;
#Autowired
private MockMvc mockMvc;
#BeforeEach
void setUp() {
initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.apply(springSecurity()).build();
}
#Test
void show_register_action() throws Exception {
User user = prepareUserEmpty();
this.mockMvc.perform(post("/adduser")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(user)))
.andDo(print())
.andExpect(status().isOk());
}
private User prepareUserEmpty(){
User user = new User();
user.setEmail("");
user.setPassword("");
user.setName("");
user.setLastName("");
return user;
}
}
RegisterController:
#Controller public class RegisterController {
#Autowired
private UserService userService;
#Autowired
private EmailSender emailSender;
#Autowired
MessageSource messageSource; // Allows to obtain messages from message.properties to Java code
#POST
#RequestMapping(value = "/adduser")
public String registerAction(User user, BindingResult result, Model model, Locale locale){ // BindingResult for validation, Locale for messageSource
String returnPage = "register";
User userExist = userService.findUserByEmail(user.getEmail());
new UserRegisterValidator().validate(user, result);
new UserRegisterValidator().validateEmailExist(userExist, result);
if (!(result.hasErrors())){
userService.saveUser(user);
model.addAttribute("message", messageSource.getMessage("user.register.success.email", null, locale));
returnPage = "index";
}
return returnPage;
} }
Validators:
public class UserRegisterValidator implements Validator {
#Override
public boolean supports(Class <?> cls){
return User.class.equals(cls);
}
#Override
public void validate(Object obj, Errors errors){
User u = (User) obj;
ValidationUtils.rejectIfEmpty(errors, "name", "error.userName.empty");
ValidationUtils.rejectIfEmpty(errors, "lastName", "error.userLastName.empty");
ValidationUtils.rejectIfEmpty(errors, "email", "error.userEmail.empty");
ValidationUtils.rejectIfEmpty(errors, "password", "error.userPassword.empty");
if (!u.getEmail().equals(null)){
boolean isMatch = AppdemoUtils.checkEmailOrPassword(AppdemoConstants.EMAIL_PATTERN, u.getEmail());
if (!isMatch)
errors.rejectValue("email", "error.userEmailIsNotMatch");
}
if (!u.getPassword().equals(null)){
boolean isMatch = AppdemoUtils.checkEmailOrPassword(AppdemoConstants.PASSWORD_PATTERN, u.getPassword());
if (!isMatch)
errors.rejectValue("password", "error.userPasswordIsNotMatch");
}
}
public void validateEmailExist(User user, Errors errors){
if (user != null)
errors.rejectValue("email", "error.userEmailExist");
}
}
My controller class is a follows:
#PostMapping(path="/users/{id}")
#PreAuthorize("hasAnyAuthority('CAN_READ')")
public #ResponseBody ResponseEntity<User> getUser(#PathVariable int id) {
...
}
I have the following Resource Server config
#Configuration
public class ResourceServerCofig implements ResourceServerConfigurer {
private static final String RESOURCE_ID = "test";
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
}
Finally my test looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientController.class)
public class ClientControllerTest {
#Autowired
private MockMvc mockMvc;
#WithMockUser(authorities={"CAN_READ"})
#Test
public void should_get_user_by_id() throws Exception {
...
mockMvc.perform(MockMvcRequestBuilders.get("/user/1")).
andExpect(MockMvcResultMatchers.status().isOk()).
andExpect(MockMvcResultMatchers.header().string(HttpHeaders.CONTENT_TYPE, "application/json")).
andExpect(MockMvcResultMatchers.jsonPath("$.name").value("johnson"));
}
}
Issue is I always get a 401 HTTP status with message unauthorized","error_description":"Full authentication is required to access this resource.
How can should I write tests for #PreAuthorized annotated controller methods methods?
I've spent part of the day figuring out how to solve this. I ended up with a solution that I think is not so bad, and could help many.
Based on what you tried to do in your test, you can do a mockMvc to test your controller. Notice that the AuthorizationServer is not called. You stay only in your Resource server for the tests.
Create a bean InMemoryTokenStore that will be used in the OAuth2AuthenticationProcessingFilter to authenticate your user. What is great is that you can add tokens to your InMemoryTokenStore before executing your tests. The OAuth2AuthenticationProcessingFilter will authenticate the user based on the token he is using and will not call any remote server.
#Configuration
public class AuthenticationManagerProvider {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
The annotation #WithMockUser does not work with OAuth2. Indeed, the OAuth2AuthenticationProcessingFilter always checks your token, wi no regard to the SecurityContext. I suggest to use the same approach as #WithMockUser, but using an annotation you create in your code base. (To have some easy to maintain and clean tests):
#WithMockOAuth2Scope contains almost all the parameters you need to customize your authentication. You can delete those you will never use, but I put a lot to make sure you see the possibilities. (Put those 2 classes in your test folder)
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public #interface WithMockOAuth2Scope {
String token() default "";
String clientId() default "client-id";
boolean approved() default true;
String redirectUrl() default "";
String[] responseTypes() default {};
String[] scopes() default {};
String[] resourceIds() default {};
String[] authorities() default {};
String username() default "username";
String password() default "";
String email() default "";
}
Then, we need a class to interpret this annotation and fill our `InMemoryTokenStore with the data you need for your test.
#Component
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {
#Autowired
private TokenStore tokenStore;
#Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2AccessToken oAuth2AccessToken = createAccessToken(mockOAuth2Scope.token());
OAuth2Authentication oAuth2Authentication = createAuthentication(mockOAuth2Scope);
tokenStore.storeAccessToken(oAuth2AccessToken, oAuth2Authentication);
return SecurityContextHolder.createEmptyContext();
}
private OAuth2AccessToken createAccessToken(String token) {
return new DefaultOAuth2AccessToken(token);
}
private OAuth2Authentication createAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
OAuth2Request oauth2Request = getOauth2Request(mockOAuth2Scope);
return new OAuth2Authentication(oauth2Request,
getAuthentication(mockOAuth2Scope));
}
private OAuth2Request getOauth2Request(WithMockOAuth2Scope mockOAuth2Scope) {
String clientId = mockOAuth2Scope.clientId();
boolean approved = mockOAuth2Scope.approved();
String redirectUrl = mockOAuth2Scope.redirectUrl();
Set<String> responseTypes = new HashSet<>(asList(mockOAuth2Scope.responseTypes()));
Set<String> scopes = new HashSet<>(asList(mockOAuth2Scope.scopes()));
Set<String> resourceIds = new HashSet<>(asList(mockOAuth2Scope.resourceIds()));
Map<String, String> requestParameters = Collections.emptyMap();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
return new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
}
private Authentication getAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());
String username = mockOAuth2Scope.username();
User userPrincipal = new User(username,
mockOAuth2Scope.password(),
true, true, true, true, grantedAuthorities);
HashMap<String, String> details = new HashMap<>();
details.put("user_name", username);
details.put("email", mockOAuth2Scope.email());
TestingAuthenticationToken token = new TestingAuthenticationToken(userPrincipal, null, grantedAuthorities);
token.setAuthenticated(true);
token.setDetails(details);
return token;
}
}
Once everything is setup, create a simple Test class under src/test/java/your/package/. This class will do the mockMvc operations, and use the # WithMockOAuth2Scope to create the token you need for your test.
#WebMvcTest(SimpleController.class)
#Import(AuthenticationManagerProvider.class)
class SimpleControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithMockOAuth2Scope(token = "123456789",
authorities = "CAN_READ")
public void test() throws Exception {
mockMvc.perform(get("/whoami")
.header("Authorization", "Bearer 123456789"))
.andExpect(status().isOk())
.andExpect(content().string("username"));
}
}
I came up with this solutions thanks to:
How to test spring-security-oauth2 resource server security?
Faking OAuth2 Single Sign-on in Spring, Two Ways
Annotation solution
Fluent API solution
A lot of debug, where I lost myself inside Spring, but I finally found what I was looking for.
For the curious:
When testing, Spring loads a InMemoryTokenStore, and give you the possibility to take one you provide (as a #Bean). When running in production, for my case, Spring uses a RemoteTokenStore, which calls the remote Authorization server to check the token (http://authorization_server/oauth/check_token).
When you decide to use OAuth2, Spring fires the OAuth2AuthenticationProcessingFilter. This was my entry point during all me debugging sessions.
I've learned a lot, thank you for this.
You can find the source code here.
Hope it will help !
I have a Spring Boot app that contains an User class - all fields have standard JSR-303 annotations (#NotNull, #Size, etc.) and validation works fine.
However when I add a custom validation to User, I can't get a dependency injected into a custom validator:
#Component
public class UniqueUsernameValidator implements
ConstraintValidator<UniqueUsername, String> {
#Autowired
private UserRepository userRepository;
#Override
public boolean isValid(String username, ConstraintValidatorContext context) {
// implements logic
}
#UniqueUsername annotation is declared as:
#Documented
#Retention(RUNTIME)
#Target({FIELD, ANNOTATION_TYPE, PARAMETER})
#Constraint(validatedBy = UniqueUsernameValidator.class)
#interface UniqueUsername {
String message() default "{com.domain.user.nonUniqueUsername}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The annotated field:
#NotBlank
#Size(min = 2, max = 30)
#UniqueUsername
private String username;
And the validator usage:
#Service
public final class UserService {
private final UserRepository userRepository;
private final Validator validator;
public UserService(UserRepository userRepository, Validator validator)
{
this.userRepository = userRepository;
this.validator = validator;
}
public void createUser(User user) {
Set<ConstraintViolation<User>> validate = validator.validate(user);
// logic...
}
}
The problem is that UserRepository is not being autowired in UniqueUsernameValidator. Field is always null.
I am using a LocalValidatorFactoryBean.
Does anyone have any idea why autowiring's not working?
#Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping("/user/new")
public String createUser(#ModelAttribute("newUser") User newUser, BindingResult bindingResult,
Model model) {
userService.createUser(newUser);
// omitted
}
You need to add #Valid annotation in front of entity class in the public String createUser(#ModelAttribute("newUser") User newUser) in front of User.
#RequestBody #Valid User user
The UserRepository implementation needs an Annotation like "#Repository" or "#Component" or "#Service". Your UserService gets the repository instance via constructor. Maybe there was a "new UserRepositoryDao()" call used. And in your validator you are trying to autowire. I guess it's either not annotated as service OR not loaded in the spring context path or as a spring bean in your spring.xml
I have dealt with the same problem a few months ago. Instead of autowiring repository, pass the service which already uses the very same repository through the annotation.
Declare the annotation to accept the field required to be unique and a service performing the validation.
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueUsernameValidator.class)
#Documented
public #interface UniqueUsername {
String message() default "{com.domain.user.nonUniqueUsername}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UniqueUsernameValidation> service(); // Validating service
String fieldName(); // Unique field
}
Use it on the POJO like:
#NotBlank
#Size(min = 2, max = 30)
#UniqueUsername(service = UserService.class, fieldName = "username")
private String username;
Notice the service passed into annotation (Class<? extends UniqueUsernameValidation> service()) must implement UniqueUsernameValidation interface.
public interface UniqueUsernameValidation {
public boolean isUnique(Object value, String fieldName) throws Exception;
}
Now make the passed UserService implement the interface above and override it's only method:
#Override
public boolean isUnique(Object value, String fieldName) throws Exception {
if (!fieldName.equals("username")) {
throw new Exception("Field name not supported");
}
String username = value.toString();
// Here is the logic: Use 'username' to find another username in Repository
}
Don't forget to UniqueUsernameValidator which processes the annotation:
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, Object>
{
#Autowired
private ApplicationContext applicationContext;
private UniqueUsernameValidation service;
private String fieldName;
#Override
public void initialize(UniqueUsername unique) {
Class<? extends UniqueUsernameValidation> clazz = unique.service();
this.fieldName = unique.fieldName();
try {
this.service = this.applicationContext.getBean(clazz);
} catch(Exception ex) {
// Cant't initialize service which extends UniqueUsernameValidator
}
}
#Override
public boolean isValid(Object o, ConstraintValidatorContext context) {
if (this.service !=null) {
// here you check whether the username is unique using UserRepository
return this.service.isUnique(o, this.fieldName))
}
return false;
}
}
I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.
My User class:
public class User
{
#NotEmpty
private String name;
#NotEmpty
private String login; // should be unique
}
My validator:
#Component
public class UserValidator implements Validator
{
#Autowired
private UserDAO userDAO;
#Override
public boolean supports(Class<?> clazz)
{
return User.class.equals(clazz) || UsersForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
/*
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
*/
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
My controller:
#Controller
public class UserController
{
#Autowired
private UserValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
#RequestMapping(value = "/save")
public ModelAndView save(#Valid #ModelAttribute("user") final User user,
BindingResult result) throws Exception
{
if (result.hasErrors())
{
// handle error
} else
{
//save user
}
}
}
So, Is it possible to use custom validator and annotation together? And if yes how?
I know this is a kind of old question but, for googlers...
you should use addValidators instead of setValidator. Like following:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}
PS: addValidators accepts multiple parameters (ellipsis)
if you checkout the source of org.springframework.validation.DataBinder you will see:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
....
public void setValidator(Validator validator) {
assertValidators(validator);
this.validators.clear();
this.validators.add(validator);
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
....
}
as you see setValidator clears existing (default) validator so #Valid annotation won't work as expected.
If I correctly understand your problem, as soon as you use you custom validator, default validation for #NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.
You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.
Using java configuration it could look like :
#Configuration
public class ValidatorConfig {
#Autowired
private MessageSource messageSource;
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Then you modify UserValidator to use it :
#Component
public class UserValidator implements Validator
{
#Autowired
#Qualifier("basicValidator")
private Validator basicValidator;
#Autowired
private UserDAO userDAO;
// ...
#Override
public void validate(Object target, Errors errors)
{
basicValidator.validate(target, errors);
// eventually stop if any errors
// if (errors.hasErrors()) { return; }
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
Well for me you have to delete the
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
Leave the
#Valid #ModelAttribute("user") final User user,
BindingResult result
And after in the function make
validator.validate(user,result)
This way you will use the validation basic with the #Valid and after you will put make the more complex validation.
Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.
Maybe is wrong, i use always the #Valid without any validator.