Spring Boot, testing controller with secret token, how to? - spring

In my microservice I have Spring boot controller which is receive also auth token from http request header and send it to another microservice for validation:
#Autowired
private AuthService authService;
#GetMapping(path = "/find", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
#Timed
#CrossOrigin
public ResponseEntity<?> find(
#RequestParam(name = "contactId", required = true) final Long contactId,
#RequestHeader(name = "secret-token", required = true) String token
) {
boolean checkTokenValidationFromAuthServer = authService.checkTokenValidationFromAuthServer(token);
if (!checkTokenValidationFromAuthServer) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("token is invalid");
}
return ResponseEntity.status(HttpStatus.OK).body("Your contact here!");
}
Auth Service must be like this
#Service
#Component
public class AuthService {
public boolean checkTokenValidationFromAuthServer(String token){
//make rest call to auth microservice
//validate token, return false, or true
return false;
}
}
Basic test always will fail because I from test I do not know secret token, I want to mock or prevent controller from validation:
#RunWith(SpringRunner.class)
#SpringBootTest
public class PoolApplicationTests {
#Autowired
private PoolController controller;
#Test
public void controllerInitializedCorrectly() {
assertThat(controller).isNotNull();
}
#Test
public void testCall() {
ResponseEntity<?> find = controller.find(1L, "token");
assertThat(find.getStatusCode()).isEqualTo(200);
}
}
I do not know what is the best strategy for this kind of cases.

Related

spring boot interceptor for specific api, should not be invoked for all the api's

2 api's were exposed in a spring boot controller class. I have a requirement to intercept only 1 api and SHOULD NOT intercept other api. Can someone assist how to do this?
Below is the code
public class HeaderValidationInterceptor extends HandlerInterceptorAdapter{
private static final Logger logger = Logger.getLogger(HeaderValidationInterceptor.class);
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
validateHeaderParam(request);
request.setAttribute("startTime", startTime);
return true;
}
}
Also I have a configuration class to add interceptor as below
Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
HeaderValidationInterceptor headerValidationInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(headerValidationInterceptor)
}
}
Controller class
#RestController
public class MyController {
#Autowired
private ICityService cityService;
#GetMapping(value = "/cities")
public List<City> getCities() {
List<City> cities = cityService.findAll();
return cities;
}
#GetMapping(value = "/cities/{cityId}")
public City getCityById(#PathVariable("cityId") String cityId) {
City city = cityService.findCityById(cityId);
return cities;
}
}
Inside your interceptor, you can check the request URI for the endpoint you want to intercept.
You can use a regular expression to match the URI. Following for /cities/{cityId} endpoint.
if (request.getRequestURI().matches("(\\/cities\\/[a-zA-Z0-9]+\\/?)")) {
validateHeaderParam(request);
request.setAttribute("startTime", startTime);
}
I'm not sure what is that want to do in your interceptor, but for your example you can do this inside your controller as well. Like this,
#GetMapping(value = "/cities/{cityId}")
public City getCityById(#PathVariable("cityId") String cityId, HttpServletRequest request) {
// Here you can use HttpServletRequest and do your validation
// validateHeaderParam(request);
// request.setAttribute("startTime", startTime);
City city = cityService.findCityById(cityId);
return cities;
}

Spring boot - Pass argument from interceptor to method in controller

