Using Spring MVC Validator in controller using multiple domain objects - java

I have a Spring MVC controller that handles requests to do with user management including a method to save user details and a method to allow a user to reset their password. I want to use a validator to ensure that the user typed the same password twice.
My controller
#Controller
public class UserDetails {
...
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new PasswordValidator());
}
...
#RequestMapping(value="/saveUserDetails", method=RequestMethod.POST)
public String saveUserDetails(
#ModelAttribute User user) {
...
}
...
#RequestMapping(value="/resetPassword", method=RequestMethod.POST)
public String resetPassword(
#Validated PasswordPair password, BindingResult result) {
...
}
And the Validator
private final static class PasswordValidator implements Validator {
private final static int MIN_LEN=5;
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
PasswordPair pair = (PasswordPair)target;
//1 impose password rules
if (pair.getPassword().length()<MIN_LEN) {
errors.rejectValue("password", "Too short", "Password must bne at least "+MIN_LEN+" chars");
}
if (!pair.getPassword().equals(pair.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "mustMatch", "passwords must match");
}
}
}
The problem is that when saveUserDetails is called spring is trying to validate the user with the password validator.
I have tried changing #InitBinder to #InitBinder("password") or #InitBinder("PasswordPair") but in that case nothing is validated at all.
How can I make it validate only the correct parameters?

Your approach of using #InitBinder(withname) should work , my guess is that you have just provided the wrong name.
Can you try this name:
#InitBinder("passwordPair")
Another option will to explicitly give your ModelAttribute a name, this way and use that name in the InitBinder:
public String resetPassword(
#ModelAttribute("password") #Valid PasswordPair password, BindingResult result) {
...
}
#InitBinder("password")
On a related note, I have a similar issue recorded with Spring Jira and have a pull request to change this behavior in place. Please vote it up if possible - https://jira.springsource.org/browse/SPR-11429

Add password and confirmPassword field to the User object instead of having PasswordPair object. Change resetPassword method to have user as inparameter.
And change the validator to validate the user. Here i suppose that you would have password validation even when create a new user?
Ex:
#Controller
public class UserDetails {
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new PasswordValidator());
}
#RequestMapping(value="/saveUserDetails", method=RequestMethod.POST)
public String saveUserDetails(#ModelAttribute User user) {
...
}
#RequestMapping(value="/resetPassword", method=RequestMethod.POST)
public String resetPassword(#Valid #ModelAttribute User user, BindingResult result) {
...
}
And the Validator
private final static class UserValidator implements Validator {
private final static int MIN_LEN=5;
#Override
public boolean supports(Class<?> clazz) {
return PasswordPair.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User)target;
//1 impose password rules
if (user.getPassword().length()<MIN_LEN) {
errors.rejectValue("password", "Too short", "Password must bne at least "+MIN_LEN+" chars");
}
if (!user.getPassword().equals(user.getConfirmPassword())) {
errors.rejectValue("confirmPassword", "mustMatch", "passwords must match");
}
}
}
or you could look at this post, maybe it'll help you:
Handling password confirmations on spring-mvc

Related

Spring invalid target for Validator

