Restful Service With Spring Boot And Filter Based Security - Codeproject

  • Uploaded by: Gabriel Gomes
  • 0
  • 0
  • January 2021
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Restful Service With Spring Boot And Filter Based Security - Codeproject as PDF for free.

More details

  • Words: 4,767
  • Pages: 18
Loading documents preview...
20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

RESTFul Service with Spring Boot and Filter Based Security Han Bo Sun, 20 Nov 2018

In this tutorial, I will show how to set up a RESTFul service using Spring Boot, And how to use Spring Security to secure the the RESTFul API. And I will used a very simple token based security to secure this service.

Download source - 10.6 KB

Introduction Just recently, I have submitted two tutorials to codeproject.com regarding Spring Boot. One for setting up a simple MVC application using Spring Boot and Spring MVC with no security. Another goes one step further and integrates with Spring Security to lock down the MVC application. From these tutorials you can see Spring Boot handles the MVC web application development really well. And it also supports development of RESTFul web services, which I am about to discuss in this tutorial. I will first explain how to create a RESTFul service using Spring Boot, and Spring Web. In the second half of the tutorial, I will show a simple way of secure the RESTFul service, a simple token based security for the RESTFul service. Application designed with MVC architecture, every page interaction would have a round trip from the browser to back end server, then from back end server back to the browser. RESTFul srevice is different from a typical MVC application. The interaction with RESTFul service is usually exchange of data by browser exchange data and the service. The RESTFul service has no responsibility of constructing the UI display. And this tutorial will show how to design such a web service using Spring Boot.

The Project File Structure For this project, I have setup the the following project structure:
dir>/pom.xml dir>/src/main/java/org/hanbo/boot/rest/App.java dir>/src/main/java/org/hanbo/boot/rest/config/RestAppSecurityConfig.java dir>/src/main/java/org/hanbo/boot/rest/config/security/AuthTokenSecurityProvider.java dir>/src/main/java/org/hanbo/boot/rest/config/security/RestAuthSecurityFilter.java dir>/src/main/java/org/hanbo/boot/rest/controllers/SampleController.java dir>/src/main/java/org/hanbo/boot/rest/controllers/SampleSecureController.java dir>/src/main/java/org/hanbo/boot/rest/models/CarModel.java dir>/src/main/java/org/hanbo/boot/rest/models/CarRequestModel.java dir>/src/main/java/org/hanbo/boot/rest/models/PurchaseTaxModel.java dir>/src/main/java/org/hanbo/boot/rest/models/UserCredential.java dir>/src/main/resources/application.properties

These are basic part that can help create the most basic RESTFul service: App.java: It is the main entry of a Java application. Spring framework use this to bootstrap and start the application. The controller classes which contains the action methods. These will handle the incoming requests. The classes in the models directory which are the requests and responses.

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

1/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

In addition, I added some configurations to this application. And these files can be found under the folder "configuration" and its sub folder "security".

The POM File Just like the previous two tutorials, I will start with the Maven POM file. Here it is: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd"> <modelVersion>4.0.0 org.springframework <artifactId>hanbo-boot-rest 1.0.1 <properties> <java.version>1.8 <parent> org.springframework.boot <artifactId>spring-boot-starter-parent 2.0.5.RELEASE <dependencies> <dependency> org.springframework.boot <artifactId>spring-boot-starter-web <dependency> org.springframework.boot <artifactId>spring-boot-starter-test <scope>test <dependency> org.springframework.boot <artifactId>spring-boot-starter-security <dependency> org.springframework.security <artifactId>spring-security-taglibs <dependency> commons-codec <artifactId>commons-codec 1.11 org.springframework.boot <artifactId>spring-boot-maven-plugin This Maven POM file is nothing special. In it, there is the Spring Boot Starter Web and Spring Boot Starter Security. One is used for implementing the RESTFul web service, and the other is to provide security for the application. The Spring Security taglib is used to provide the @PreAuthroize annotation to lock down the RESTFul controller action methods. I also added the Apache Commons codec jar to encode and decode Base64 string values, which is used in the HTTP security header and the authorization filter that decoding it. https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

