Spring Boot authentication not available from error page - spring-boot

Depending on whether the user is logged in, I want to display an element on an error page e.g.
<div sec:authorize="isAuthenticated()">
Logged in
</div>
The problem is, however is that the current Authentication Principal is being cleared for some reason. I thought it had something to do with the No Auth for You bug, so I added
security.filter-dispatcher-types: ASYNC, FORWARD, INCLUDE, REQUEST, ERROR
However still the problem persists. I loaded up both projects and debug-logged spring security. With the No Auth For You project I'm getting:
SecurityContextHolder now cleared, as request processing completed
Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#76d66409
Processing ErrorPage[errorCode=0, location=/error]
/error at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
/error at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImp.....
Which gives the correct security context.
With my project I'm getting
SecurityContextHolder now cleared, as request processing completed
Cleared thread-bound request context: org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper#380b96d
Processing ErrorPage[errorCode=0, location=/error]
/error at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
/error at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
No HttpSession currently exists
No SecurityContext was available from the HttpSession: null. A new one will be created.
So the issue seems to be "No HttpSession currently exists". I am using Redis Sessions, if that makes a difference. However I don't know really where to go from here, when it comes to debugging.
I am using the GlobalDefaultException handler code from the Spring blog, if that makes any difference. However I actually ported this handler to the No Auth For You project, so I don't believe it is that.
If I leave this error page and view any other page, then I am still logged in.
Update
I removed Redis and it could find the session. Maybe it is something to do with Tomcat and Redis?
Update 2018
I have looked at this again, and I think it has something to do with the GlobalExceptionHandler (as mentioned before taken from Spring's blog).
"If you want to have a default handler for any exception, there is a slight wrinkle. You need to ensure annotated exceptions are handled by the framework."
#ControllerAdvice
public class GlobalExceptionHandler
{
#ExceptionHandler({Exception.class})
#ResponseStatus(value = INTERNAL_SERVER_ERROR)
public String exception(
HttpServletRequest request,
Exception e,
Model model
) throws Exception
{
// other logging things here
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
{
// template that renders this can't access the SecurityContext
throw e;
}
// this template can access the SecurityContext
return "error";
}
}
The caught exception is able to access the SecurityContext, however the re-thrown exceptions (handled by Spring) can't access the SecurityContext.
Remember that both templates can access the SecurityContext, when Redis is not being used. I looked at the documentation for Spring Session and it says
"...[t]his creates a Spring Bean with the name of springSessionRepositoryFilter that implements Filter. The filter is what is in charge of replacing the HttpSession implementation to be backed by Spring Session."
So maybe this bean isn't being used for some reason?
Under Spring Boot 2.0.0.M7 it works, so I wonder what was changed
DEBUG 3767 --- [0.1-8080-exec-1] o.s.security.web.FilterChainProxy : /errorTest/notFound at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG 3767 --- [0.1-8080-exec-1] o.s.d.redis.core.RedisConnectionUtils : Opening RedisConnection
DEBUG 3767 --- [0.1-8080-exec-1] io.lettuce.core.RedisChannelHandler : dispatching command AsyncCommand [type=HGETALL, output=MapOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
[SNIP REDIS]
DEBUG 3767 --- [0.1-8080-exec-1] o.s.d.redis.core.RedisConnectionUtils : Closing Redis Connection
DEBUG 3767 --- [0.1-8080-exec-1] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl#936f4f11: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken
I think it is related to the following issue:
https://github.com/spring-projects/spring-boot/issues/5737
I think it makes sense to change SecurityFilterAutoConfiguration to use the ERROR dispatch type by default. The ERROR dispatch type is also the default of Spring Security's Servlet Java Config.
The reason the ERROR dispatch type makes sense for Spring Security is many times navigation is based on the permissions of the current user. Without Spring Security being populated, the error page may not have
meaningful navigation.

Related

Grails + SpringSecurity: Unable to use both OAuth2 and BasicAuth on the same endpoint

Using:
grails 4.1.2
spring-security-web 5.2.8
spring-security-oauth2 2.0.18
spring-security-core 4.0.0.M1
I have rest endpoints that allow both oauth2 and basicAuth:
grails.plugin.springsecurity.filterChain.chainMap = [
[ pattern: '/rest/**', filters: 'JOINED_FILTERS,-basicAuthenticationFilter,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter,-basicExceptionTranslationFilter',]
]
Logging in with oauth2 works without problems, but when I log in with basicAuth, I end up with an "anonymous" user authenticated by "GrailsAnonymousAuthenticationFilter".
I debugged the problem and noticed the following:
BasicAuth is performed 2 times: Once in the filter springSecurityFilterChain (pattern=/*) and once in the filter filterChainProxy (/rest/**)
Both filters contain the same filter chain for the given endpoint (when debugging I see that springSecurityFilterChain delegates to filterChainProxy). The order of relevant elements is OAuth2AuthenticationProcessingFilter -> BasicAuthenticationFilter -> GrailsAnonymousAuthenticationFilter
The first BasicAuth attempt works, however the second doesn't (see more below)
There are 2 problems that cause the "anonymous" user issue:
When filterChainProxy runs after springSecurityFilterChain, the OAuth2AuthenticationProcessingFilter removes the authentication from the SecurityContextHolder
BasicAuthenticationFilter implements OncePerRequest, so when it is run the second time in springSecurityFilterChain it will not authenticate the user (again)
My questions:
Can I get rid of one of the filters to fix the anonymous login issue?
Why does BasicAuth implement OnlyOncePerRequest? Is this a security measure?
Why does OAuth2 clear the SecurityContext? Is this (also) a security measure?

spring-boot request to context-path/ generates 405 Method Not Allowed error for static content

I have a spring boot application with context-path /services. The context-path entry in property file:
server.servlet.context-path=/services
When I use the URL http://localhost:8080/services/ with GET method, it is working fine and returning the static index.html file in response. But when I try to use the same URL http://localhost:8080/services/ with POST method, I am getting the following error:
{
"timestamp": "2020-11-11T07:06:37.341+00:00",
"status": 405,
"error": "Method Not Allowed",
"message": "",
"path": "/services/"
}
I have tried using redirect-context-root property, but that did not help. I also tried server.servlet.context-path=/services/ - but this also did not help. What property or configuration will force spring framework to allow POST method for any request with context-path with a trailing slash (/) ?
From the logs, I can see the view is getting resolved to [view="forward:index.html"], but right after that we see the ERROR HttpRequestMethodNotSupportedException: Request method 'POST' not supported. So not able to understand which spring class is responsible for throwing this error? And how can we suppress this behavior?
o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/rest/**']
o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/'; against '/rest/**'
o.s.s.web.util.matcher.OrRequestMatcher : No matches found
o.s.web.servlet.DispatcherServlet : POST "/services/", parameters={}
pertySourcedRequestMappingHandlerMapping : looking up handler for path: /
o.s.b.a.w.s.WelcomePageHandlerMapping : Mapped to ParameterizableViewController [view="forward:index.html"]
o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#363fa5d2
w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
o.s.web.servlet.DispatcherServlet : Completed 405 METHOD_NOT_ALLOWED
There is a spring issue reported: https://github.com/spring-projects/spring-framework/issues/22140. But spring also made it clear that they will not make any such change, since request to static resources should not be of type POST, as we are not trying to modify them, rather we are trying to GET them.
I was looking for a workaround, since I had to support POST at the context-root (because of SSO-IDP requirement). So I tried to replicate the 302 Redirect behavior in a Filter. In one of the filters, I checked for the request method POST, request URI /services/ and redirected the request to /services/. This converts the POST request into GET at path /services/ and it works.
// Inside doFilter() method of a Filter
if ("POST".equalsIgnoreCase(httpRequest.getMethod()) && "/services/".equalsIgnoreCase(httpRequest.getRequestURI())) {
httpResponse.sendRedirect("/services/"); // This converts the POST request to GET request
return;
}
After introducing this behavior in filter, when I POST a request to http://localhost:8080/services/, from the above filter, it gets redirected to http://localhost:8080/services/ using GET method and I get the desired index.html as response.

Validation error in controller returns 401 instead of 400

I have a post endpoint in my rest controller which is accessible without authentication/authorization. it is configured in WebSecurityConfigurerAdapter like this:
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/api/v1/user");
}
The endpoint has a validation of an input data (provided by annotation javax.validation.Valid). When I send invalid data, I receive 401 response instead of 400. This problem doesn't exist in secured endpoints, where the default spring boot 400 message is sent.
During debugging, I discovered that during handling the MethodArgumentNotValidException (which is thrown when the validation error occurs in the controller), the WebExpressionVoter is executed and returns value -1, which means 'access denied'. How can I configure my application to ignore security checks for endpoints, which are public?
I know that using ControllerAdvice for exception handling is one of the option, but is it possible to have default 400 messages?
Thanks in advance for any hints!
EDIT:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
Ok, I found the soulution. I don't why, but when an exception occurs, there is another POST request to this url: /error. When I added this url to my ignoring list then it started to work. So my configuration looks like this:
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/api/v1/user", "/error/**")
}
#Ken Chan thank you for the information about debuging filters! It was crucial.
Using webSecurity.ignoring() will cause the request to that URL bypass the whole security filter chain totally . So it is interesting that WebExpressionVoter would still kick in for these ignored URLs.
So , I am guessing the following possibilities :
/api/v1/user will be redirected to other URL which is secured.
You are thinking antMatchers("/api/v1/user") will match all URLs under /api/v1/user such as /api/v1/user/1 . However, it only matches /api/v1/user .
To ignore all URLs under /api/v1/user , you should use :
web.ignoring().antMatchers("/api/v1/user/**");

Spring Security Sequence of execution

I am not able to find out where and when exactly the authentication manager is executed by spring security. I mean there are certian filters which are executed sequentially as below:
FIRST
- CHANNEL_FILTER
- CONCURRENT_SESSION_FILTER
- SECURITY_CONTEXT_FILTER
- LOGOUT_FILTER
- X509_FILTER
- PRE_AUTH_FILTER
- CAS_FILTER
- FORM_LOGIN_FILTER
- OPENID_FILTER
- BASIC_AUTH_FILTER
- SERVLET_API_SUPPORT_FILTER
- REMEMBER_ME_FILTER
- ANONYMOUS_FILTER
- EXCEPTION_TRANSLATION_FILTER
- SESSION_MANAGEMENT_FILTER
- FILTER_SECURITY_INTERCEPTOR
- SWITCH_USER_FILTER
- LAST
But when exactly authentication provider authenticates the provided username and password, i mean to ask after which these below filters is the authentication provider is executed .
Regards
Jayendra
From Spring Security documentation:
The order that filters are defined in the chain is very important.
Irrespective of which filters you are actually using, the order should
be as follows:
ChannelProcessingFilter, because it might need to redirect to a different protocol
SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and
any changes to the SecurityContext can be copied to the HttpSession
when the web request ends (ready for use with the next web request)
ConcurrentSessionFilter, because it uses the SecurityContextHolder functionality but needs to update the SessionRegistry to reflect
ongoing requests from the principal
Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter,
BasicAuthenticationFilter etc - so that the SecurityContextHolder can
be modified to contain a valid Authentication request token
The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your
servlet container
RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder,
and the request presents a cookie that enables remember-me services to
take place, a suitable remembered Authentication object will be put
there
AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder,
an anonymous Authentication object will be put there
ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an
appropriate AuthenticationEntryPoint can be launched
FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
So the authentication manager is called at step 4. If you look at the source code of UsernamePasswordAuthenticationFilter you will see something like:
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// ...
return this.getAuthenticationManager().authenticate(authRequest);
}

An Authentication object was not found in the SecurityContext

I have an application exporting web services, with a configured Spring Security SecurityFilterChain (with SecurityContextPersistenceFilter among others, which is required for the rest).
My application also uses Spring Security to secure method invocations.
I have following error when method security is triggered:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
The 2nd part requires an Authentication in SecurityContextHolder as showed in org.springframework.security.access.intercept.AbstractSecurityInterceptor (line 195):
SecurityContextHolder.getContext().getAuthentication();
But, SecurityContextPersistenceFilter removes it before method invocation is triggered, as shown in
org.springframework.security.web.context.SecurityContextPersistenceFilter (line 84)
SecurityContextHolder.clearContext();
What can I do to have this object in SecurityContextHolder when method invocation is triggered?
Thank you in advance.
I'm using Spring Security 3.0.8-RELEASE
SecurityContextHolder.clearContext() will be called only after request processing completion. So normally all your application logic code will be executed before this line, and there is no problem at all. But the problem may be present if you execute some new thread in your code (by default security context will be not propogated). If this is your case then you can try to force context propogation to child thread. If you use only one thread then make sure that all your code is covered by spring security filter chain (may be you have some custom filter that executed around spring security filter chain?).
OK, my application is placed over Apache CXF DOSGi 1.4 to generate REST endpoints. Apache CXF interceptors cause an unexpected behaviour and SecurityContextHolder.clearContext() is called before finishing the request processing.
More information about this bug can be found here.

Resources