Spring Boot - Interceptor: Exclude path only in GET request - java

I am working on a Java Spring project and we wrote an Interceptor for security. This class implements WebMvcConfigurer, and we override the addInterceptors method:
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor(aService, sService))
.excludePathPatterns("/", "/ping", "/resc")
}
This works nicely.
Now, the path "/resc" has a GET request but also a POST request. The point is the POST request must be intercepted and GET request to same path not.
Is there a way to achieve that?
Thanks

The InterceptorRegistration does not provide any methods for your purpose.
But I think there is a way to achive your behavior. You can autowire the ApplicationContext. And than do this inside your Interceptor:
try {
RequestMappingHandlerMapping req2HandlerMapping = (RequestMappingHandlerMapping)applicationContext.getBean("requestMappingHandlerMapping");
HandlerExecutionChain handlerExeChain = req2HandlerMapping.getHandler(request);
if (Objects.nonNull(handlerExeChain)) {
Method method = ((HandlerMethod) handlerExeChain.getHandler()).getMethod();
if (!method.isAnnotationPresent(GetMapping.class)) {
//Provide your Security Checks here.
}
} catch (Exception e) {
//provide some Error Code
}
If you also want to chekc a specific path. For example your "/resc" you can check that es well with an additional if.

Related

How to allow endpoint to be called if request has specific header token using spring security

I'm using spring boot to build a web application, sometimes I ddont want to spend to much time on permissioning and rule management I just want to be able to create a random string token and allow other services to use my service if they know this token...
all know all the danger and problems related to this approach but for prototyping integration this is what i need...
the question is how can i configure spring security to validate a specific token for a specific endpoint [note that i want different tokens for different endpoints, if the thing was only 1 single token for the whole application se solution would be a simple filter]
EXAMPLE:
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers(HttpMethod.POST, "/boards/").access("SOMETHING LIKE: httpRequest->headers->get('tokenHeader') == 'mytoken'")
.anyRequest().authenticated()
//......
}
I would like to achieve something like this....
The only way i'm able to do what i want now is by doing:
#Value("${controller.plan.authorization}")
private String AUTHORIZATION_TOKEN;
#GetMapping
public List<Plan> getPlans(#RequestHeader(HttpHeaders.AUTHORIZATION) authToken: String) {
if (AUTHORIZATION_TOKEN.equals(authToken))
return planService.findAll()
else
throw ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid authorization_token")
}
which is quite out of spring pattern and really annoying in my opinion
is there any way to achieve that using spring security?
So you have some common code which needs to be executed in the same way in multiple controllers and endpoints.
This makes interceptor a suitable solution.
Try the following
#Configuration
public class WebSecurityConfig implements WebMvcConfigurer
{
#Autowired
private MyInterceptor myInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("*/boards/*");
}
}
Then the functionality you have described should be something close to the following
#Component
public class MyInterceptor implements HandlerInterceptor {
#Value("${controller.plan.authorization}")
private String AUTHORIZATION_TOKEN;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
final String requestTokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (AUTHORIZATION_TOKEN.equals(requestTokenHeader)) {
//continue execution in spring flow to reach the relative controller
return true;
} else {
//break flow and return response.
response.getWriter().write("invalid authorization_token");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
}
}
Now your controller can break free of that boilerplate code.

How to check HTTP request header for certain endpoints in Spring Framework

I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.

Spring security not allowing access with simple matcher and permitAll

