Disable HTTP OPTIONS method in spring boot application - java

I had developed rest API on spring boot application. The APIs accept only GET , and POST , but on requesting using OPTIONS method , API responding 200 status (instead of 405). I googled this issue , but none of the solution was springboot based .
Response:
Allow: OPTIONS, TRACE, GET, HEAD, POST
Public: OPTIONS, TRACE, GET, HEAD, POST
Need to disable OPTIONS method.

Previous answer is for tomcat only, so adding mine as well. You can disable the method cross-container by, for example, using a standard servlet filter:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
#Component
public class MethodFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getMethod().equals("OPTIONS")) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
} else {
filterChain.doFilter(request, response);
}
}
}
Note: it is assumed that this class is componentscanned by Spring. If not, you can use other registration methods as detailed in here.

Try this; in allowedMethods you can filter methods which are required:
#Configuration
public class CorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(origins u want to allow)
.allowCredentials(false).allowedMethods("POST", "GET", "PUT");
}
};
}
}

I tried this and it worked.
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container.getClass().isAssignableFrom(TomcatEmbeddedServletContainerFactory.class)) {
TomcatEmbeddedServletContainerFactory tomcatContainer = (TomcatEmbeddedServletContainerFactory) container;
tomcatContainer.addContextCustomizers(new ContextSecurityCustomizer());
}
}
};
}
private static class ContextSecurityCustomizer implements TomcatContextCustomizer {
#Override
public void customize(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.setName("restricted_methods");
securityCollection.addPattern("/*");
securityCollection.addMethod(HttpMethod.OPTIONS.toString());
constraint.addCollection(securityCollection);
constraint.setAuthConstraint(true);
context.addConstraint(constraint);
}
}

If you are using spring security, you can use the method below:
#Bean
public HttpFirewall configureFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","OPTIONS"));
return strictHttpFirewall;
}

Related

HandlerInterceptor spring - exclude requests on management port

I would like my HandlerInterceptor, in my spring boot application, not to run on requests coming in on the management port.
management.port = 9091
I can think of 2 ways to do it, but i'm looking for a more standard way.
One will be to check the port from within the HandlerInterceptor:
#Override
public boolean preHandle(#NonNull HttpServletRequest request, #NonNull HttpServletResponse response, #NonNull Object handler) {
if(request.getLocalPort() == managementPort){
return true;
}
else{
....
}
}
The second will be to exclude all paths, related to the managementPort when registering the interceptor:
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).excludePathPatterns(managementPortEndpoints);
}
This can easily get out of sync.
Is there a more native way to achieve it? (spring configuration of some sort)
I choose javax.servlet.Filter for filtering requests.
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
public class BlockFilter implements Filter {
private int managementPort = 9091;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request.getRemotePort() == managementPort) {
chain.doFilter(request, response);
} else {
((HttpServletResponse) response).setStatus(401);
}
}
}
Once all accesses to management.port are requests made for URLs that start with /actuator - spring will not run the filters and interceptors on the requests.
So, that seems like the way to go about it

Set SameSite attribute of JSESSIONID cookie - JHipster application

I'm trying to set the SameSite attribute of the JSESSIONID cookie in our JHipster gateway, and upon trying to verify in Chrome, there is nothing showing up under the SameSite column for it.
Possibly of note: we're currently not deployed and running the application locally on HTTP (a localhost address). Running in TLS mode also has the same problem, however.
These are two things I've tried in order to get this working:
The second approach from the first answer here How to enable samesite for jsessionid cookie - a filter that is used in JHipster's SecurityConfiguration.java file in the configure() method.
import java.io.IOException;
import java.util.Collection;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
public class SameSiteFilter implements javax.servlet.Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
addSameSiteCookieAttribute((HttpServletResponse) response); // add SameSite=strict cookie attribute
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
}
}
#Override
public void destroy() {
}
}
A CookieSerializer which we got from an internal partner:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.session.web.http.CookieSerializer;
#Configuration
class CookieConfiguration {
#Bean
public static CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("Lax");
return serializer;
}
}
Neither of these work. Is there something else we can try for this particular flavor of Spring?
In case you are using Tomcat (i.e. not WebFlux), the following configuration will add SameSite=strict to all cookies, including JSESSIONID:
#Configuration
public class SameSiteCookieConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer configureSameSiteCookies() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies("strict");
context.setCookieProcessor(cookieProcessor);
};
}
}

