Spring RequestMapping with custom isLoggedIn parameter - java

I've been searching internet for several hours now, and cannot find a way to add my own variables into request mapping.
We are using a custom user authentication system. And I want to serve 2 different controllers for the same path depending on if the user is authenticated or not. More specifically, I want to create controllers that only mapped to if the user is not authenticated.
How can I achieve something like this: (I want to define isLoggedIn myself).
public class PageController {
#RequestMapping(value = "/page", isLoggedIn = false)
#ResponseBody
String getPage(){
return "Page content";
}
}
I want request to hit this controller if user is not logged in, and fallback to catch all if user is logged in. I am open to solutions using Interceptors, Custom Annotations, or extending RequestMapping or anything else.

#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RequestMapping(
method = {RequestMethod.GET}
)
public #interface CustomGetRequestMapping {
#AliasFor(value = "value", annotation = RequestMapping.class)
String path();
boolean isLoggedIn() default false;
}
I guess this is not enough because you have to write interceptor and catch the logged in user.

Related

How to design API in Spring MVC?

I have a Spring MVC controller but I'm not sure that it is a good or bad design. As far as I know, api versioning is missing but apart from that I implemented Swagger for documentation and added SpringSecurity and tried to follow YARAS(Yet Another RESTful API Standard) to build it but I need another eye on that to comment it.
#Slf4j
#Controller
#RequestMapping
#RequiredArgsConstructor
public class XGameController implements GameController {
private final GameService gameService;
private final ObjectMapper mapper;
#RequestMapping(value = "/", method= RequestMethod.GET)
public String index() {
return "game";
}
#RequestMapping(value = "/login", method= RequestMethod.GET)
public String login() {
return "login";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games", method= RequestMethod.POST)
public String initializeGame(Model model) {
log.info("New XGame is initializing...");
Game game = new Game();
game = gameService.initializeGame(game.getId());
try {
model.addAttribute("game", mapper.writeValueAsString(game));
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
log.info("New XGame is initialized successfully!");
return "game";
}
#Secured("ROLE_USER")
#RequestMapping(value = "/games/{gameId}", method= RequestMethod.PUT)
public #ResponseBody Game play(#PathVariable("gameId") String gameId,
#RequestParam Integer pitNumber,
#RequestParam String action) {
log.info("Sowing stone is triggered...");
return gameService.executeGameRules(UUID.fromString(gameId), pitNumber);
}
#RequestMapping(value = "/403", method= RequestMethod.GET)
public String error403() {
return "/error/403";
}
}
My swagger snapshot;
I would make some changes.
In /games/{gameId} I would use PATCH instead of PUT. The reason is that PUT is intended to completely replace the resource (in your case, the Game). This does not seem to be what you are doing in this endpoint. PATCH is intended to partially update a resource, which seems much more suited to what you are doing here.
Still in /games/{gameId} I would use the request body to provide the needed data instead of query parameters. It simply doesn't seem right. Query parameters are way more suited to GET requests than to POST, PUT or PATCH.
I would rename /403 to something else that actually gives some context about what 403 is. Having said this, I would go with /error-pages/403. Additionally, I would also consider removing this endpoint from the swagger specification.
Other than this, it seems fine to me.
Some advices :
Use a path that represents the context or the idea of your controller and you can add the version
#RequestMapping("/V1/xgame")
Use specialized annotations such as : #GetMapping, #PostMapping etc...
For /403 use a meaning full name such as /errors
Use custom message that you will return to the users. For that you need a ControllerAdvice.
Google on patterns and best practices Rest API design
Read some books for better undertanding.
Firstly, instead of #RequestMapping use a specific Mapping(Get, Post,etc.) and the use of type of mapping is up to you which you find more particular to the cause of using it
if you are redirecting from a page to homepage try to use return "redirect:"/url"" instead of just returning HTML file directly.
Rename your method for error, RequestMapping value to some more reasonable name.
instead of using return "/error/403"
use return "redirect:/error/403"

Spring security role-based authorisation best practices

So I basically have a controller method with a PreAuthorize annotation. By default the method will return all projects. The method signature also includes an optional query string (blank query means retrieve all records).
The issue is that if the logged in user is only supposed to view/manage his/her own records, the query string needs to include a filter in it such as "clientId:2".
Now to do that, I was thinking of using the Principal object to retrieve the logged in user and check if he/she is a client as well, then I update the query by adding the required filter to it.
I am just not sure if this is the best approach for this type of issues.
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
#RequestMapping(value = "/projects", method = RequestMethod.GET)
public ResponseEntity<RestResponse> list(Principal principal, #RequestParam(value = "query", required = false, defaultValue = "") String query) {
//If a client is logged in, he/she will have the MANAGE_OWN authority so will need to update the query string to include clientId:<logged-in-client-id>
I would rather move the #PreAuthorize to an application service.
class SomeApplicationService {
UserService userService;
SecurityService securityService;
#PreAuthorize("hasAuthority('MANAGE_ALL') OR hasAuthority('VIEW_ALL') OR hasAuthority('MANAGE_OWN')")
public List<Project> getProjects(String clientId) {
User currentUser = userService.getLoggedInUser();
if(securityService.canManageAllProjects(currentUser))
//get all projects or projects of clientId
else if(securityService.canManageOwnProjects(currentUser))
//get own projects, ignore clientId
}
}

hibernate validator custom constraint throws exception, how to catch it in spring when using #Valid?

I have a subsystem in a web application that performs an interactive validation on a signup process, so when a user types an username, email, password, etc. there is an async request to the server to perform the validation, and then returns a response.
One of the validations performed for username or emails is that they aren't used already for any other user, meaning a query to the database is required, this is why i choose to perform the validation on the server side. I'm using Spring Framework 4.1, Spring Data and Hibernate Validator 5.1.
For the available username and email validations, following the HV reference guide i created custom constraints annotations (only showing username one):
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = UniqueUsernameValidator.class)
#Documented
public #interface UniqueUsername {
String message() default "{}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and validator class:
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername,String> {
private final UserRepository userRepository;
#Autowired
public UniqueUsernameValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public void initialize(UniqueUsername annotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// this should be checked with #NotNull at field level
if (value == null)
return true;
User user = userRepository.findByUsername(value);
if (user == null)
return true;
return false;
}
}
So when the user types into a form field, there are AJAX requests with jQuery where i serialize the entire form and send it on a POST request that i capture with a #RestController like this:
#RequestMapping(value = "/signup/validation/{field}", method = RequestMethod.POST)
public ResponseEntity<ValidationResponse> validateField(
#PathVariable("field") String field,
#Valid #ModelAttribute("registrationForm") UserRegistrationForm registrationForm,
BindingResult result,
Locale locale) {
...
}
Question 1: In case the repository throws a DataAccessException in the validator class, is there a way to catch it at the rest controller when using the #Valid annotation?
Question 2: Is there anything wrong with this approach?
Note: I'm not catching the DataAccessException in the validator class because i wan't it to propagate and be able to catch it on the controller so i can return an appropiate HTTP 500 code with the ResponseEntity return value and inform the user there has been some issue with the validation, because if the database is down for some reason, the signup request will fail to be persisted aswell.
Note: I could get a validator instance inside the #RestController method and then perform the validation right there, and be able to catch the exception easy, which is the workaround i'm using right now, but i'd like to avoid this for the sake of reducing boilerplate and code duplication.

Overloading a spring controller method with the same request mapping

I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.

Spring Web MVC - validate individual request params

I'm running a webapp in Spring Web MVC 3.0 and I have a number of controller methods whose signatures are roughly as follows:
#RequestMapping(value = "/{level1}/{level2}/foo", method = RequestMethod.POST)
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#RequestParam("foo_name") String fooname,
#RequestParam(value = "description", required = false) String description);
I'd like to add some validation - for example, description should be limited to a certain length or fooname should only contain certain characters. If this validation fails, I want to return a message to the user rather than just throw some unchecked exception (which would happen anyway if I let the data percolate down to the DAO layer). I'm aware of JSR303 but have not worked with it and don't quite understand how to apply it in a Spring context.
From what I understand, another option would be to bind the #RequestBody to an entire domain object and add validation constraints there, but currently my code is set up to accept individual parameters as shown above.
What is the most straightforward way to apply validation to input parameters using this approach?
This seems to be possible now (tried with Spring 4.1.2), see https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
Extract from above page:
Add MethodValidationPostProcessor to Spring #Configuration class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Add #Validated to controller class
Use #Size just before #RequestParam
#RequestMapping("/hi")
public String sayHi(#Size(max = 10, message = "name should at most 10 characters long") #RequestParam("name") String name) {
return "Hi " + name;
}
Handle ConstraintViolationException in an #ExceptionHandler method
There's nothing built in to do that, not yet anyway. With the current release versions you will still need to use the WebDataBinder to bind your parameters onto an object if you want automagic validation. It's worth learning to do if you're using SpringMVC, even if it's not your first choice for this task.
It looks something like this:
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#Valid #ModelAttribute() FooWrapper fooWrapper,
BindingResult errors) {
if (errors.hasErrors() {
//handle errors, can just return if using Spring form:error tags.
}
}
public static class FooWrapper {
#NotNull
#Size(max=32)
private String fooName;
private String description;
//getset
}
If you have Hibernate Validator 4 or later on your classpath and use the default dispatcher setup it should "Just work."
Editing since the comments were getting kind of large:
Any Object that's in your method signature that's not one of the 'expected' ones Spring knows how to inject, such as HttpRequest, ModelMap, etc, will get data bound. This is accomplished for simple cases just by matching the request param names against bean property names and calling setters. The #ModelAttribute there is just a personal style thing, in this case it isn't doing anything. The JSR-303 integration with the #Valid on a method parameter wires in through the WebDataBinder. If you use #RequestBody, you're using an object marshaller based on the content type spring determines for the request body (usually just from the http header.) The dispatcher servlet (AnnotationMethodHandlerAdapter really) doesn't have a way to 'flip the validation switch' for any arbitrary marshaller. It just passes the web request content along to the message converter and gets back a Object. No BindingResult object is generated, so there's nowhere to set the Errors anyway.
You can still just inject your validator into the controller and run it on the object you get, it just doesn't have the magic integration with the #Valid on the request parameter populating the BindingResult for you.
If you have multiple request parameters that need to be validated (with Http GET or POST). You might as well create a custom model class and use #Valid along with #ModelAttribute to validate the parameters. This way you can use Hibernate Validator or javax.validator api to validate the params. It goes something like this:
Request Method:
#RequestMapping(value="/doSomething", method=RequestMethod.GET)
public Model dosomething(#Valid #ModelAttribute ModelRequest modelRequest, BindingResult result, Model model) {
if (result.hasErrors()) {
throw new SomeException("invalid request params");
}
//to access the request params
modelRequest.getFirstParam();
modelRequest.getSecondParam();
...
}
ModelRequest class:
class ModelRequest {
#NotNull
private String firstParam;
#Size(min = 1, max = 10, message = "You messed up!")
private String secondParam;
//Setters and getters
public void setFirstParam (String firstParam) {
this.firstParam = firstParam;
}
public String getFirstParam() {
return firstParam;
}
...
}
Hope that helps.

Categories

Resources