2/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

Unlike the previous two tutorials, which package the project as war files, in this tutorial, the project is packaged as a regular jar file.

The Main Entry If you have not read my previous two tutorials, you might not know that application written in Spring Boot does not necessarily need to be deployed into a application server to execute. It can be an standalone application. This is why there will be a main entry. The code looks like this: package org.hanbo.boot.rest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } This is a very simple program. All it does is taking the definition of the current class, then do a scan and find out what are the injectable classes and how to setup the dependency tree so that the application can run. Again, I like to point out that there is no XML base configuration. Everything is done by convention or by class annotation.

The Controller Class The main entry is almost the same for every Spring Boot application. Sometimes it might be a bit different. Once you have it, you can add a controller and some action methods. Then a web application will be ready for use. This is different from the MVC based application, which requires the action methods in a controller to return a view back to the user's browser. With RESTFul APIs, the action methods are returning Spring based HTTP responses, which is considerably simpler. Before I venture further, let me show you the code of this REST based controller: package org.hanbo.boot.rest.controllers; import import import import

java.util.ArrayList; java.util.List; java.util.Optional; java.util.stream.Collectors;

import import import import import import import import import

org.hanbo.boot.rest.models.CarModel; org.hanbo.boot.rest.models.CarRequestModel; org.springframework.http.ResponseEntity; org.springframework.web.bind.annotation.PathVariable; org.springframework.web.bind.annotation.RequestBody; org.springframework.web.bind.annotation.RequestMapping; org.springframework.web.bind.annotation.RequestMethod; org.springframework.web.bind.annotation.ResponseBody; org.springframework.web.bind.annotation.RestController;

@RestController public class SampleController { private List allCars; public SampleController() { allCars = createCarModelCollection(); } @ResponseBody @RequestMapping(value="/public/allCars/{yearOfManufacture}", method = RequestMethod.GET) public ResponseEntity> allCars( @PathVariable("yearOfManufacture") https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

3/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

int year) { ResponseEntity> retVal = null; if (allCars == null || year < 1980) { List listOfCars = new ArrayList(); retVal = ResponseEntity.ok(listOfCars); return retVal; } List foundCars = allCars .stream() .filter(x -> x.getYearOfManufacturing() == year) .collect(Collectors.toList()); retVal = ResponseEntity.ok(foundCars); return retVal; } @RequestMapping(value="/public/findCar", method = RequestMethod.POST) public ResponseEntity findCar( @RequestBody CarRequestModel req) { ResponseEntity retVal = null; if (allCars == null || req == null) { retVal = ResponseEntity.ok((CarModel)null); return retVal; } Optional foundCar = allCars .stream() .filter(x -> x.getYearOfManufacturing() == req.getYear() && x.getMaker().equalsIgnoreCase(req.getManufacturer()) && x.getModel().equalsIgnoreCase(req.getModel())) .findFirst(); if (foundCar.isPresent()) { retVal = ResponseEntity.ok(foundCar.get()); } else { retVal = ResponseEntity.ok((CarModel)null); } return retVal; } private List createCarModelCollection() { List retVal = new ArrayList(); CarModel car = new CarModel(); car.setFullPrice(20000); car.setMaker("Nessan"); car.setModel("Altima"); car.setRebateAmount(600); car.setSuggestedRetailPrice(19250); car.setYearOfManufacturing(2005); retVal.add(car); car = new CarModel(); car.setFullPrice(20096); car.setMaker("Subaru"); car.setModel("Legacy"); car.setRebateAmount(487); car.setSuggestedRetailPrice(20001); car.setYearOfManufacturing(2006); retVal.add(car); car = new CarModel(); car.setFullPrice(20890); https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

4/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

car.setMaker("Subaru"); car.setModel("Outback"); car.setRebateAmount(695); car.setSuggestedRetailPrice(19980); car.setYearOfManufacturing(2007); retVal.add(car); car = new CarModel(); car.setFullPrice(21500); car.setMaker("Honda"); car.setModel("Civic"); car.setRebateAmount(750); car.setSuggestedRetailPrice(20100); car.setYearOfManufacturing(2008); retVal.add(car); car = new CarModel(); car.setFullPrice(22600); car.setMaker("Toyota"); car.setModel("Camery"); car.setRebateAmount(708); car.setSuggestedRetailPrice(21100); car.setYearOfManufacturing(2008); retVal.add(car); return retVal; } } There are a couple noticeable things about this class. First is the annotation used on the class: @RestController public class SampleController { ... } If you have read through one of my two earlier tutorial, you will know that for MVC, the annotation used on the class should be @Controller. Defining RESTFul web service uses different annotation, which is @RestController. This is one noticeable difference between defining a RESTFul Api controller and a MVC controller. Now, take a look at one of the action methods: @RequestMapping(value="/public/allCars/{yearOfManufacture}", method = RequestMethod.GET) public ResponseEntity> allCars( @PathVariable("yearOfManufacture") int year) { ResponseEntity> retVal = null; if (allCars == null || year < 1980) { List listOfCars = new ArrayList(); retVal = ResponseEntity.ok(listOfCars); return retVal; } List foundCars = allCars .stream() .filter(x -> x.getYearOfManufacturing() == year) .collect(Collectors.toList()); retVal = ResponseEntity.ok(foundCars); return retVal; } Another noticeable thing is that this method is the annotation @RequestMapping. This specifies the URL path which the action method can handle. It also specifies what the HTTP method this action method can handle as well. In this case, it handles only HTTP get requests.

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

