Unit - V Spring REST
Unit - V Spring REST
The @PathVariable method parameter annotation is used to indicate that a method parameter should be bound
to the value of a URI template variable.
The following code snippet shows the use of a single @PathVariable in a controller method:
@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
The URI Template "/owners/{ownerId}" specifies the variable name ownerId. When the controller
handles this request, the value of ownerId is set the value in the request URI. For example, when a request
comes in for /owners/fred, the value 'fred' is bound to the method parameter String ownerId.
The matching of method parameter names to URI Template variable names can only be done if your code
is compiled with debugging enabled. If you do have not debugging enabled, you must specify the name of
the URI Template variable name to bind to in the @PathVariable annotation.
For example
@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String ownerId, Model model)
{
// implementation omitted
}
The name of the method parameter does not matter in this case, so you may also use a controller method
with the signature shown below
@RequestMapping("/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model
model) {
// implementation omitted
}
Multiple @PathVariable annotations can be used to bind to multiple URI Template variables as shown
below:
@RequestMapping("/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String
petId, Model model) {
Owner owner = ownerService.findOwner(ownderId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
The following code snippet shows the use of path variables on a relative path
@Controller
@RequestMapping("/owners/{ownerId}/**")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String
petId, Model model) {
// implementation omitted
}
}
@RequestParam :@RequestParam annotation is used to read the form data and bind it automatically to
the parameter present in the provided method. So, it ignores the requirement of HttpServletRequest object
to read the provided data.
Including form data, it also maps the request parameter to query parameter and parts in multipart requests.
If the method parameter type is Map and a request parameter name is specified, then the request
parameter value is converted to a Map else the map parameter is populated with all request parameter
names and values.
RequestParam Example
Let's create a login page that contains a username and password. Here, we validate the password with a
specific value.
It is the login page that receive name and password from the user.
index.jsp
1. <html>
2. <body>
3. <form action="hello">
4. UserName : <input type="text" name="name"/> <br><br>
5. Password : <input type="text" name="pass"/> <br><br>
6. <input type="submit" name="submit">
7. </form>
8. </body>
9. </html>
In controller class:
The @RequestParam is used to read the HTML form data provided by a user and bind it to the request
parameter.
The Model contains the request data and provides it to view page.
HelloController.java
1. package com.javatpoint;
2. import org.springframework.stereotype.Controller;
3. import org.springframework.ui.Model;
4. import org.springframework.web.bind.annotation.RequestMapping;
5. import org.springframework.web.bind.annotation.RequestParam;
6.
7. @Controller
8. public class HelloController {
9.
10. @RequestMapping("/hello")
11. //read the provided form data
12. public String display(@RequestParam("name") String name,@RequestParam("pass") String pa
ss,Model m)
13. {
14. if(pass.equals("admin"))
15. {
16. String msg="Hello "+ name;
17. //add a message to the model
18. m.addAttribute("message", msg);
19. return "viewpage";
20. }
21. else
22. {
23. String msg="Sorry "+ name+". You entered an incorrect password";
24. m.addAttribute("message", msg);
25. return "errorpage";
26. }
27. }
28. }
To run this example, the following view components must be located inside the WEB-INF/jsp directory.
viewpage.jsp
1. <html>
2. <body>
3. ${message}
4. </body>
5. </html>
errorpage.jsp
1. <html>
2. <body>
3. ${message}
4. <br><br>
5. <jsp:include page="/index.jsp"></jsp:include>
6. </body>
7. </html>
8. </html>
Output:
Matrix variables is a spring coined term and an alternative implementation for passing and parsing URI
path parameters.
Matrix variables support became available in Spring MVC 3.2 and is meant to simplify requests with a
large number of parameters.
In this article, we will show how we can simplify complex GET requests that use either variable or
optional path parameters inside the different path segments of a URI.
2. Configuration
To enable Spring MVC Matrix Variables, let’s start with the configuration:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}Copy
These variables can appear in any part of the path, and the character equals (“=”) is used for giving values
and the semicolon(‘;’) for delimiting each matrix variable. On the same path, we can also repeat the same
variable name or separate different values using the character comma(‘,’).
Our example has a controller that provides information about the employees. Each employee has a
working area, and we can search by that attribute. The following request could be used for searching:
http://localhost:8080/spring-mvc-java-
2/employeeArea/workingArea=rh,informatics,adminCopy
or like this:
http://localhost:8080/spring-mvc-java-2
/employeeArea/workingArea=rh;workingArea=informatics;workingArea=adminCopy
When we want to refer to these variables in Spring MVC, we should use the
annotation @MatrixVariable.
We can specify required or default properties for the variable. In the following example,
the contactNumber is required, so it must be included in our path, something like this :
http://localhost:8080/spring-mvc-java-
2/employeesContacts/contactNumber=223334411Copy
5. Complement Parameter
As a result, we will get all the employees who have the contact number 22001 or whose name is John.
If for some reason, we want to get all the variables that are available on the path, we can bind them to
a Map:
http://localhost:8080/spring-mvc-java-
2/employeeData/id=1;name=John;contactNumber=2200112334Copy
7. Partial Binding
Apart from simplicity, flexibility is another gain, matrix variables can be used in a variety of different
ways. For example, we can get each variable from each path segment. Consider the following request:
http://localhost:8080/spring-mvc-java-2/
companyData/id=2;name=Xpto/employeeData/id=1;name=John;
contactNumber=2200112334Copy
If we only want to know the matrix variable name of the companyData segment, then, we should use as
an input parameter the following:
@MatrixVariable(value="name", pathVar="company") String name
Copy
8. Firewall Setup
If the application uses Spring Security, then StrictHttpFirewall is used by default. This blocks requests
that appear to be malicious, including Matrix Variables with semicolon separator.
We can customize this implementation in application configuration and allow such variables whilst
rejecting other possibly malicious requests.
However, this way, we can open the application to attacks. Therefore, we should only implement this
after careful analysis of the application and security requirements.
9. Conclusion
This article illustrated some of the various ways that matrix variables can be used.
It’s essential to understand how this new tool can deal with requests that are too complex or help us add
more parameters to delimit our search.
Exception Handling
The Spring framework MVC module has excellent features for error handling. But it is left to the
developer to use those features to treat the exceptions and return meaningful responses to the API client.
Let’s look at an example of the default Spring Boot answer when we issue an HTTP POST to
the /birds endpoint with the following JSON object that has the string “aaa” on the field “mass,” which
should be expecting an integer:
{
"scientificName": "Common blackbird",
"specie": "Turdus merula",
"mass": "aaa",
"length": 4
}
The Spring Boot default answer, without proper error handling, looks like this:
{
"timestamp": 1658551020,
"status": 400,
"error": "Bad Request",
"exception":
"org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Unrecognized token 'three': was expecting
('true', 'false' or 'null'); nested exception is
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'aaa': was
expecting ('true', 'false' or 'null')\n at [Source:
java.io.PushbackInputStream@cba7ebc; line: 4, column: 17]",
"path": "/birds"
}
The Spring Boot DefaultErrorAttributes-generated response has some good fields, but it is too
focused on the exception. The timestamp field is an integer that doesn’t carry information about its
measurement unit. The exception field is only valuable to Java developers, and the message leaves the
API consumer lost in implementation details that are irrelevant to them. What if there were more details
we could extract from the exception? Let’s learn how to handle exceptions in Spring Boot properly and
wrap them into a better JSON representation to make life easier for our API clients.
As we’ll be using Java date and time classes, we first need to add a Maven dependency for the Jackson
JSR310 converters. They convert Java date and time classes to JSON representation using
the @JsonFormat annotation:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Next, let’s define a class for representing API errors. We’ll create a class called ApiError with enough
fields to hold relevant information about errors during REST calls:
class ApiError {
ApiError(HttpStatus status) {
this();
this.status = status;
}
The status property holds the operation call status, which will be anything from 4xx to signal
client errors or 5xx to signal server errors. A typical scenario is an HTTP code 400:
BAD_REQUEST when the client, for example, sends an improperly formatted field, like an
invalid email address.
The timestamp property holds the date-time instance when the error happened.
The message property holds a user-friendly message about the error.
The debugMessage property holds a system message describing the error in detail.
The subErrors property holds an array of suberrors when there are multiple errors in a single
call. An example would be numerous validation errors in which multiple fields have failed.
The ApiSubError class encapsulates this information:
abstract class ApiSubError {
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
class ApiValidationError extends ApiSubError {
private String object;
private String field;
private Object rejectedValue;
private String message;
The ApiValidationError is a class that extends ApiSubError and expresses validation problems
encountered during the REST call.
Below, you’ll see examples of JSON responses generated after implementing these improvements.
Here is a JSON example returned for a missing entity while calling endpoint GET /birds/2:
{
"apierror": {
"status": "NOT_FOUND",
"timestamp": "22-07-2022 06:20:19",
"message": "Bird was not found for parameters {id=2}"
}
}
Here is another example of JSON returned when issuing a POST /birds call with an invalid value for
the bird’s mass:
{
"apierror": {
"status": "BAD_REQUEST",
"timestamp": "22-07-2022 06:49:25",
"message": "Validation errors",
"subErrors": [
{
"object": "bird",
"field": "mass",
"rejectedValue": 999999,
"message": "must be less or equal to 104000"
}
]
}
}
Spring Boot Error Handler
RestController is the base annotation for classes that handle REST operations.
ExceptionHandler is a Spring annotation that provides a mechanism to treat exceptions thrown during
execution of handlers (controller operations). This annotation, if used on methods of controller classes,
will serve as the entry point for handling exceptions thrown within this controller only.
Altogether, the most common implementation is to use @ExceptionHandler on methods
of @ControllerAdvice classes so that the Spring Boot exception handling will be applied globally or
to a subset of controllers.
ControllerAdvice is an annotation in Spring and, as the name suggests, is “advice” for multiple
controllers. It enables the application of a single ExceptionHandler to multiple controllers. With this
annotation, we can define how to treat such an exception in a single place, and the system will call this
handler for thrown exceptions on classes covered by this ControllerAdvice.
The subset of controllers affected can be defined by using the following selectors
on @ControllerAdvice: annotations(), basePackageClasses(),
and basePackages(). ControllerAdvice is applied globally to all controllers if no selectors are
provided
By using @ExceptionHandler and @ControllerAdvice, we’ll be able to define a central point for
treating exceptions and wrapping them in an ApiError object with better organization than is possible
with the default Spring Boot error-handling mechanism.
Handling Exceptions:
Next, we’ll create the class that will handle the exceptions. For simplicity, we call
it RestExceptionHandler, which must extend from Spring
Boot’s ResponseEntityExceptionHandler. We’ll be
extending ResponseEntityExceptionHandler, as it already provides some basic handling of Spring
MVC exceptions. We’ll add handlers for new exceptions while improving the existing ones.
If you take a look at the source code of ResponseEntityExceptionHandler, you’ll see a lot of
methods called handle******(),
like handleHttpMessageNotReadable() or handleHttpMessageNotWritable(). Let’s see how
can we extend handleHttpMessageNotReadable() to
handle HttpMessageNotReadableException exceptions. We just have to override the
method handleHttpMessageNotReadable() in our RestExceptionHandler class:
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object>
handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders
headers, HttpStatus status, WebRequest request) {
String error = "Malformed JSON request";
return buildResponseEntity(new ApiError(HttpStatus.BAD_REQUEST, error, ex));
}
That said, let’s create an ExceptionHandler for this newly created EntityNotFoundException in
our RestExceptionHandler class. Create a method called handleEntityNotFound() and annotate
it with @ExceptionHandler, passing the class object EntityNotFoundException.class to it. This
declaration signalizes Spring that every time EntityNotFoundException is thrown, Spring should call
this method to handle it.
When annotating a method with @ExceptionHandler, a wide range of auto-injected parameters
like WebRequest, Locale, and others may be specified as described here. We’ll provide the
exception EntityNotFoundException as a parameter for this handleEntityNotFound method:
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(
EntityNotFoundException ex) {
ApiError apiError = new ApiError(NOT_FOUND);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
}
Great! In the handleEntityNotFound() method, we set the HTTP status code to NOT_FOUND and
usethe new exception message. Here is what the response for the GET /birds/2 endpoint looks like
now:
{
"apierror": {
"status": "NOT_FOUND",
"timestamp": "22-07-2022 04:02:22",
"message": "Bird was not found for parameters {id=2}"
}
}
It is important to control exception handling so we can properly map exceptions to the ApiError object
and inform API clients appropriately. Additionally, we would need to create more handler methods (the
ones with @ExceptionHandler) for thrown exceptions within the application code. The GitHub
Data Validation:
When building REST API, we expect RESTful service to have a certain structure or format. We expect
REST API request to follow this structure and constraints. We can not assume that API request will
follow structure and constraints. How to handle if REST request is not meeting constraints.
We need to think about following questions while designing our REST API.
One of the fundamental principles of RESTful services is to think about “Consumers” while designing
your REST web services. We should keep in mind below design principles for validating REST API in
Spring.
REST API should return a clear message indicating what was wrong int he request.
API should try to provide information on what can be done to fix the error.
Well defined response status.
Spring MVC provides a number of build in options for REST API date validation. We will cover different
options in this post. It is up to your requirement to determine which approach fulfill your use case.
If our REST API is using @RequestParam or @PathVaraible, Spring provides out of the box support for
validating it. Here is a simple use case for validating REST data using Spring
@GetMapping("/greeting")
This is the simplest validation provided by Spring MVC. It will validate incoming request. Spring will
send a detailed message back to the consumer in case we are missing required data in the REST API.
If we run our application without passing name in the request parameter, Spring will send a detailed error
response back to the consumer.
"timestamp": 1517983914615,
"status": 400,
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"path": "/greeting"
}
We will talk about customizing error response in our exception handling post.
Real life REST API’s are more complex than a simple method with few parameters. Enterprise REST
API involve complex domain models and also require the ability to validate REST API data.
Spring MVC provides build in support for JSR303 (Bean Validation). Bean Validation comes with an
extensive list of validation available out of the box along with the ability to create custom validations.
With Spring Bean Validation support, we need to annotate out DTO with required annotations and build
in validation support will ensure to validate incoming data before passing it to our business layer.
@NotNull
return firstName;
this.firstName = firstName;
return lastName;
this.lastName = lastName;
this.age = age;
return email;
this.email = email;
before we move ahead, let’s discuss what we are trying to validate in our DTO
We will not accept null values for firstName and lastName (You can also
add @notEmpty annotation from Hibernate validator)
Age should not be null and should be greater than or equal to 18.
We need a valid email address for the customer.
Bean Validation provides us a way not only to validate REST API request but also provide consumer-
centric error messages.
We are using the hard-coded message in the class itself, but Spring provides the way to use a localized
properties file for localized messages.
@RestController
@RequestMapping("/api/rest")
@PostMapping("/customer")
{"firstName":"Umesh","lastName":null,"age":"18","email":"umeshawasthi@wordpress-
241348-2978695.cloudwaysapps.com"}
timestamp": 1518066946111,
"status": 400,
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [],
"message": "Validation failed for object='customer'. Error count: 1",
"path": "/api/rest/customer"
If we will send all values in correct format and data type, REST API will send success response back to
the consumer
"firstName": "Umesh",
"lastName": "Awasthi",
"age": 18,
"email": "[email protected]"
JSR 303 bean validation provides a number of out of the box validator. For enterprise applications, we
will get into a situation where out of the box validator will not fulfill our business requirements.
Spring provides flexibility to create our own custom validator and uses these custom validations. Please
read Spring MVC Custom Validator for more detail.
You can use same domain object for different endpoints. Let’s take the example of our customer.
If we are creating a new customer, we do not need customer id and it can be null.
For updating customer, we need customer id to identify customer for the update.
For such use cases, you don’t need 2 separate objects but can use Grouping constraints feature provided
by JSR 303.
Summary
we discussed different options for Validating REST API using Spring. Validating data is essential for
the Spring REST API. Data validation perform data sanity and provide additional security check for data
insertion.
We discussed build in validation support from Spring and Spring MVC along with an option to create
customer validator in Spring if out of the box validator is not sufficient for the business needs.
In our next articles on building REST API with Spring, we will discuss REST Resource Naming Best
Practices.
Securing Spring REST endpoints
Security plays a vital role in REST API development. An insecure REST API can provide direct access to
sensitive data on back-end systems. So organizations need to pay attention to API Security.
Spring Security provides various mechanisms to secure our REST APIs. One of them is API keys.
An API key is a token that a client provides when invoking API calls.
In this tutorial, we’ll discuss the implementation of API key-based authentication in Spring Security.
Spring Security can be used to secure REST APIs. REST APIs are stateless. Thus, they shouldn’t use
sessions or cookies.
Instead, these should be secured using Basic authentication, API Keys, JWT, or OAuth2-based
tokens.
Basic authentication is a simple authentication scheme. The client sends HTTP requests with
the Authorization header that contains the word Basic, followed by a space, and a Base64-
encoded string username:password. Basic authentication is only considered secure with other security
mechanisms, such as HTTPS/SSL.
2.2. OAuth2
OAuth2 is the de facto standard for REST API security. It’s an open authentication and authorization
standard that allows resource owners to give clients delegated access to private data via an access token.
2.3. API Keys
Some REST APIs use API keys for authentication. An API key is a token that identifies the API client to
the API without referencing an actual user. The token can be sent in the query string or as a request
header. Like Basic authentication, it’s possible to hide the key using SSL.
In this article, we’ll focus on implementing API Key authentication using Spring Security.
In this section, we’ll create a Spring Boot application and secure it using API key-based authentication.
3.1. Maven Dependencies
The idea is to get the HTTP API Key header from the request, and then check the secret with our
configuration. In this case, we’ll need to add a custom Filter in the Spring Security configuration
class. We’ll start by implementing the GenericFilterBean.
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
try {
Authentication authentication =
AuthenticationService.getAuthentication((HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception exp) {
HttpServletResponse httpResponse = (HttpServletResponse)
response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = httpResponse.getWriter();
writer.print(exp.getMessage());
writer.flush();
writer.close();
}
filterChain.doFilter(request, response);
}
}Copy
We only need to implement a doFilter() method. In this method, we’ll evaluate the API Key header, and
set the resulting Authentication object into the current SecurityContext instance. Next, the request is
passed to the remaining filters for processing, and then routed to DispatcherServlet, and finally to our
controller.
We’ll delegate the evaluation of the API Key and construction of the Authentication object to
the AuthenticationService class:
Our getAuthentication method is quite simple; we just compare the API Key header and secret with a
static value. To construct the Authentication object, we must use the same approach Spring Security
typically uses to build the object on a standard authentication.
To successfully implement authentication for our application, we’ll need to convert the incoming
API Key to an Authentication object, such as
an AbstractAuthenticationToken. The AbstractAuthenticationToken class implements
the Authentication interface, representing the secret/principal for an authenticated request.
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return apiKey;
}
}Copy
The ApiKeyAuthentication class is a type of AbstractAuthenticationToken object with
the apiKey information obtained from the HTTP request. We’ll use the setAuthenticated(true) method in
the construction.
We can register our custom filter programmatically by creating a SecurityFilterChain bean. In this
case, we’ll need to add the AuthenticationFilter before
the UsernamePasswordAuthenticationFilter class using the addFilterBefore() method on
an HttpSecurity instance.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws
Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.authenticated()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}Copy
Also, the session policy is set to STATELESS because we’ll use REST endpoints.
3.5. ResourceController
@RestController
public class ResourceController {
@GetMapping("/home")
public String homeEndpoint() {
return "Baeldung !";
}
}Copy
3.6. Disabling the Default Auto-Configuration
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class})
public class ApiKeySecretAuthApplication {
4. Testing
We can use the curl command to consume the secured application. First, let’s try to request
the /home without providing any security credentials:
5. Conclusion
In this article, we discussed the REST API security mechanisms. Then we implemented Spring Security
in our Spring Boot application to secure our REST API using the API Keys authentication mechanism.