My spring webflux service exposes a health-check endpoint, which is called every few seconds. spring-security is configured, and currently each health-check call creates a new session, which fills the SessionStore quickly.
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/actuator/*").permitAll() // disable security for health-check
.anyExchange().authenticated()
...
.and().build();
}
logs:
2020-07-23 21:58:03.805 DEBUG 4722 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [b185e815-1] HTTP GET "/actuator/health"
2020-07-23 21:58:03.845 DEBUG 4722 --- [ctor-http-nio-3] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
Is it possible to configure spring-session or spring-security to not create sessions for specific paths?
Related
I have enabled CSRF on my spring cloud api gateway server.
I have angular as my GUI framework which calls the rest services through the api gateway.
I have used a custom filter to add the CSRF token to the response headers.
When the POST call is made I see that the formData is lost. So I always get 400 Bad request errors.
I disabled CSRF and the request goes through fine without any issues.
Is there something wrong?
Below is my spring cloud gateway configuration. Gateway is used only for routing the requests to other microservices, it does not have any controllers or rest endpoints.
#SpringBootApplication
public class GatewayApplication {
#Autowired
ProfileManager profileManager;
#PostConstruct
public void onInit() {
profileManager.printActiveProfiles();
}
public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
}
below is the filter code
#Component
public class CsrfHeaderFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Mono<CsrfToken> token = (Mono<CsrfToken>) exchange.getAttributes().get(CsrfToken.class.getName());
if (token != null) {
return token.flatMap(t -> chain.filter(exchange));
}
return chain.filter(exchange);
}
}
My POST rest endpoints are defined with
#RequestParam
below is the code from one of the rest service endpoints. It is an upstream service implemented using the traditional servlet springboot framework.
#RequestMapping(value = "terminate/{listName}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED)
#CrossOrigin
#Loggable (activityname = ActivityLogConstants.DESCRIPTOR_TERMINATE)
public Response terminate(#Context HttpServletRequest reqContext, #PathVariable String listName, #RequestParam(value = "rowData") String rowData)
throws ServiceException {....}
The formData is lost by the time the request reaches the upstream services.
Looks like the filter in spring cloud gateways is blocking formData
here is my netty configuration:
#Configuration
public class NettyConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
#Value("${server.max-initial-line-length:65536}")
private int maxInitialLingLength;
#Value("${server.max-http-header-size:65536}")
private int maxHttpHeaderSize;
public void customize(NettyReactiveWebServerFactory container) {
container.addServerCustomizers(
httpServer -> httpServer.httpRequestDecoder(
httpRequestDecoderSpec -> {
httpRequestDecoderSpec.maxHeaderSize(maxHttpHeaderSize);
httpRequestDecoderSpec.maxInitialLineLength(maxInitialLingLength);
return httpRequestDecoderSpec;
}
)
);
}
}
below is my application.yml
sample log:
2022-07-28 09:18:20.743 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] Received response (auto-read:false) : [X-Content-Type-Options=nosniff, X-XSS-Protection=1; mode=block, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Pragma=no-cache, Expires=0, Strict-Transport-Security=max-age=31536000 ; includeSubDomains, X-Frame-Options=DENY, X-Application-Context=application:18080, Date=Thu, 28 Jul 2022 03:48:20 GMT, Connection=close, content-length=0]
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_received])
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] reactor.netty.channel.FluxReceive : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] FluxReceive{pending=0, cancelled=false, inboundDone=false, inboundError=null}: subscribing inbound receiver
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.client.HttpClientOperations : [id:199cd714-5, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] Received last HTTP packet
2022-07-28 09:18:20.744 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Decreasing pending responses, now 0
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Last HTTP packet was sent, terminating the channel
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Completed 400 BAD_REQUEST
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb-11, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Last HTTP response frame
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] c.m.webgateway.handler.RequestLogger : Total time required to process /cms-service/webapi/terminate/descriptor request 60055
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [response_completed])
2022-07-28 09:18:20.745 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080] onStateChange(POST{uri=/cms-service/webapi/terminate/descriptor, connection=PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 - R:localhost/127.0.0.1:18080]}}, [disconnecting])
2022-07-28 09:18:20.752 DEBUG 26532 --- [ctor-http-nio-5] r.n.resources.PooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080] Channel closed, now: 0 active connections, 4 inactive connections and 0 pending acquire requests.
2022-07-28 09:18:20.752 DEBUG 26532 --- [ctor-http-nio-5] r.n.r.DefaultPooledConnectionProvider : [id:199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080] onStateChange(PooledConnection{channel=[id: 0x199cd714, L:/127.0.0.1:50342 ! R:localhost/127.0.0.1:18080]}, [disconnecting])
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] r.n.http.server.HttpServerOperations : [id:b0f975eb, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Increasing pending responses, now 1
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] reactor.netty.http.server.HttpServer : [id:b0f975eb-12, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] Handler is being applied: org.springframework.http.server.reactive.ReactorHttpHandlerAdapter#7c82616c
2022-07-28 09:18:23.805 DEBUG 26532 --- [ctor-http-nio-5] o.s.w.s.adapter.HttpWebHandlerAdapter : [b0f975eb-12, L:/0:0:0:0:0:0:0:1:10443 - R:/0:0:0:0:0:0:0:1:50337] HTTP GET "/cms-service/webapi/data/descriptor"
below is the link to the sample project.
https://github.com/manjosh1990/webgateway-issues
I tried to ignore FORM URL ENCODED requests and GET request, but it still does not work
private static final Set<HttpMethod> ALLOWED_METHODS = new HashSet<>(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS));
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll().and()
.csrf(csrf -> csrf
.requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()));
return http.build();
}
private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
exchange.getRequest().getHeaders().getContentType()) || !ALLOWED_METHODS.contains(exchange.getRequest().getMethod())
? ServerWebExchangeMatcher.MatchResult.match()
: ServerWebExchangeMatcher.MatchResult.notMatch();
}
Thanks for the minimal sample to reproduce the issue!
After some testing, I'm unable to come up with a workaround or fix for your configuration that allows a form post (URL-encoded) to pass through the gateway with CSRF protection enabled. My best guess is it has to do with how Spring Security is consuming the request body (which should be cached for subsequent filters to consume) vs how Spring Cloud Gateway is consuming the request body in order to proxy to the downstream service.
I tested this by disabling CSRF protection and adding the following filter:
#Component
public class TestWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.defer(() -> exchange.getFormData()
.doOnSuccess(System.out::println))
.then(chain.filter(exchange));
}
}
In my testing, this causes the request through the gateway to block for a long time before receiving:
{
"timestamp": "2022-08-10T19:13:54.265+00:00",
"status": 400,
"error": "Bad Request",
"path": "/cms-service/webapi/service/post/test"
}
Since this appears to be a bug in Spring Security, I'd recommend submitting a bug in Spring Security and we can work through it from there.
If you would like to work around the issue in the meantime, you can disable CSRF protection for these types of requests, as follows:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange((authorize) -> authorize
.anyExchange().authenticated()
)
.csrf((csrf) -> csrf
.requireCsrfProtectionMatcher(ignoringFormUrlEncodedContentType())
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
return http.build();
}
private ServerWebExchangeMatcher ignoringFormUrlEncodedContentType() {
return (exchange) -> !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(
exchange.getRequest().getHeaders().getContentType())
? ServerWebExchangeMatcher.MatchResult.match()
: ServerWebExchangeMatcher.MatchResult.notMatch();
}
}
Important: This is not ideal, because these requests won't be protected. However, this might make sense if these requests were never performed in a browser. In that case, it would make sense to have a separate authentication mechanism, such as requiring a bearer token instead of form login, etc. (as in the example above).
I have a Spring Boot application with two security configurations (two WebSecurityConfigurerAdapters), one for a REST API with "/api/**" endpoints, and one for a web front-end at all other endpoints. The security configuration is here on Github and here's some relevant parts of it:
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
jwtAuthenticationFilter.setPostOnly(true);
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
.and();
}
}
The JWTAuthenticationFilter is a custom subclass of UsernamePasswordAuthenticationFilter that processes sign-in attempts to the REST API (by HTTP POST with a JSON body to /api/login) and returns a JWT token in the "Authorization" header if successful.
So here's the issue: failed login attempts to /api/login (either with bad credentials or missing JSON body) are redirecting to the HTML login form /api/login. Non-authenticated requests to other "/api/**" endpoints result in a simple JSON response such as:
{
"timestamp": "2019-11-22T21:03:07.892+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/v1/agency"
}
{
"timestamp": "2019-11-22T21:04:46.663+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/v1/badlink"
}
Attempts to access other protected URLs (not starting with "/api/") by a non-authenticated user redirect to the login form /login, which is the desired behavior. But I don't want API calls to /api/login to redirect to that form!
How can I code the correct behavior for failed API logins? Is it a question of adding a new handler for that filter? Or maybe adding an exclusion to some behavior I've already defined?
More detail on the exception and handling:
I looked at the logs, and the exception being thrown for either bad credentials or malformed JSON is a subclass of org.springframework.security.authentication.AuthenticationException. The logs show, for example:
webapp_1 | 2019-11-25 15:30:16.048 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
(...stack trace...)
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#7f9648b6
webapp_1 | 2019-11-25 15:30:16.133 DEBUG 1 --- [nio-8080-exec-6] n.j.webapps.granite.home.HomeController : Accessing /login page.
When I access another URL, for example one that doesn't exist such as /api/x, it looks very different. It's pretty verbose but it looks like the server is trying to redirect to /error and is not finding that to be an authorized URL. Interestingly if I try this in a web browser I get the error formatted with my custom error page (error.html), but if I access it with Postman I just get a JSON message. A sample of the logs:
webapp_1 | 2019-11-25 16:07:22.157 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
webapp_1 |
webapp_1 | org.springframework.security.access.AccessDeniedException: Access is denied
...
webapp_1 | 2019-11-25 16:07:22.174 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
webapp_1 | 2019-11-25 16:07:22.175 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
...
webapp_1 | 2019-11-25 16:07:22.211 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/api/**'
...
webapp_1 | 2019-11-25 16:07:22.214 DEBUG 1 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy : /error reached end of additional filter chain; proceeding with original chain
webapp_1 | 2019-11-25 16:07:22.226 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
webapp_1 | 2019-11-25 16:07:22.230 DEBUG 1 --- [nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
webapp_1 | 2019-11-25 16:07:22.564 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
webapp_1 | 2019-11-25 16:07:22.577 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Mon Nov 25 16:07:22 GMT 2019, status=403, error=Forbidden, message=Access Denied, path=/a (truncated)...]
webapp_1 | 2019-11-25 16:07:22.903 DEBUG 1 --- [nio-8080-exec-6] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
webapp_1 | 2019-11-25 16:07:22.905 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 403
So it looks like what I maybe need to do is to configure the "authentication failure handler" for the REST API to go to "/error" instead of going to "/login", but only for endpoints under /api/**.
I added an #Override of unsuccessfulAuthentication() to my Authentication filter
The grandparent class (AbstractAuthenticationProcessingFilter) has a method for unsuccessful authentications (i.e. AuthenticationException) which delegates to an authentication failure handler class. I could have created my own custom authentication failure handler, but instead decided to simply override the unsuccessfulAuthentication method with some code that sends back a response with a 401 status and a JSON error message:
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"authentication error?\"}");
}
...and added a custom AuthenticationEntryPoint to make the other errors match
This doesn't have the exact form of the error messages I had seen at other endpoints, so I also created a custom AuthenticationEntryPoint (the class that handles unauthorized requests to protected endpoints) and it does basically the same thing. My implementation:
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"unauthorized?\"}");
}
}
Now the security configuration for the REST endpoints looks like this (note the addition of ".exceptionHandling().authenticationEntryPoint()":
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint())
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
I'll need to work on my error messages so they're more informative and secure.
And this doesn't exactly answer my original question, which was how to get those default-style JSON responses I liked, but it does allow me to customize the errors from all types of access failures independently of the web configuration. (Endpoints outside of /api/** still work with the web form login.)
Full code as of the current commit: github
I've managed to successfully use LDAP to retrieve users from an ActiveDirectory.
However, to do so I had to manually instantiate the BaseLdapPathContextSource like this:
Working Spring Security configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapProperties.getServer());
contextSource.setUserDn(ldapProperties.getUserDN());
contextSource.setPassword(ldapProperties.getPassword());
contextSource.setBase(ldapProperties.getRootDN());
contextSource.afterPropertiesSet();
// #formatter:off
auth.ldapAuthentication()
.contextSource(contextSource)
.userDnPatterns(new String[]{ ldapProperties.getUserOU() })
.userSearchBase("")
.userSearchFilter("(sAMAccountName={0})")
.ldapAuthoritiesPopulator((userData, username) ->
Collections.singleton(new SimpleGrantedAuthority("CLIENT"))
);
// #formatter:on
}
If instead of this I use the methods provided by the builder, then every login attempt gets rejected.
Not working Spring Security configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(ldapProperties.getServer())
.managerDn(ldapProperties.getUserDN())
.managerPassword(ldapProperties.getPassword())
.root(ldapProperties.getRootDN())
.and()
.userDnPatterns(new String[]{ ldapProperties.getUserOU() })
.userSearchBase("")
.userSearchFilter("(sAMAccountName={0})")
.ldapAuthoritiesPopulator((userData, username) ->
Collections.singleton(new SimpleGrantedAuthority("CLIENT"))
);
}
I've debugged the ContextSourceBuilder, and it is building a DefaultSpringSecurityContextSource too, similarly of how I am doing it.
The only difference between the manually built context and the one provided by the builder seems to be that objectPostProcessors are applied to it after beign built. However, most of them just autowire beans.
Log provided on a failed authentication (that should have succeeded):
2018-09-25 15:45:25.679 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.LdapAuthenticationProvider
2018-09-25 15:45:25.680 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.LdapAuthenticationProvider : Processing authentication request for user: mcurrao
2018-09-25 15:45:25.689 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.BindAuthenticator : Attempting to bind as ou=factory
2018-09-25 15:45:25.689 DEBUG 10784 --- [nio-8080-exec-3] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user ou=[my_ou]
2018-09-25 15:45:25.694 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.BindAuthenticator : Failed to bind as OU=[my_ou]: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090400, comment: AcceptSecurityContext error, data 52e, v1db1
Note: [my_ou] is actually being replaced as my own ou. But as I don't know how sensitive this is, better not risk it.
Note 2: This log is only provided by enabling Spring debug logging. No exception is thrown nor information is given on a login attempt if said debug is not enabled.
This error codes seem to be just "Couldn't find your user" errors.
TL;DR: LDAP authentication does not work if I use the provided contextSource() builder, forcing me to instantiate a context source.
I am running spring boot application with basic authentication enabled and spring boot admin with UI security enabled .
My spring boot client fails to register to the spring boot admin server . Below are the logs :
2018-08-23 15:17:09.676 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Created POST request for "http://localhost:9001/instances"
2018-08-23 15:17:09.699 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2018-08-23 15:17:09.724 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Writing [Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay)] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#12c0c0b3]
2018-08-23 15:17:09.864 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#5953ec3b8 pairs: {POST /instances HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{Authorization: Basic YWRtaW46YWRtaW4=}{User-Agent: Java/1.8.0_161}{Host: localhost:9001}{Connection: keep-alive}{Content-Length: 287}
2018-08-23 15:17:10.124 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#126c0bea10 pairs: {null: HTTP/1.1 403}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{X-Content-Type-Options: nosniff}{X-Frame-Options: DENY}{X-XSS-Protection: 1 ; mode=block}{Content-Type: text/plain}{Transfer-Encoding: chunked}{Date: Thu, 23 Aug 2018 09:47:10 GMT}
2018-08-23 15:17:10.128 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : POST request for "http://localhost:9001/instances" resulted in 403 (null); invoking error handler
2018-08-23 15:17:10.138 WARN 4992 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator : Failed to register application as Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay) at spring-boot-admin ([http://localhost:9001/instances]): 403 null. Further attempts are logged on DEBUG level
Below are my webSecurity config class from admin server
#SpringBootApplication
#EnableAdminServer
#EnableAutoConfiguration
#Configuration
public class SpringStarterAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringStarterAdminApplication.class, args);
}
// Added for spring boot security login ui for admin
public static class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
"/instances",
"/actuator/**"
);
// #formatter:on
}
}
Not sure what is preventing the client to register to admin Server .
The inner configuration class should be annotated with #Configuration too.
It's even better if you refactor it into a separate class.
We've migrated our software from spring boot 1.5.7 to spring boot 2.
We're using JSF by including joinfaces-parent in our pom.xml.
At the startup, all works perfectly, but login call does not work:
Request method 'POST' not supported
It is probably a Spring Security issue? CSRF is already disabled.
Here's our SecurityConfig file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) {
try {
http.csrf().disable().authorizeRequests()
.antMatchers("/javax.faces.resource/**", Page.LOGIN.getUrlForSecurityContext())
.permitAll()
.and()
........
// *** login configuration
.formLogin()
.loginPage(Page.LOGIN.getUrlForSecurityContext()).permitAll()
.failureUrl(Page.LOGIN.getUrlForSecurityContext() + "?error=true")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.and()
...........
// #formatter:on
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
.......
}
The login request does not arrives to our backend.
I found out that this error is generated from the dispatcher.forward function, called from xhtml. Here the function:
public void login() throws ServletException, IOException {
final ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
final RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/login");
dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
FacesContext.getCurrentInstance().responseComplete();
}
Here more logs when the error message happens:
[io.undertow.servlet] (default task-3) Initializing Spring FrameworkServlet 'dispatcherServlet'
16:02:20,926 INFO [org.springframework.web.servlet.DispatcherServlet] (default task-3) FrameworkServlet 'dispatcherServlet': initialization started
16:02:20,938 INFO [org.springframework.web.servlet.DispatcherServlet] (default task-3) FrameworkServlet 'dispatcherServlet': initialization completed in 12 ms
16:02:20,949 WARN [org.springframework.web.servlet.PageNotFound] (default task-3) Request method 'POST' not supported
16:02:20,973 ERROR [org.springframework.boot.web.servlet.support.ErrorPageFilter] (default task-3) Cannot forward to error page for request [/login] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
Thanks in advice!
Spring Security configuration looks ok for me. There is something wrong with your login controller. I suppose your login method is called in response to POST request from the client. Then it tries to forward this POST to render login page and finally throws an exception. Obviously it should be GET request instead of POST.