Spring boot and Jersey, can't make ExceptionMapper work

I am using Spring boot + Spring Security + Jersey.
I am trying to do something always that an Unathorized error happens with an ExceptionMapper, but it doesn't seem to work. However, other Mappers that I've done work perfectly.
This is my code:
Unauthorized Excepcion:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_UNATHORIZED;
public class NotAuthorizedMapper implements ExceptionMapper<NotAuthorizedException> {
#Override
public Response toResponse(NotAuthorizedException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_UNATHORIZED)
.build();
return Response.status(Response.Status.UNAUTHORIZED)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(errorDTO)
.build();
}
}
Other custom mapper that works perfectly:
package com.ulises.usersserver.rest.exceptionsmappers;
import com.ulises.usersserver.rest.dto.ErrorDTO;
import com.ulises.usersserver.rest.dto.ErrorDTOBuilder;
import com.ulises.usersserver.services.exceptions.UserNotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import static com.ulises.usersserver.constants.Constants.REQUEST_ERROR_USER_DOESNT_EXIST;
public class UserNotFoundExceptionMapper implements ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException e) {
final ErrorDTO errorDTO = ErrorDTOBuilder.builder()
.message(REQUEST_ERROR_USER_DOESNT_EXIST)
.build();
return Response.status(Response.Status.NOT_FOUND).entity(errorDTO).build();
}
}
They are of course registered in Jersey's config:
#Bean
public ResourceConfig jerseyConfig() {
final ResourceConfig resourceConfig = new ResourceConfig();
...
resourceConfig.register(NotFoundMapper.class);
resourceConfig.register(NotAuthorizedMapper.class);
...
return resourceConfig;
}
I don't seem to be able to map other exceptions such as InternalServerErrorException (I managed to map this one by doing
ExceptionMapper<Exception>, which doesn't look very correct to me.
Anyone knows why is this happening? I've checked all possible questions here and none of them solved this.
Thanks in advance.
Okay, it seems Jersey has no control over Spring Security's exceptions.
The way to solve this (had to dig alot) is to override the method from AuthenticationEntryPoint that S-Security uses to return the 401 response if an user isn't authorized.
So I created the following class implementing that interface:
public class CustomEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException) throws IOException {
JSONObject json = new JSONObject();
json.put("Message", "You don't have access to view this section. Please, log in with an authorized account.");
response.addHeader("Content-Type", "application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().println(json);
}
}
And then just add this configuration to S-Security to use this class as entry point:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(new CustomEntryPoint());
..... any other config you had .....
}

Spring Security CORS filter not working