For learning purposes, I have made a custom authentication system where I pass a token from the client to the server through the Authorization header.
In the server side, I'd like to know if it's possible to create in the interceptor, before the request reaches a method in the controller, an User object with the email from the token as a property, and then pass this user object to every request where I require it.
This what I'd like to get, as an example:
#RestController
public class HelloController {
#RequestMapping("/")
public String index(final User user) {
return user.getEmail();
}
}
public class User {
private String email;
}
Where user is an object that I created in the pre-interceptor using the request Authorization header and then I can pass, or not, to any method in the RestController.
Is this possible?
#Recommended solution
I would create a #Bean with #Scope request which would hold the user and then put the appropriate entity into that holder and then take from that holder inside the method.
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CurrentUser {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}
and then
#Component
public class MyInterceptor implements HandlerInterceptor {
private CurrentUser currentUser;
#Autowired
MyInterceptor(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.currentUser.setCurrentUser(new User("whatever"));
return true;
}
}
and in the Controller
#RestController
public class HelloController {
private CurrentUser currentUser;
#Autowired
HelloController(CurrentUser currentUser) {
this.currentUser = currentUser;
}
#RequestMapping("/")
public String index() {
return currentUser.getCurrentUser().getEmail();
}
}
#Alternative solution
In case your object that you would like to have, only contains one field, you can just cheat on that and add that field to the HttpServletRequest parameters and just see the magic happen.
#Component
public class MyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TRY ONE AT THE TIME: email OR user
//BOTH SHOULD WORK BUT SEPARATELY OF COURSE
request.setAttribute("email", "login#domain.com");
request.setAttribute("user", new User("login#domain.com"));
return true;
}
}
You can use a local thread context object as follows - which will be handling one parameter per request thread (thread safe):
public abstract class LoggedUserContext {
private static ThreadLocal<User> currentLoggedUser = new ThreadLocal<>();
public static void setCurrentLoggedUser(User loggedUser) {
if (currentLoggedUser == null) {
currentLoggedUser = new ThreadLocal<>();
}
currentLoggedUser.set(loggedUser);
}
public static User getCurrentLoggedUser() {
return currentLoggedUser != null ? currentLoggedUser.get() : null;
}
public static void clear() {
if (currentLoggedUser != null) {
currentLoggedUser.remove();
}
}
}
Then in the interceptor prehandle function:
LoggedUserContext.setCurrentLoggedUser(loggedUser);
And in the interceptor postHandler function:
LoggedUserContext.clear();
From any other place:
User loggedUser = LoggedUserContext.getCurrentLoggedUser();

Spring Boot, how to pass AuthenticationPrinciple to controller from a test?

