Multiple Controller classes for single request mapping in spring - spring

I have Controller class with more number of rest methods, so i want to split my controller class as multiple sub classes, but i want same request mapping for all the classes
Example
#RequestMapping("/Student")
public class StudentController {
#PostMapping(consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> saveStudnt(HttpServletRequest request)
{
}
#GetMapping(consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> getStudent(HttpServletRequest request)
{
}
}
i want to separate these 2 methods into 2 separate controller but i want access with same request mapping /student
is this possible in spring boot?

Why not? In runtime, Spring considers only final endpoints, not depend on where they are placed. You can have the same names of methods in different classes, but one path for an endpoint.

You can split the methods in two classes but this is not a best practise as far as I know
public class ControllerPost {
#PostMapping(path ="/Student" consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> saveStudnt(HttpServletRequest request)
{
}
}
and
public class ControllerGet {
#GetMapping(path ="/Student" consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> getStudent(HttpServletRequest request)
{
}
}

Related

How to make sure that the content of the request's parameter is safe?

This is my first time writing something in Spring. I add to the code already in the repo a small service that accepts a request parameter from the client and passes it to a third-party API.
The service contains two classes, not counting the interface:
#RestController
#RequestMapping("/store/search")
#CrossOrigin("*")
public class APToid {
private AptoidSearch aptoidSearch;
#GetMapping
public ResponseEntity<String> searchInAptoidStore(#RequestParam("query") String query) {
return aptoidSearch.aptoidSearch(query);
}
}
and
#Service
public class AptoidSearchImpl implements AptoidSearch {
#Autowired
private APToidUriConfiguration configuration;
#Override
public ResponseEntity<String> aptoidSearch(String query) {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForEntity(configuration.getUri() + query, String.class);
}
}
The query string, as you can see, is taken directly from the client. What are some ways to make sure the client isn't giving me some dangerous shit, like exploit, etc?

Custom Schema for Spring Boot 2 OpenAPI 3 Documentation

I have a requirement to integrate OpenAPI 3 documentation for my Spring Boot 2 project. We did not used modals/DTOs on controllers.
Here is the sample controller:
#RestController
#RequestMapping(value = "/pet")
public class PetController {
#RequestMapping(value = "/save", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Map<String, Object>> savePet(
#RequestBody Map<String, Object> petObj, HttpServletRequest request)
throws Exception {
String petResponse = petDAO.savePet(petObj, request, true);
return new ResponseEntity<Map<String, Object>>(petResponse, HttpStatus.OK);
}
}
Request body:
{
"name":"Test",
"category":"school"
}
My response:
{
"petId":"1",
"petName":"Test",
"petCategory":"school",
"petStaus":"active"
}
I am not able to find a way to add the OpenAPI doc for my custom Map object. I want to add key, description, type, example(s) for each property in my Map manually.
Can anyone suggest how to do this?
This is the default behaviour of the springdoc-openapi library in order to ignore other injectable parameters supported by Spring MVC.
https://docs.spring.io/spring/docs/5.1.x/spring-framework-reference/web.html#mvc-ann-arguments
If you want to change this behaviour, you can just exlcude it as follow:
SpringDocUtils.getConfig().removeRequestWrapperToIgnore(Map.class);

How to unit test a multipart POST request with Spring MVC Test?

I am trying to create unit test for REST APi but having big trouble with the uploading excel method.
Here is the method on the controller side
#RestController()
#RequestMapping(path = "/upload")
#CrossOrigin(origins = "http://localhost:4200")
public class FileController {
#Autowired
FileService fileService;
#PostMapping(value = "/{managerId}/project/{projectId}")
public List<Task> importExcelFile(#RequestParam("file") MultipartFile files, #PathVariable int managerId,
#PathVariable int projectId) throws IOException, ParseException {
return fileService.getTasksFromExcel(files, managerId, projectId);
}
Whatever I try I get a lot of errors and evidently I don't really understand what I am supposed to do.
The main error I get is
current request is not a multipart request
You can do the following.
I just simplified your example a tiny bit.
So, here's the controller that returns the file size of the file it receives.
#RestController
#RequestMapping(path = "/upload")
public class FileController {
#PostMapping(value = "/file")
public ResponseEntity<Object> importExcelFile(#RequestParam("file") MultipartFile files) {
return ResponseEntity.ok(files.getSize());
}
}
and this one is the test of it. There is a class called MockMvc that Spring provides to easily unit test your controllers and controller advices. There is a method called multipart that you can use to simulate file upload cases.
class FileControllerTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new FileController())
.build();
#Test
#SneakyThrows
void importExcelFile() {
final byte[] bytes = Files.readAllBytes(Paths.get("TEST_FILE_URL_HERE"));
mockMvc.perform(multipart("/upload/file")
.file("file", bytes))
.andExpect(status().isOk())
.andExpect(content().string("2037")); // size of the test input file
}
}
Generally Multipart uploads can be tested via MockMultipartFile:
https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/file-upload-test.html

Postman Request to GET query with Multiple List<String> Parameters