5/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

The method returns an object of type ResponseEntity>. ResponseEntity wraps the actual object that is being returned back in HTTP response. It can also transform the Java object into JSON and prepare the proper HTTP response to be returned. I like to use this object type because it allows me to set the status code, and returned object. Here is how it was used: ResponseEntity> retVal = null; ... retVal = ResponseEntity.ok(foundCars); return retVal; Next, the parameter of the method is annotated with @PathVariable. This is telling the Spring Web that in the URL path, a part of it can be used as a value. The annotation @RequestMapping specifies the URL path pattern: "/public/allCars/{yearOfManufacture}". "{yearOfManufacture}" will be used as parameter value into the method. In this case the value represented by "{yearOfManufacture}" is passed into the parameter called year. Lastly, the body of the method looks up all the cars in a list that has the year of manufacture of a specific year. And the found list would be returned back as a JSON list of objects. If no cars found, an empty list will be returned. At this point, that is all needed to create the RESTFul service. An main entry, a controller, and some DTO data models. I am not going to show you the code of the DTO data models. They are mock object types with mock data for tutorial purposes. To make this tutorial more interesting, I added a security filter, which is what we will discuss next.

Security Filter It is little different when handling security in a RESTFul service than a MVC web application. When a web application that utilize the RESTFul service, the service itself does not keep sessions, instead, every request that comes in, the service must check check the HTTP header values for authentication data. Once found, it can transform the authenticatiom data into proper authorization. That is, it will create a authoziation token with user name, credential, and user roles for the request. Then pass the request to the action methods. Secured action methods will have annotations like @PreAuthorize(...). Unless the request has the security token with proper role(s), the annotation will force return 403 before the action methods can get the request. If you try hard enough, you can force session based security with authorization cookies. However, RESTFul service supposed to be stateless, using sessions to enforce security would limit the scalability of the service, thus defeat the purpose of being a RESTFul web service. In this tutorial, I am assuming the request coming from the client end (usually from the browser) is already authenticated. That is, some other sevice has provided a security token to the request. The RESTFul service, once intercepted the request, the filter will use the token from the request to determine what the authroization is for the request before it is handed off to the actual action method.