As far as Spring security is concerned, it is completely new to me. I found many sources online describing how to set up basic security and was able to get HTTPS REST calls to work with the following configuration on the server side:
#Configuration
#EnableWebSecurity
#EnableConfigurationProperties(SecurityAuthProperties.class)
public class ServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final SecurityAuthProperties properties;
#Autowired
public ServerSecurityConfiguration(SecurityAuthProperties properties) {
this.properties = properties;
}
#Override
public void configure(HttpSecurity http) throws Exception {
properties.getEndpoints().forEach((key, value) -> {
try {
for (HttpMethod method : value.getMethods()) {
http.authorizeRequests().antMatchers(method, value.getPath()).permitAll().and()
.httpBasic().and().csrf().disable();
}
} catch (Exception e) {
throw new SecurityConfigurationException(
"Problem encountered while setting up endpoint restrictions", e);
}
});
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Upon closer inspection, though, it looks as though some portion (not sure how much) is actually being disabled. Could this be why it allows access from a client?
When I modified the configuration to what follows below, I always get the response "Forbidden".
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/rst/**").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
It seems to me that this code would allow access to anything in the path /rst and under, yet the opposite seems to be true. What am I missing?
Note: Another thing I should mention is that there is currently no "user" authentication. The "client" is not web based, but is a separate Spring Boot service that has its own client-side security configuration.
Update:
Here is one of the controllers:
#RestController
#RequestMapping("/rst/missionPlanning")
public class MissionPlannerController {
#Autowired
private MissionPlanner service;
#Autowired
private ThreadPoolTaskExecutor executor;
#PostMapping(value = "/planMission", produces = MediaType.APPLICATION_JSON_VALUE)
public DeferredResult<ResponseEntity<GeneralResponse>> planMission() {
DeferredResult<ResponseEntity<GeneralResponse>> result = new DeferredResult<>(60000L);
executor.execute(new Runner(result));
return result;
}
private class Runner implements ITask {
private DeferredResult<ResponseEntity<GeneralResponse>> result;
public Runner(DeferredResult<ResponseEntity<GeneralResponse>> result) {
this.result = result;
}
#Override
public void executeTask() {
// Invoke service and set result.
result.setResult(ResponseEntity.ok(service.planMission()));
}
}
}
Update:
Interesting. I found an example from another SO post (Security configuration with Spring-boot) that seems to work. The only thing that's different is the disabling of CSRF.
I see that stands for Cross-Site Request Forgery, but I don't really understand what that is, whether I should have it enabled, and if I do, how do I get it to work then?
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/rst/**").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
There could be something wrong with how you've set up your controller. Does your controller that contains that path have #RequestMapping("/rst")?
It'd be helpful if you updated your post with what your controller looks like.
Edit:
It seems your issue was the type of request being made if you had to disble CSRF.
CSRF requires a token to be specified on all request methods that can cause a change (i.e. POST, PUT, DELETE, PATCH, but not GET).
The reason for this is that when you control the web page, it adds a layer of security where only you are allowed to make these API calls. Without the CSRF token specified in the request, a malicious user will not be able to make that request to your service since the CSRF token is impossible to guess.
You can read more about it here:
https://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html#csrf-include-csrf-token
And here: https://www.baeldung.com/spring-security-csrf

spring WebFilter overrides ExceptionHandler header

i'm using both a WebFilter and a WebExceptionHandler.
The WebFilter should add a new header only if the ExceptionHandler didn't set it.
However, the WebFilter is added to the ServerWebExchange before the ExceptionHandler is executed by the WebHttpHandler, so it cannot tell whether the ExceptionHandler was triggered or not.
#Component
#Order(-2)
class MyErrorWebExceptionHandler(g: MyErrorAttributes, applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer)
: AbstractErrorWebExceptionHandler(g, ResourceProperties(), applicationContext) {
init {
super.setMessageWriters(serverCodecConfigurer.writers)
super.setMessageReaders(serverCodecConfigurer.readers)
}
#Override
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(RequestPredicates.all(), HandlerFunction<ServerResponse> { renderErrorResponse(it) })
}
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x ->
x.set(c_ApplicationStatus, errorPropertiesMap[c_ApplicationStatus].toString())
}.build()
}
#Component
class ServerResponseHeaderWebFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
exchange.response.headers.set(c_ApplicationStatus, HttpStatus.OK.value().toString())
return chain.filter(exchange)
}
}
We can quickly model the execution order in this case, with something like:
WebFilter
|- setting the application status header to OK
|- calling chain.filter
|- finding the handler and calling it
|- in case of an error, the exception handler is called
|- after chain.filter
Once the filter chain is done with the exchange, the response has been committed and processed, so there's no way to change the response headers at that point. With this code sample, the exception handler, if executed, will override whatever header the web filter has set.
So technically, the answer to your question is that there's no way to modify the response once the handler chain has taken care of it. This is the expected behavior of Spring WebFlux, by design.
But it sounds like we need to take a step back and talk about what you're trying to achieve.
Are you trying to check whether your exception handler is being called at all while developing a feature?
Are you trying to adapt the error handling mechanism in Spring Boot in some way that's not possible right now?
If you'd like to talk about what you're trying to achieve, please ask another question so that SO users can benefit from this question.
eventually i found that the header set in the filter can be seen in the request.exchange() object. it must be removed there for the new header set to replace it.
private fun renderErrorResponse(request: ServerRequest): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(request, false)
request.exchange().response.headers.remove(c_ApplicationStatus)
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.headers { x -> x.set(c_ApplicationStatus, value) }
.build()
}

Micronaut security fails to "secure"

I have a simple Micronaut- based "hello world" service that has a simple security built in (for the sake of testing and illustrating the Micronaut security). The controller code in the service that implements the hello service is provided below:
#Controller("/hello")
public class HelloController
{
public HelloController()
{
// Might put some stuff in in the future
}
#Get("/")
#Produces(MediaType.TEXT_PLAIN)
public String index()
{
return("Hello to the World of Micronaut!!!");
}
}
In order to test the security mechanism, I have followed the Micronaut tutorial instructions and created a security service class:
#Singleton
public class SecurityService
{
public SecurityService()
{
// Might put in some stuff in the future
}
Flowable<Boolean> checkAuthorization(HttpRequest<?> theReq)
{
Flowable<Boolean> flow = Flowable.fromCallable(()->{
System.out.println("Security Engaged!");
return(false); <== The tutorial says return true
}).subscribeOn(Schedulers.io());
return(flow);
}
}
It should be noted that, in a departure from the tutorial, the flowable.fromCallable() lambda returns false. In the tutorial, it returns true. I had assumed that a security check would fail if a false is returned, and that a failure would cause the hello service to fail to respond.
According to the tutorials, in ordeer to begin using the Security object, it is necessary to have a filter. The filter I created is shown below:
#Filter("/**")
public class HelloFilter implements HttpServerFilter
{
private final SecurityService secService;
public HelloFilter(SecurityService aSec)
{
System.out.println("Filter Created!");
secService = aSec;
}
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain)
{
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.doOnNext(res->{
System.out.println("Responding!");
});
return(resp);
}
}
The problem occurs when I run the microservice and access the Helo world URL. (http://localhost:8080/hello) I cannot cause the access to the service to fail. The filter catches all requests, and the security object is engaged, but it does not seem to prevent access to the hello service. I do not know what it takes to make the access fail.
Can someone help on this matter? Thank you.
You need to change request in your filter when you no have access to resource or process request as usual. Your HelloFilter looks like this:
#Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> theReq, ServerFilterChain theChain) {
System.out.println("Filtering!");
Publisher<MutableHttpResponse<?>> resp = secService.checkAuthorization(theReq)
.switchMap((authResult) -> { // authResult - is you result from SecurityService
if (!authResult) {
return Publishers.just(HttpResponse.status(HttpStatus.FORBIDDEN)); // reject request
} else {
return theChain.proceed(theReq); // process request as usual
}
})
.doOnNext(res -> {
System.out.println("Responding!");
});
return (resp);
}
And in the last - micronaut has security module with SecurityFilter, you can use #Secured annotations or write access rules in configuration files more examples in the doc

Categories