I've a springboot/openapi application. No dependency on spring security.
When launching a POST request via swagger, the returned status is 403.
The request doesn't arrive in the controller class.
A Get request however does work and returns a status 200.
The following is configured
#Configuration
public class Config {
#Bean
ForwardedHeaderFilter forwardedHeaderFilter() {
return new ForwardedHeaderFilter();
}
}
}
application.yaml
server:
port: 50086
forward-headers-strategy: framework
use-forward-headers: true
What could be the cause of the status 403 ?
Controller
#CrossOrigin
#RestController
#RequestMapping("/ta")
public class TaController {
#Operation(summary = "Calculate")
#RequestMapping(value = "/calculateWithPrices", method = RequestMethod.POST)
public ResponseEntity<CaculationResponseDto> calculateWithPrices(#RequestBody CaculationWithPricesRequestDto caculationWithPricesRequestDto) {
// code ...
}
Try to add a SecurityConfig which inherits from WebSecurityConfigurerAdapter. Example is here.
With the method configure you can set the access to specific url-endpoints and allow the call on them.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider).eraseCredentials(false);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("**apiEndpoint**").authenticated()
.and().csrf().disable().headers().frameOptions().disable().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Deactivate authorization for whole application
// http.authorizeHttpRequests().antMatchers("/**").permitAll().and().csrf().disable();
}
}
Class CustomAuthenticationProvider:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ICustomerRepository customerRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String id = authentication.getName().toString();
String pin = authentication.getCredentials().toString();
try {
// Check if the customer passed in Username exists
CustomerDTO customer = customerRepository.findById(Long.parseLong(id)).orElseThrow();
} catch (Exception e) {
// TODO Auto-generated catch block
throw new BadCredentialsException(id);
}
Collection<? extends GrantedAuthority> authorities = Collections
.singleton(new SimpleGrantedAuthority("ROLE_CUSTOMER"));
return new UsernamePasswordAuthenticationToken(id, pin, authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I have created a Spring Restful Service and Spring MVC application.
Restful Service ::
Restful service returns an entity if its existing in DB. If it doesn't exist It returns a custom Exception information in ResponseEntity object.
It is working as expected tested using Postman.
#GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(#PathVariable Long itemId, #RequestHeader HttpHeaders httpHeaders) {
MyItem myItem = myitemService.validateMyItem(itemId);
ResponseEntity<MyItem> responseEntity = null;
if (myItem == null) {
throw new ItemNotFoundException("Item Not Found!!!!");
}
responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
return responseEntity;
}
If the requested Entity does not exist Restful Service returns below.
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.
If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.
Spring MVC application calls Restful Web Service using Rest template
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
This is expected behavior. Rest template throws exception when the http status is client error or server error and returns the response when http status is not error status.
You have to provide implementation to use your error handler, map the response to response entity and throw the exception.
Create new error exception class with ResponseEntity field.
public class ResponseEntityErrorException extends RuntimeException {
private ResponseEntity<ErrorResponse> errorResponse;
public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
this.errorResponse = errorResponse;
}
public ResponseEntity<ErrorResponse> getErrorResponse() {
return errorResponse;
}
}
Custom error handler which maps the error response back to ResponseEntity.
public class ResponseEntityErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
ExceptionResponse errorObject = errorMessageExtractor.extractData(response);
throw new ResponseEntityErrorException(ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(errorObject));
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
RestTemplate Configuration - You have to set RestTemplate's errorHandler to ResponseEntityErrorHandler.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
Calling Method
#Autowired restTemplate
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
Try using the #ResponseBody annotation on your Exceptionhandler. e.g:
public #ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
You should use Custom Exception Handler to fix your case. It looks like this
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public CustomResponseEntityExceptionHandler() {
super();
}
// 404
#ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {
BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
Constants.HttpStatusMsg.ERROR_NOT_FOUND);
logger.error(ex.getMessage());
return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
}
And your code should throw some exception, eg:
if (your_entity == null) {
throw new EntityNotFoundException("said something");
}
If you get this case in somewhere else again, you just throw exception like above. Your handler will take care the rest stuffs.
Hope this help.
I've started your application and works just fine.
Maven :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
The controller class is :
#Controller
public class ValidationController {
#GetMapping(value = "/validate/{itemId}")
public #ResponseBody ResponseEntity<MyItem> validateItem(#PathVariable Long itemId) {
if (itemId.equals(Long.valueOf(1))) {
throw new ItemNotFoundException();
}
return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
}
#ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
System.out.println("In CREEH::ItemNFE");
ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
return responseEntity;
}
}
and the test:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ValidationController.class, secure = false)
public class TestValidationController {
#Autowired
private MockMvc mockMvc;
#Test
public void testExpectNotFound() throws Exception {
mockMvc.perform(get("/validate/1"))
.andExpect(status().isNotFound());
}
#Test
public void testExpectFound() throws Exception {
mockMvc.perform(get("/validate/2"))
.andExpect(status().isOk());
}
}
Are you sure the url you are trying to use with RestTemplate is correct?
String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
Your get method is #GetMapping(value = "/validate/{itemId}"
If you don't have request mapping at the level of the controller the url should be:
http://localhost:8080/validate/1
Another difference is the missing #ResponseBody on your controller method.
I am using my spring boot REST API with the controller as follows:-
`
#Autowired
HazelcastInstance hazelcastinstance;
String username="VAKSDNDDODM#DLDM#DMOD#DI##*#EK";
#RestController
#RequestMapping(value = "/Acode/availabile/multiple/{Code}/")
public class MultiController {
#Resource(name = "AService")
protected AImpl AService;
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> readMulti(
#RequestHeader(value="Auth-Token") String token,
#RequestBody XRequest Request,
#PathVariable String Code) throws Exception {
if(token.equalsIgnoreCase(username))
{
Response response = AService.readMultiX(Request, Code);
}
else
{
response.setMessage("Unauthorized access!");
response.setStatus(340);
}
return new ResponseEntity<Response>(response, HttpStatus.OK);
}
}
`
I wish to do the header comparision before API control enters the controller such that declaring #RequestHeader is not required in controller.Is it possible to do this or do we have an implementation for that?
I have written a custom strong authorization server and libraries for integration called PowerAuth 2.0.
Currently, the developer who tries to secure the API call with it can use it as such:
#Controller
#RequestMapping(value = "/session")
public class AuthenticationController {
#Autowired
private PowerAuthAuthenticationProvider authenticationProvider;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(
#RequestHeader(value = "X-PowerAuth-Authorization", required = true) String signatureHeader,
HttpServletRequest servletRequest) throws Exception {
PowerAuthApiAuthentication apiAuthentication = authenticationProvider.validateRequestSignature(
servletRequest,
"/session/login",
signatureHeader
);
if (apiAuthentication != null && apiAuthentication.getUserId() != null) {
return "OK";
} else {
return "NOT OK";
}
}
}
I would like to simplify the work for the developer though, so that the code can look like this:
#Controller
#RequestMapping(value = "/session")
public class AuthenticationController {
#RequestMapping(value = "/login", method = RequestMethod.POST)
#PowerAuth(value = "/session/login")
public #ResponseBody String login(PowerAuthApiAuthentication apiAuthentication) throws Exception {
if (apiAuthentication != null && apiAuthentication.getUserId() != null) {
return "OK";
} else {
return "NOT OK";
}
}
}
Principle (probably?):
Remove the need for autowired authentication provider
Replace the signature verification call with a custom request filter
Bind the request filter to a custom annotation with parameters
Inject the resulting authentication object in a method parameter
Since I am not strong in Spring, could you please provide me a guidance on how to do this?
Here is my Rest Service Class
#Controller
#RequestMapping(value = "/system_users")
#Secured({ "ROLE_MANAGER", "ROLE_EDITOR" })
public class SystemUsers {
#Inject
SystemUserBo systemUserBo;
...
...
...
#RequestMapping(value = "/get_users", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
#ResponseBody
public RestResponse getUser(#RequestBody String userName) {
try {
String[] systemUser = systemUserBo.getRecord(userName);
return new RestResponse(systemUser);
} catch (Throwable t) {
return new RestResponse(RestResponse.ERR_UNKNOWN);
}
}
...
...
...
}
I secured other methods with ROLE_MANAGER, ROLE_EDITOR and their specific tags
and i want to give full access only that method.
Which annotation should i use?
Thanks for helping.