I am trying to test a Multi Module Spring WebMVC API endpoint using Postman. This is Spring-MVC web app & using other frameworks too.
I want to know how to make a request to this URL.
My Controller File looks like this.
#Controller
#RequestMapping(value = "/xyz")
public class XyzWebController {
#CrossOrigin(origins = "*")
#RequestMapping(value = "", method = RequestMethod.GET)
#ResponseBody
public List<XyzChild> getProperties(#RequestParam XyzQueryDTO query) {
return childService.getAll(query);
}
...
}
XyzQueryDTO.java looks like this.
public class XyzQueryDTO {
List<String> properties;
List<String> applications;
public XyzQueryDTO() {
}
public XyzQueryDTO(List<String> properties,
List<String> applications) {
super();
this.properties = properties;
this.applications = applications;
}
...
}
Please assist me with the URL with which i can test this API.
Thanks in Advance.
It's more simple and correct to use RequestMethod.POST instead of RequestMethod.GET and #RequestBody instead of #RequestParam
#RequestMapping(value = "", method = RequestMethod.POST)
#ResponseBody
public List<XyzChild> getProperties(#RequestBody XyzQueryDTO query) {
return childService.getAll(query);
}
And you can use #RestController instead of #Controller and remove #ResponseBody
For data type conversion use jackson librairy.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
In Postman you can fill your XyzQueryDTO in the BODY as json

How to check security acess (#Secured or #PreAuthorize) before validation (#Valid) in my Controller?

here is my Controller code :
#PreAuthorize("hasRole('CREATE_USER')")
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public UserReturnRO createUser(#Valid #RequestBody UserRO userRO) throws BadParameterException{
return userService.createUser(userRO);
}
My need is when a client without the appropriate role tries to create a user, the controller responds "Not authorized" even if the data sent are not valid. Instead of that, if the client (without the appropriate role) tries to create a user with wrong data, my controller responds with the #Valid message (ex : "password cannot be empty"), while I want it responds "not authorized".
In the PreAuthorized Interface we can find this sentence :
Annotation for specifying a method access-control expression which will be evaluated to decide whether a method invocation is allowed or not.
but it seems that it's not the case.
You can not do this directly, since #Valid is processed before an actual method call and as a result before #PreAuthorize.
But what you can do instead is to inject BindingResult just right after your model (userRO) and in doing so - take control of validation process. Then check if BindingResult has some errors and if so return bad request response (similar to what spring does).
Example:
#ResponseBody
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole('CREATE_USER')")
public ResponseEntity<?> createUser(#RequestBody #Valid UserRO userRO, BindingResult result) {
if (result.hasErrors()) {
return ResponseEntity.badRequest().body(result.getAllErrors());
}
return ResponseEntity.ok(userService.createUser(userRO));
}
As already stated, Spring Security's #PreAuthorize is method advice, which means that it does not get to participate until the method and its arguments have already been resolved.
Aside from the answer already given, there are a few ways to move authorization before argument resolution, instead.
Filter Security
First, Spring Security checks URLs before the request is mapped to a method. And since this is a #Controller, it's reasonable to suppose that you could instead map the request to the role at that level instead of #PreAuthorize:
http
.authorizeRequests()
.mvcMatchers(POST, "/somepath").hasRole("CREATE_USER")
Handler Interceptor
Second, Spring MVC does ship with limited support for checking authorities before parsing method arguments. For example, you can do:
#EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
UserRoleAuthorizationInterceptor userRole =
new UserRoleAuthorizationInterceptor();
userRole.setAuthorizedRoles("CREATE_USER");
registry.addInterceptor(userRole);
}
}
This is much more basic than #PreAuthorize since it's a global setting, but I've included it for completeness.
Handler Interceptor, Part 2
Third (warning, some inelegance ahead), you can create your own HandlerInterceptor.
The flow is:
FilterSecurityInterceptor <== where .mvcMatchers(...).hasRole(...) lives
Then HandlerInterceptors
Then argument validation
Then MethodSecurityInterceptor <== where #PreAuthorize lives
So, your HandlerInterceptor would check before arguments are resolved. It doesn't have to be as involved as MethodSecurityInterceptor, though. It could, for example, simply be:
static class AuthorizationInterceptor extends HandlerInterceptorAdapter {
SecurityMetadataSource securityMetadataSource;
AccessDecisionManager accessDecisionManager;
#Override
public void preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
Authentication authenticated = (Authentication) request.getUserPrincipal();
MethodInvocation mi = convert(handler);
Collection<ConfigAttribute> attributes =
this.securityMetadataSource.getAttributes(mi);
// throws AccessDeniedException
this.accessDecisionManager.decide(authenticated, mi, attributes);
return true;
}
}
Then you wire it together with:
#EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodConfig extends GlobalMethodSecurityConfiguration {
#Bean
HandlerInterceptor preAuthorize() throws Exception {
return new AuthorizationInterceptor(
accessDecisionManager(), methodSecurityMetadataSource());
}
}
#EnableWebMvc
public static class MvcConfig implements WebMvcConfigurer {
#Autowired
AuthorizationInterceptor authorizationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizationInterceptor);
}
}
It's inelegant because MethodSecurityInterceptor would still participate in authorized requests, which would ostensibly be the majority.

Resources