I am trying to validate the string email to check if it already appears within my MYSQL database, when I execute with an email thats already used I get the following error:
java.lang.IllegalStateException: Invalid target for Validator [co2103.hw2.controller.TestResultsValidator#62b41c6]: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'testResults' on field 'email': rejected value [abc#le.ac.uk]; codes [email.testResults.email,email.email,email.java.lang.String,email]; arguments []; default message [is already provided by a different user! Please user another one!]
Here is the validator code
public class TestResultsValidator implements Validator{
private TestResultsRepository TrRepo;
private HomeTestRepository HTRepo;
public TestResultsValidator (TestResultsRepository TrRepo, HomeTestRepository HTRepo) {
this.TrRepo = TrRepo;
this.HTRepo = HTRepo;
}
#Override
public boolean supports(Class<?> clazz) {
return TestResults.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
TestResults tr = (TestResults) target;
for(TestResults t : TrRepo.findAll()) {
//SAME EMAIL
if (tr.getEmail().equals(t.getEmail())) {
errors.rejectValue("email", "email", "is already provided by a different user! Please user another one!");
System.out.println("Email is already taken by a different user, please try another username");
break;
}
The controller code
//Add new results
#RequestMapping(value = "/addResults",method = {RequestMethod.POST , RequestMethod.GET})
public String newHotel(#Valid #ModelAttribute TestResults results, BindingResult result, Model model) {
if (result.hasErrors()) {
model.addAttribute("errors", result);
return "start";
}
else {
trRepo.save(results);
return "Submitted";
}}
You need to register you validator to spring.
First ad Component Annotation to your validtor.
#Component
public class TestResultsValidator implements Validator{
.....
}
Register it in the controller.
#Controller
class TestResultController {
#Autowired
TestResultsValidator testResultsValidator ;
#InitBinder("testResultsValidator")
protected void initMessageBinder(WebDataBinder binder) {
binder.addValidators(testResultsValidator );
}
}

Jax RS Authorization

I have an existing code at a class which is extended from javax.ws.rs.core.Application
...
Context childContext = component.getContext().createChildContext();
JaxRsApplication application = new JaxRsApplication(childContext);
application.add(this);
application.setStatusService(new ErrorStatusService());
childContext.getAttributes().put("My Server", this);
...
ChallengeAuthenticator challengeGuard = new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "REST API Realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(null, "user"));
User owner = new User("admin", "admin");
realm.getUsers().add(owner);
realm.map(owner, Role.get(null, "admin"));
//Attach verifier to check authentication and enroler to determine roles
challengeGuard.setVerifier(realm.getVerifier());
challengeGuard.setEnroler(realm.getEnroler());
challengeGuard.setNext(application);
// Attach the application with HTTP basic authentication security
component.getDefaultHost().attach(challengeGuard);
I don't have a web.xml at my code. I would like to add authorization to my code. This: https://restlet.com/technical-resources/restlet-framework/guide/2.3/core/security/authorization does not apply to me since I don't have restlet resources.
How can I implement jax rs authorization into my code?
EDIT 1: Existing code uses restlet JAX-RS extension: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs
I've tried that at my jax-rs resource class:
#GET
#Path("/")
public String getStatus() {
if (!securityContext.isUserInRole("admin")) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
...
}
However, it throws 403 even I log in with admin user.
EDIT 2:
When I check here: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs There is a piece of code:
this.setRoleChecker(...); // if needed
This may solve my issue but I don't know how to set a role checker.
PS: I use jersey 1.9 and restlet 2.2.3.
It's not really clear (at least to me :-) ) what you are trying to achieve.
If you have a class which is a subclass of javax.ws.rs.core.Application, you should be able to simply add #RolesAllowed("user") as an annotation to your resource classes, as shown in https://jersey.java.net/documentation/latest/security.html
#Path("/")
#PermitAll
public class Resource {
#RolesAllowed("user")
#GET
public String get() { return "GET"; }
#RolesAllowed("admin")
#POST
public String post(String content) { return content; }
#Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}
Accessing that resource should prompt you for your credentials. If that doesn't work, then you need to provide a small code sample, which compiles and doesn't do what you want it to do. Then it's easier to see where the problem is and what needs to be done to make it work
I could make it work like that:
Application class:
...
application.setRoles(getRoles(application));
...
public static List<Role> getRoles(JaxRsApplication application) {
List<Role> roles = new ArrayList<>();
for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
roles.add(new Role(application, authorizationRole.toString()));
}
return roles;
}
...
Authorization enum:
public enum AuthorizationRoleEnum {
USER("user"),
ADMIN("admin");
private final String value;
AuthorizationRoleEnum(String value) {
this.value = value;
}
#Override
public String toString() {
return value;
}
}
At my resource classes:
...
#Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
if (securityContext.getAuthenticationScheme() != null
&& !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
}
}
...
You need to implement your RoleChecker using this interface.
As the doc says:
Because the Restlet API does not support its own mechanism for role checks (as e.g. the Servlet API), you must use this inteface if you need role checks in a JAX-RS application.
This interface is used to check, if a user is in a role. Implementations must be thread save.
so as an example of implementation you can do smth like this:
public class MyRoleChecker implements RoleChecker {
public boolean isInRole(Principal principal, String role) {
return principal.getRole().equals(role);
}
}
Edited:
On the other hand as you use the new API, you need to implement SecurityContext and inject it using #Context in your resource methods.
Then you fetch roles list from the storage by username. The storage implementation is up to you. Please refer to this example
#Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Auth-Token");
String subject = validateToken(authHeaderVal); //execute custom authentication
if (subject!=null) {
final SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return subject;
}
};
}
#Override
public boolean isUserInRole(String role) {
List<Role> roles = findUserRoles(subject);
return roles.contains(role);
}
#Override
public boolean isSecure() {
return uriInfo.getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
return "Token-Based-Auth-Scheme";
}
});
}
}
}