The Spring Security Configuration Here is the class that I have defined the Spring security configuration: package org.hanbo.boot.rest.config; import org.hanbo.boot.rest.config.security.AuthTokenSecurityProvider; import org.hanbo.boot.rest.config.security.RestAuthSecurityFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBui lder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @Configuration https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

6/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

@EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class RestAppSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthTokenSecurityProvider authProvider; @Override protected void configure(HttpSecurity http) throws Exception { System.out.println(" ++++++++++++ Called AuthTokenSecurityProvider.configure( Http Security )..."); http.csrf().disable() .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); http.addFilterAfter(new RestAuthSecurityFilter(super.authenticationManager()), BasicAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder authMgrBuilder) throws Exception { System.out.println(" ++++++++++++ Called AuthTokenSecurityProvider.configure( Auth Manager )..."); authMgrBuilder.authenticationProvider(authProvider); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } The class is called "RestAppSecurityConfig". @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class RestAppSecurityConfig extends WebSecurityConfigurerAdapter { ... } It iherits from WebSecurityConfigurerAdapter. Any sub class derived from WebSecurityConfigurerAdapter would provide the security configurations for the web application. The problem is that Spring framework will not look for it unless you annotate the sub class with @Configuration. There are two more annotation for the class. One is called @EnableWebSecurity. This annotation will enable Spring security for the entire application. The other annotation, EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) enables the authorization annotations for the action methods, such as @PreAuthorize(...) and @PostAuthorize(...). The method called configure(...) is used to define how a request should be handled: @Override protected void configure(HttpSecurity http) throws Exception { System.out.println(" ++++++++++++ Called AuthTokenSecurityProvider.configure( Http Security )..."); http.csrf().disable() .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); http.addFilterAfter(new RestAuthSecurityFilter(super.authenticationManager()), BasicAuthenticationFilter.class); } In this method, what I have defined is as the following: https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

7/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