I'm using spring security with OAuth2 (version: 4.0.4.RELEASE) and spring (verison: 4.3.1.RELEASE).
I'm developing frontend in Angular and I'm using grunt connect:dev (http://127.0.0.1:9000). When I trying to login by localhost address everything working fine but from other I'm getting error:
"XMLHttpRequest cannot load http://localhost:8084/oauth/token?client_id=MY_CLIENT_ID. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:9000' is therefore not allowed access. The response had HTTP status code 401."
I have configured mapping (Overrided public void addCorsMappings(CorsRegistry registry)) in WebMvcConfigurerAdapter (like below) but it still not working for http://127.0.0.1:9000.
registry.addMapping("/**")
.allowedOrigins("http://127.0.0.1:9000")
.allowedMethods("POST", "OPTIONS", "GET", "DELETE", "PUT")
.allowedHeaders("X-Requested-With,Origin,Content-Type,Accept,Authorization")
.allowCredentials(true).maxAge(3600);
Configuration based on: https://spring.io/guides/gs/rest-service-cors/
Please, point me the right directon to resolve this issue.
Hopefully, you found an answer long ago, but if not (and if anyone else finds this question searching as I was):
The issue is that Spring Security operates using filters and those filters generally have precedence over user defined filters, #CrossOrigin and similar annotations, etc.
What worked for me was to define the CORS filter as a bean with highest precedence, as suggested here.
#Configuration
public class MyConfiguration {
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://127.0.0.1:9000");
config.setAllowedMethods(Arrays.asList("POST", "OPTIONS", "GET", "DELETE", "PUT"));
config.setAllowedHeaders(Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept", "Authorization"));
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Sorry for long time response. I resolved the issue by configuring my CORS filter like below:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
private static final Logger LOGGER = LogManager.getLogger(CORSFilter.class.getName());
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String origin = ((HttpServletRequest) req).getHeader("Origin");
if (ofNullable(origin).isPresent() && origin.equals("http://127.0.0.1:9000")) {
LOGGER.info("CORSFilter run");
response.addHeader("Access-Control-Allow-Origin", "http://127.0.0.1:9000");
response.addHeader("Access-Control-Allow-Credentials", "true");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.addHeader("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept,Authorization");
response.setStatus(200);
}
}
chain.doFilter(addNessesaryHeaders(request), response);
}
private MutableHttpServletRequest addNessesaryHeaders(final HttpServletRequest request) {
final MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request);
mutableRequest.putHeader("Accept", "application/json");
mutableRequest.putHeader("Authorization", "Basic" + " bXVzaWNzY2hvb2w6");
return mutableRequest;
}
#Override
public void destroy() {
}
}
You can try something like that
#Configuration
public class CorsConfig {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods(HttpMethod.OPTIONS.name(),
HttpMethod.PATCH.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.GET.name(),
HttpMethod.POST.name())
.maxAge(360);
}
};
}
}
Note: Spring version should be 4.2 or later
below worked for me.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}

Spring security custom authentication filter without web.xml

Using annotation and java configuration it is not quite clear to me how to register an overridden filter for spring security.
What I want to achieve is to do an auto login without showing a Login form since at that time the user will already be authenticated. Therefore will only be reading a header param and use spring security for authorization purposes.
This is a simplified version of what I'm trying to, and the Spring security works correctly, except for sometimes showing the login screen.
Bootstrapping the BypassLoginFilter is all I need to get this going. Also read somewhere that http auto config should be off for this kind of behaviour, but not sure how to implement in pure java configuration.
SecurityWebApplicationInitializer.java
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{
}
SecurityConfig .java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/*").permitAll()
.anyRequest().hasRole("USER").and()
.formLogin()
.permitAll();
http.addFilterBefore(new BypassLoginFilter(), LogoutFilter.class);
//.and().anonymous().disable();
}
#Override
#Autowired
protected void registerAuthentication(AuthenticationManagerBuilder auth) {
try {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER").and().withUser("admin").password("password")
.roles("USER", "ADMIN");
} catch (Exception e) {
e.printStackTrace();
}
}
}
BypassLoginFilter.java
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class BypassLoginFilter extends AbstractAuthenticationProcessingFilter{
private static String HEADER_IS_ADMIN = "isAdmin";
public BypassLoginFilter()
{
super("/*");
}
//Never gets executed
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
boolean isAdmin = Boolean.valueOf(request.getHeader(HEADER_IS_ADMIN));
PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken("","",getAuthorities(isAdmin));
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return getAuthenticationManager().authenticate(authRequest);
}
private List<GrantedAuthority> getAuthorities(boolean isAdmin)
{
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
if(isAdmin){
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
return authorities;
}
}
You can try following approach. Let's say you have a YourUser class which looks like that:
public class YourUser extends org.springframework.security.core.userdetails.User{
...
public String getStartPage(){ return "/userhomepage"; }
...
}
Then you need to declare authentication handler:
#Component
public class YourAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof YourUser) {
final YourUser user = (YourUser) authentication.getPrincipal();
return user.getStartPage();
}else {
return "/defaultPageForNonAuthenticatedUsers";
}
}
}
And use it in the security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// rest calls are ommited
http.successHandler(successHandler());
}
#Bean
public AuthenticationSuccessHandler successHandler() throws Exception {
return new YourAuthenticationSuccessHandler();
}
}

Categories

Resources