Removing duplication from Spring controllers

I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.

"Chaining" two controllers?

A REST-API I am developing allows access to various kinds of user data.
Users can be identified via their Id, their email or their username. A user then has a couple of other data such as orders etc.
Now I am planning to expose the following endpoints:
/rest/user/byemail/test#example.org/profile
/rest/user/byemail/test#example.org/orders
/rest/user/byemail/test#example.org/address
/rest/user/byid/123456/profile
/rest/user/byid/123456/orders
/rest/user/byid/123456/address
/rest/user/byusername/test/profile
/rest/user/byusername/test/orders
/rest/user/byusername/test/address
As you can see, the URL always consists of two "parts": One for identifying the user and the other one for identifying the resource.
It would be great if I could now avoid writing 9 different methods in my controller (as there might be other types of information in the future).
Is it somehow possible to write one UserController which is then returned and parametrized by the MainController?
public class UserController {
#RequestMapping("/profile")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/orders")
public ModelAndView profile(User user) {
//...
}
#RequestMapping("/address")
public ModelAndView profile(User user) {
//...
}
}
#RequestMapping("/rest/user")
public class MainController {
#RequestMapping("byemail/{email}")
public ??? byEmail(#PathVariable String email) {
User user = //Find user by email
//???
}
#RequestMapping("byusername/{username}")
public ??? byUserName(#PathVariable String username) {
User user = //Find user by username
//???
}
#RequestMapping("byid/{id}")
public ??? byId(#PathVariable String id) {
User user = //Find user by id
//???
}
}
Or is it maybe possible to solve this via some kind of routing?
However, it would be important to "split" the URL and use one half to find the correct user which will then be available as a parameter when parsing the second half.
Why not just one controller with these request mappings?
#RequestMapping("/rest/user")
public class UserController {
#RequestMapping("{keyType}/{key}/orders")
public ModelAndView orders(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/profile")
public ModelAndView profile(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
#RequestMapping("{keyType}/{key}/address")
public ModelAndView address(#PathVariable String keyType, #PathVariable String key) {
User u = findUser(keyType, key);
// ...
}
private User findUser(String keyType, String key) {
// ...
}
}
Take a look at Spring Data REST.
This API exposes a SimpleJpaRespository to the web, accessible via RESTful paths.

Spring - disable bind exceptions (for a particular property)

In a web application I'm working on using Spring 2.5.6.SEC01, I essentially have an Integer field that takes a number to determine which page to scroll to. The requirements changed, and we no longer want to display an error message, but simply ignore the user's input if they enter an invalid number, say "adfadf".
I was reading that you can do that via:
TypeMismatch.property=Some New Error Message
However, after having tried that, we are still getting the original error message:
java.lang.Integer.TypeMismatch=...
I only want to disable this message for that given property. How can I do that? I still want binding to occur automatically, I just don't want to hear about it now.
Walter
According to DefaultMessageCodesResolver
In case of code "typeMismatch", object name "user", field "age"
typeMismatch.user.age
typeMismatch.age
typeMismatch.int
typeMismatch
So you should get (I suppose your commandName is called command and your property is age) Adapt according to your code
typeMismatch.command.age
typeMismatch.age
typeMismatch.java.lang.Integer
typeMismatch
Notice The third code
typeMismatch.java.lang.Integer
It will solve what you want
UPDATE
I have created a Person command class
public class Person implements Serializable {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
And a person controller
public class PersonController extends SimpleFormController {
public PersonController() {
setCommandClass(Person.class);
setValidator(new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "Age is required");
}
});
}
#Override
protected ModelAndView onSubmit(Object command) throws Exception {
return new ModelAndView();
}
}
Here goes my myMessages.properties (root of the classpath)
typeMismatch.command.age=typeMismatch.command.age
typeMismatch.age=typeMismatch.age
typeMismatch.java.lang.Integer=typeMismatch.java.lang.Integer
typeMismatch=typeMismatch
So, i have done the following test
public class PersonControllerTest {
private PersonController personController;
private MockHttpServletRequest request;
private MessageSource messageSource;
#Before
public void setUp() {
request = new MockHttpServletRequest();
request.setMethod("POST");
personController = new PersonController();
messageSource = new ResourceBundleMessageSource();
((ResourceBundleMessageSource) messageSource).setBasename("myMessages");
}
#Test
public void failureSubmission() throws Exception {
/**
* Ops... a bindException
*
* Age can not be a plain String, It must be a plain Integer
*/
request.addParameter("age", "not a meaningful age");
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindException = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
for (Object object : bindException.getAllErrors()) {
if(object instanceof FieldError) {
FieldError fieldError = (FieldError) object;
assertEquals(fieldError.getField(), "age");
/**
* outputs typeMismatch.command.age
*/
System.out.println(messageSource.getMessage((FieldError) object, null));
}
}
}
}
If you want the second one, you must get rid of typeMismatch.command.age key resource bundle
typeMismatch.age=typeMismatch.age
typeMismatch.java.lang.Integer=typeMismatch.java.lang.Integer
typeMismatch=typeMismatch
Or write your own implementation of MessageCodesResolver
public class MyCustomMessageCodesResolver implements MessageCodesResolver {
private DefaultMessageCodesResolver defaultMessageCodesResolver = new DefaultMessageCodesResolver();
public String [] resolveMessageCodes(String errorCode, String objectName) {
if(errorCode.equals("age"))
/**
* Set up your custom message right here
*/
return new String[] {"typeMismatch.age"};
return defaultMessageCodesResolver.resolveMessageCodes(String errorCode, String objectName);
}
public void String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
if(errorCode.equals("age"))
/**
* Set up your custom message right here
*/
return new String[] {"typeMismatch.age"};
return defaultMessageCodesResolver.resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType);
}
}
And set up your PersonController
public class PersonController extends SimpleFormController {
public PersonController() {
setMessageCodesResolver(new MyCustomMessageCodesResolver());
setCommandClass(Person.class);
setValidator(new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "Age is required");
}
});
}
You can register a custom PropertyEditor for that field, which wouldn't fail on type mismatch.
Since this is a Spring MVC application and assuming that it is a simple form, you can set this up in many ways. Can you specify your controller settings? For post request, you can record a suppressed field before the validator is called (assuming you have specified one) or after the validator is called. If you want to do it before validation, you can call [this][2]. After validation, you can call [this][3]
[2]: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html#onBind(javax.servlet.http.HttpServletRequest, java.lang.Object, org.springframework.validation.BindException)
[3]: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html#onBindAndValidate(javax.servlet.http.HttpServletRequest, java.lang.Object, org.springframework.validation.BindException)

Categories

Resources