I have a Spring Boot 2 web service. It accepts a Bearer token and uses Spring OAuth2 to go to an external URL and grab the users details using that token. I'm trying to create tests that test the implementation of the web service, but I'm struggling to figure out how to pass the AuthenticationPrinicple from a test?
Here is an example of a method in my controller;
#RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity listUserUploadedFiles(#AuthenticationPrincipal Client client) {
FileListResponse rsp = new FileListResponse(fileStorageService.getUserFiles(client.getUid()));
return rsp.response(HttpStatus.OK);
}
Here is my test so far;
#Test
public void TestSimple() throws Exception {
mockMvc.perform(get("/file"))
.andExpect(status().isOk());
}
Currently the test fails because the Client (AuthenticationPrinicple) is null.
How would I do this?
---- UPDATE ----
Here is my Client object, as you can see, it doesn't inherit UserDetails.
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
private String uid;
private String email;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
There is also a PrincipalExtractor bean setup.
#Bean
public PrincipalExtractor principalExtractor() {
return map -> {
Client client = new Client();
client.setUid(String.valueOf(map.get("uid")));
client.setEmail(String.valueOf(map.get("name")));
return client;
};
}
To provide a little more information on the project. This is a microservice, the user authenticates via a web application which takes place externally to this service. When calls to this server are made, a token is supplied which is verified with an authenticate server (again, external). The response from authentication server provides us with a uuid and an email field, the PrincipalExtractor maps these to a Client object, which is then passed into the controller to be used.
You could mock your user when testing #WithMockUser.
If you use MockMvc there is no extra sec-config needed
#RunWith(SpringRunner.class)
#WebMvcTest(SecuredController.class)
public class SecuredControllerWebMvcIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithMockUser(value = "user")
public void testSimple() throws Exception {
mockMvc.perform(get("/file"))
...
if you want to use #WithMockUser in a #SpringBootTest you need extra configuration
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerSpringBootIntegrationTest
...
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
...
ref spring-security-integration-tests

Post authorizing Spring asynchronous controller response

I have a REST controller with a GET method. It returns a resource. I want to verify if the resource belongs to the authorized user by comparing the owner field on the Resource with the authorized user's login. With a normal synchronous request I'd do something like this:
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/resources/{id}")
#PostAuthorize("returnObject.ownerLogin == authentication.name")
public Resource getResource(#PathVariable Long id) {
return aService.getResource(id);
}
}
But what if the controller method is asynchronous (implemented with DeferredResult)?
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/resources/{id}")
#PostAuthorize("returnObject.ownerLogin == authentication.name")
public DeferredResult<Resource> getResource(#PathVariable Long id) {
DeferredResult<Resource> deferredResult = new DeferredResult<>();
aService
.getResourceAsync(id)
.thenAccept(resource -> {
deferredResult.setResult(resource);
});
return deferredResult;
}
}
Where AService interface looks like this:
#Service
public class AService {
#Async
public CompletableFuture<Resource> getResourceAsync(Long id) {
// implementation...
}
public Resource getResource(Long id) {
// implementation...
}
}
And Resource class is a simple DTO:
public class Resource {
private String ownerLogin;
// other fields, getters, setters
}
In the second example Spring Security obiously looks for the ownerLogin field on the DeferredResult instance. I'd like it to treat the asynchronously resolved Resource as the returnObject in the #PostAuthorize SPEL expression.
Is it possible? Maybe someone can suggest an alternatve approach? Any suggestions are welcome.
Couldn't achieve my goal with PostAuthorize and endedd up doing the following:
Made Resource a subresource of the User resource. Used a PreAuthorize annotation to validate user's login.
#RestController
#RequestMapping("/api")
public class AController {
private final AService aService;
public AController(AService aService) {
this.aService = aService;
}
#GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}/resources/{id}")
#PreAuthorize("#login == authentication.name")
public DeferredResult<Resource> getResource(#PathVariable String login, #PathVariable Long id) {
DeferredResult<Resource> deferredResult = new DeferredResult<>();
aService
.getResourceAsync(login, id)
.thenAccept(resource -> {
deferredResult.setResult(resource);
});
return deferredResult;
}
}
Added an ownership check in AService. If Resource owner and the requesting user's login don't match throw an Exception that resolves to a 404 HTTP status:
#Service
public class AService {
private final ARepository aRepository;
public AController(ARepository aRepository) {
this.aRepository = aRepository;
}
#Async
public CompletableFuture<Resource> getResourceAsync(String owner, Long id) {
Resource resource = aRepository.getResource(id);
if (!resource.owner.equals(owner)) {
// resolves to 404 response code
throw ResourceNotFounException();
}
return resource;
}
}

TestRestTemplate postForEntity does not send the requestbody Spring Boot 1.4

#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ServerApplication.class)
public class ExampleTest {
public static final String EXAMPLE = "/example";
//this is the TestRestTemplate used
#Autowired
TestRestTemplate testRestTemplate;
//this is the test
#Test
public void testExample() {
String s = "asd";
Object response = testRestTemplate.postForEntity(EXAMPLE, s, String.class ,Collections.emptyMap());
}
}
This is the tested endpoint:
#RestController
public class ServerController {
#ResponseBody
#PostMapping("/example")
public String exampleEndpoint (String in) {
return in;
}
}
#SpringBootApplication
#ComponentScan("com.company.*")
public class ServerApplication {
etc.
When I debug it, at the exampleEndpoint the in parameter is always null.
I'm using Spring Boot 1.4 Spring 4.3.2
I changed the names and removed all the company stuff, but otherwise this is how it is, it works in the sense that it hits the endpoint, it just that the request doesn't go through.
You need to annotate the argument in your method exampleEndpoint with RequestBody like so:
public String exampleEndpoint (#RequestBody String in) {
return in;
}

Resources