Any requests route into /public/**, will be handled without any authentication. Although this has no effect because we added a customized filter. Any requests route into other sub path that is not "/public/" must be explicitly authenicated and authorized. Add a customized filter that will handle security token in the request. The customized filter is called RestAuthSecurityFilter, And it takes an AuthenticationManager object so that authenticationManager object can properly authorize the request before it is sent to the action method. Before the AuthenticationManager object can be used, it has to be properly initialized. That is what the next method does: @Override protected void configure(AuthenticationManagerBuilder authMgrBuilder) throws Exception { System.out.println(" ++++++++++++ Called AuthTokenSecurityProvider.configure( Auth Manager )..."); authMgrBuilder.authenticationProvider(authProvider); }

AuthenticationManagerBuilder is the builder object that can create a AuthenticationManager object. And it needs a authentication provider (AuthenticationProvider). That is why I created my own authentication provider for this. We will cover it in the next section. There is one last method in this class, which is a getter that returns an AuthenticationManager object. This is necessary because my filter is defined as Spring service, and it needs a dependency injection of the AuthenticationManager object. So in order for the start-up to work properly, I needed this getter that return the AuthenticationManager bean.

Authentication Provider Customized authentication provider is also a necessity. The simpliest way to see this is: An authentication manager builder would create an authentication manager. The autnetication manager uses an authentication provider to authenticate the user's credential. Create a customized authentication provider allows a programmer to define how the user credential can be verified and how to set the user auhorization. A common thing that all of us like to do is: Get user name and password, hash the password. Fetch the user credential from database. Match the user name and password hash against the user credential in the database, if they are equal and user is actively. Then the associated roles from database will be added to the security token. If the user credential does not match, then no security token will be created. When the request is passed down to the action method, the security annotation would fail the request with status code 403. This is a typical scenario of web application authentication and authorization. For RESTFul service, user log in is handled differently, most of the times there is a security token in the request. For this example, I am just going to assume the HTTP request has a header entry which can be transformed into a user name and password pair. My authenticaition provider would be able to check the user credential just like the scenario I have described. My customized authentication provider class looks like the following: package org.hanbo.boot.rest.config.security; import java.util.ArrayList; import java.util.List; import import import import import import import

org.springframework.security.authentication.AuthenticationProvider; org.springframework.security.authentication.UsernamePasswordAuthenticationToken; org.springframework.security.core.Authentication; org.springframework.security.core.AuthenticationException; org.springframework.security.core.GrantedAuthority; org.springframework.security.core.authority.SimpleGrantedAuthority; org.springframework.stereotype.Service;

@Service public class AuthTokenSecurityProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

8/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

if (auth == null) { return null; } String name = auth.getName(); String password = ""; if (auth.getCredentials() != null) { password = auth.getCredentials().toString(); } if (name == null || name.length() == 0) { return null; } if (password == null || password.length() == 0) { return null; } Authentication retVal = null; List grantedAuths = new ArrayList(); if (name.equalsIgnoreCase("anonymous-user") && password.equalsIgnoreCase("anonymouspass")) { grantedAuths.clear(); retVal = new UsernamePasswordAuthenticationToken( "anonymous", "not-authenticated", grantedAuths ); } else if (name.equalsIgnoreCase("Elrick") && password.equalsIgnoreCase("123tset321")) { grantedAuths.clear(); grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); retVal = new UsernamePasswordAuthenticationToken( name, "UserAuthenticated", grantedAuths ); } System.out.println("Add Auth - User Name: " + retVal.getName()); System.out.println("Add Auth - Roles Count: " + (retVal.getAuthorities() != null? retVal.getAuthorities().size() : 0)); return retVal; } @Override public boolean supports(Class tokenClass) { return tokenClass.equals(UsernamePasswordAuthenticationToken.class); } } This class has a method called supports(...). This method basically checks whether the authentication token is the right type which this provider can handle. If it is, then it will use its other method authenticate(...) to attempt authenticate and authorize the user. The method authenticate(...) will check the user name and password, if the user name is "Elrick" and password "123tset321", then authentication token will be set for the user and its role will be "ROLE_USER". if the user name is "anonymoususer" and password is "anonymouspass", then an anonymous user authentication token will be created, and it has no user role. And for all other cases, the a null authentication token will be returned. In this case, the authentication process failed. It is handled in my security filter.

Security Filter Finally, the security filter which I have created for the sake of this tutorial. It looks like this:

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

9/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

package org.hanbo.boot.rest.config.security; import java.io.IOException; import import import import import import

javax.servlet.FilterChain; javax.servlet.ServletException; javax.servlet.ServletRequest; javax.servlet.ServletResponse; javax.servlet.http.HttpServletResponse; javax.servlet.http.HttpServletRequest;

import import import import import import import import import

org.apache.tomcat.util.codec.binary.Base64; org.hanbo.boot.rest.models.UserCredential; org.springframework.security.authentication.AuthenticationManager; org.springframework.security.authentication.UsernamePasswordAuthenticationToken; org.springframework.security.core.Authentication; org.springframework.security.core.context.SecurityContext; org.springframework.security.core.context.SecurityContextHolder; org.springframework.stereotype.Component; org.springframework.web.filter.GenericFilterBean;

import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; @Component public class RestAuthSecurityFilter extends GenericFilterBean { private AuthenticationManager authManager; public RestAuthSecurityFilter(AuthenticationManager authManager) { this.authManager = authManager; } @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { if (request != null) { String auth = ((HttpServletRequest)request).getHeader("testAuth"); System.out.println("Test Auth: " + auth); SecurityContext sc = SecurityContextHolder.getContext(); UsernamePasswordAuthenticationToken authReq = null; authReq = getAuthCredentialFrom(auth); Authentication authCool = authManager.authenticate(authReq); sc.setAuthentication(authCool); chain.doFilter(request, response); } else { ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Request."); } } catch (Exception ex) { throw new ServletException("Unknown exception in RestAuthSecurityFilter", ex); } } private UsernamePasswordAuthenticationToken getAuthCredentialFrom(String authHdrValue) throws JsonParseException, JsonMappingException, IOException { UsernamePasswordAuthenticationToken defUserToken = new UsernamePasswordAuthenticationToken("anonymous-user", "anonymouspass"); https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

10/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

if (authHdrValue == null || authHdrValue.length() == 0) { return defUserToken; } else { byte[] decodedBytes = Base64.decodeBase64(authHdrValue); String authCred = new String(decodedBytes); System.out.println(authCred); if (authCred != null && authCred.length() > 0) { ObjectMapper objMapper = new ObjectMapper(); UserCredential userCrede = objMapper.readValue(authCred, UserCredential.class); if (userCrede != null) { return new UsernamePasswordAuthenticationToken(userCrede.getUserName(), userCrede.getUserPassword()); } else { return defUserToken; } } else { return defUserToken; } } } } The class I have defined is called RestAuthSecurityFilter. It extends from GenericFilterBean, so that I can override the filter method called doFilter. The class is annotated with @Component so that if needed it can be injected. This is the reason why I need a bean getter in the class RestAppSecurityConfig. Well it is part of the reason. The other is that for this class I added a constructor that took an AuthenticationManager object as parameter. I really need the AuthenticationManager object. You will see why next. Here is the code for the doFilter(): @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { if (request != null) { String auth = ((HttpServletRequest)request).getHeader("testAuth"); System.out.println("Test Auth: " + auth); SecurityContext sc = SecurityContextHolder.getContext(); UsernamePasswordAuthenticationToken authReq = null; authReq = getAuthCredentialFrom(auth); Authentication authCool = authManager.authenticate(authReq); sc.setAuthentication(authCool); chain.doFilter(request, response); } else { ((HttpServletResponse)response).sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Request."); } } catch (Exception ex) { throw new ServletException("Unknown exception in RestAuthSecurityFilter", ex); } } https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

11/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

This method will first get the untransformed security token from the header of the requets. And it is output to the commandline console just for a sanity check. It is done by the following code: String auth = ((HttpServletRequest)request).getHeader("testAuth"); System.out.println("Test Auth: " + auth); Then, I use a helper method to autheticate the security token, which is done with the following code: authReq = getAuthCredentialFrom(auth); And the code for this helper method is as the following: private UsernamePasswordAuthenticationToken getAuthCredentialFrom(String authHdrValue) throws JsonParseException, JsonMappingException, IOException { UsernamePasswordAuthenticationToken defUserToken = new UsernamePasswordAuthenticationToken("anonymous-user", "anonymouspass"); if (authHdrValue == null || authHdrValue.length() == 0) { return defUserToken; } else { byte[] decodedBytes = Base64.decodeBase64(authHdrValue); String authCred = new String(decodedBytes); System.out.println(authCred); if (authCred != null && authCred.length() > 0) { ObjectMapper objMapper = new ObjectMapper(); UserCredential userCrede = objMapper.readValue(authCred, UserCredential.class); if (userCrede != null) { return new UsernamePasswordAuthenticationToken(userCrede.getUserName(), userCrede.getUserPassword()); } else { return defUserToken; } } else { return defUserToken; } } } The security token before the transformation is a JSON string encoded as a Base64 byte array. It has to be transformed from Base64 encoded byte array back to a string. Here is the code how it is done: byte[] decodedBytes = Base64.decodeBase64(authHdrValue); String authCred = new String(decodedBytes); System.out.println(authCred); Once we have a readable string of the security token, it would be converted from JSON to an actual Java object. Since Spring starter wab library supports RESTFul web service, it has to include some kind of JSON serializer/deserializer. That library is Jackson. This means we don't have to add another JSON serializer/deserializer library. It is already available. And the way I deserialize the string is as the following: ObjectMapper objMapper = new ObjectMapper(); UserCredential userCrede = objMapper.readValue(authCred, UserCredential.class); The UserCredential class is defined as the following: package org.hanbo.boot.rest.models; public class UserCredential { private String userName; https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

12/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

private String userPassword; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPassword() { return userPassword; } public void setUserPassword(String userPassword) { this.userPassword = userPassword; } } The helper method will convert the UserCredential object to a UsernamePasswordAuthenticationToken object and returned to the caller doFilter() method. Back in the doFilter() method, once it got a valid UsernamePasswordAuthenticationToken object, it is passed into the AuthenticationManager object for attempt auhentication and authorization. Which object actual does this authentication and authorization? The right answer would be my authentication provider. The helper method will attempt to return the default anonymous user with no role associated. And in the filter if the request is bad, the response is status code 400. And if there is any exception, the response would be 500 internal server error.

Secured RESTFul API Controller Now that the security filter and authentication provider is in place, it is time to secure the API controller. I have two controller classes, one is public accessible. That is, the action methods in this class is not annotated with @PreAuthorize, and they are accessible by un-authenticated users. The source code for this controller has been listed in previous sections. If you need a refresher, please go up and check it out. The class name is SampleController. To test this controller and action methods, you can use the following two URLs: http://localhost:8080/public/allCars/{year of manufacturing}: This can be done with a HTTP GET method. http://localhost:8080/public/findCar: This can be done with a HTTP POST method. The request body is a JSON object. And response will be an JSON string represent the found car object. The secured API controller uses annotation @PreAuthorize to filter out any request that does not have the correct authorization token. The source code for this controller looks like this: package org.hanbo.boot.rest.controllers; import import import import import import import import import

org.hanbo.boot.rest.models.CarRequestModel; org.hanbo.boot.rest.models.PurchaseTaxModel; org.springframework.http.ResponseEntity; org.springframework.security.access.prepost.PreAuthorize; org.springframework.web.bind.annotation.RequestBody; org.springframework.web.bind.annotation.RequestMapping; org.springframework.web.bind.annotation.RequestMethod; org.springframework.web.bind.annotation.ResponseBody; org.springframework.web.bind.annotation.RestController;

@RestController public class SampleSecureController { @PreAuthorize("hasRole('ROLE_USER')") @ResponseBody @RequestMapping(value="/secure/calculateTax.", method = RequestMethod.POST) public ResponseEntity calculateTax( @RequestBody CarRequestModel req) https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

13/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

{ PurchaseTaxModel retVal = new PurchaseTaxModel(); retVal.setYear(req.getYear()); retVal.setModel(req.getModel()); retVal.setManufacturer(req.getManufacturer()); retVal.setTaxAmount(4000); retVal.setTaxRate(0.03f); ResponseEntity resp = ResponseEntity.ok(retVal); return resp; } } As you can see in the class, the action method has a @PreAuthorize annotation. This annotation ensures that only the authenticated users with the role of "ROLE_USER" can access this action method. To test this controller and action methods, you can use the following URL: http://localhost:8080/secure/calculateTax. This method only accepts HTTP POST request. More information about how to testing this will be covered in the next section.

Test the RESTFul Web Service Testing this web service is pretty easy. It is all about HTTP requests and response, there is no need to do any page navigation. I use a tool called "Postman". Maybe you have heard of it. Anyways, before we test it, we have to build the whole projects. To build the project, go to the base directory of the project and run the following command: mvn clean install To test the application, in the base directory and run the following command: java -jar target\hanbo-boot-rest-1.0.1.jar The build and the execution will succeed. Let's start testing. Boot up the "Postman" application. When it starts, the screenshot looks like this:

Start with the first test scenario, run a requests to get all the cars in a specific manufacturing year. The supported years are: 2005, 2006, 2007, and 2008. Here are the settings for the request: Set the HTTP method to "GET". URL is set to: http://localhost:8080/public/allCars/2008 Click the button "Send" Here is a screenshot of the request:

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

14/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

As the web service is running, it will respond with the following: [ { "yearOfManufacturing": 2008, "model": "Civic", "maker": "Honda", "suggestedRetailPrice": 20100, "fullPrice": 21500, "rebateAmount": 750 }, { "yearOfManufacturing": 2008, "model": "Camery", "maker": "Toyota", "suggestedRetailPrice": 21100, "fullPrice": 22600, "rebateAmount": 708 } ] The screenshot of the response looks like the following:

The next test is the HTTP POST request of finding a car. The request URL is: http://localhost:8080/public/findCar The Postman setting for the HTTP POST request: The HTTP method should be "POST". The URL is: http://localhost:8080/public/findCar Select the tab "Body", and check he radio box "Raw". In the drop down, select "Application/JSON".

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

15/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

The request needs a JSON object, and it looks like this: { "year": 2007, "manufacturer": "Subaru", "model": "Outback" } Here is the screenshot of the request:

Click the button "Send". And the response returned will be: { "yearOfManufacturing": 2007, "model": "Outback", "maker": "Subaru", "suggestedRetailPrice": 19980, "fullPrice": 20890, "rebateAmount": 695 } Lastly, the test scenario of the secured RESTFul controller. For this scenario, we need a security header in the request. The full string of the request looks like this: { "userName": "Elrick", "userPassword": "123tset321" } To add a small twsit, I encode this as a Base64 string, whcih looks like this: eyAidXNlck5hbWUiOiAiRWxyaWNrIiwgInVzZXJQYXNzd29yZCI6ICIxMjN0c2V0MzIxIiB9 To setup Postman application for this scenario, here are the steps: The HTTP method should be "POST". The URL is: http://localhost:8080/secure/calculateTax Frist select the "Header" tab In the first available header entry, set the key as "testAuth", and the value to: eyAidXNlck5hbWUiOiAiRWxyaWNrIiwgInVzZXJQYXNzd29yZCI6ICIxMjN0c2V0MzIxIiB9. Select the tab "Body", set the same as the previous request: Check he radio box "Raw". In the drop down, select "Application/JSON". https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

16/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

Use the same JSON for the request body.

Click the button "Send" and you will get the following response: { "year": 2007, "manufacturer": "Subaru", "model": "Outback", "taxRate": 0.03, "taxAmount": 4000 } Just to make this a little more interesting, remove the header entry called "testAuth". And run the same request again, you will see error response instead of expected response: { "timestamp": "2018-11-19T03:43:12.349+0000", "status": 403, "error": "Forbidden", "message": "Forbidden", "path": "/secure/calculateTax" } Here is the screenshot of the response of a request with the empty "testAuth" header entry:

Points of Interest That is all for this tutorial. A summary of what has been covered in this tutorial: How to set up a RESTFul service using Spring Boot. https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

17/18

20/11/2018

RESTFul Service with Spring Boot and Filter Based Security - CodeProject

How to add security configuration to the Spring Boot application and configure to use customized filer and authentication provider. Again, I had a blast writing this. I hope you enjoy this one.

History 11/18/2018 - Initial Draft.

License This article, along with any associated source code and files, is licensed under The MIT License

About the Author Han Bo Sun Team Leader The Judge Group

No Biography provided

United States

Comments and Discussions Visit https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based to post and view comments on this article, or click here to get a print view with messages. Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile Web04-2016 | 2.8.181119.1 | Last Updated 20 Nov 2018

Article Copyright 2018 by Han Bo Sun Everything else Copyright © CodeProject, 1999-2018

https://www.codeproject.com/Articles/1267678/RESTFul-Service-with-Spring-Boot-and-Filter-Based?display=Print

18/18

Related Documents


More Documents from "thong nm"