Expected Behavior
I have a custom filter (doing things like user validation) expected to be put after both BearerTokenAuthenticationFilter (Used for OAuth2 ResourceServer) and OAuth2LoginAuthenticationFilter (Used for OAuth2 client).
I'm expecting to have a new addFilterAfter to accept multiple reference filters instead of one. For example:
public HttpSecurity addFilterAfter(Filter filter, Collection<Class? extends Filter>> afterFilters) {}
The expected behavior is to have my custom filter added after the last filter in afterFilters.
Current Behavior
Currently, HttpSecurity has addFilterAfter and addFilterBefore method, both accepts only one existing filter as reference position.
Also, internal field filterOrders is private which means I cannot call its getOrder(filter) to detect order of existing filters.
Context
Since now I cannot put my custom filter to multiple existing filters. Then, I have to relying on options:
* find out which existing filter is late in the list by running my application and logging their ordering, and then call addFitlerAfter with that filter.
This put my code depends on sequence of filters which is internal to framework. I searched for example, there are no documentation said that BearerTokenAuthenticationFilter will always be after OAuth2LoginAuthenticationFilter, then relying on the ordering of these two filters is a hack and may break in future release.
Comment From: jzheaux
Hi, @dopsun, I appreciate you filing this issue and for your work in trying to understand the most idiomatic way to work with Spring Security.
I think I'd like to understand your use case better. Why is it that you care about (for example) the precise placement of BearerTokenAuthenticationFilter vs OAuth2LoginAuthenticationFilter? Why do you need to ensure your filter is before or after both of these?
Comment From: dopsun
Hi @jzheaux, thanks for looking into this ticket. Here is my scenario.
First, in my app, here are 4 components related with this issue :
* KeyCloak: act as OAuth2 Auth Server
* Gateway: support user to login from web browser or command line
* OAuth2 client login process for user from web browser
* BearerToken auth for user from command line. Command line tool will first get token from KeyCloak and call API via Gateway with token
* Service: micro service process built on OAuth2 resource server
* Database:
* user table for user information parsed from JWT token
* Other business object tables, may have foreign key to user, for example, created_by for auditing
Here are the design constraints/ requirements:
* KeyCloak is centrally managed. So, it's database is external to my app
* New users may be added to KeyCloak anytime (actually KeyClock may getting its users from systems like AD), and my app's user table should automatically populate those user basic details when new user's first time login, either from web browser or command line tool
* Then in my service layer, it will always assume authenticated user information has been populated
To guarantee authenticated user information always populated:
* add a filter to populate user information by parsing token populated
* my filter should be placed after both OAuth2LoginAuthenticationFilter and BearerTokenAuthenticationFilter to save user information from token to user table
* This is where feature in this ticket needed 💡
More alternative solution considered
* If without feature requested in this ticket, another workaround is to have two custom filters, one added after OAuth2LoginAuthenticationFilter and the other one added after BearerTokenAuthenticationFilter.
* This could be a better option comparing to check log file and find which one is late in the chain and add after that
* I think this alternative still not right solution, comparing with feature requested in this ticket.
Comment From: katnova
Hi @dopsun,
I'm not a maintainer, but I've worked allot with SpringBoot's filters, so I thought I'd throw my two cents in the ring here for you.
Firstly, I'm fairly certain you can check filter order with logs:
# application.yml
logging:
level:
org.springframework.web.servlet: DEBUG
org.springframework.boot.web.servlet: DEBUG
In those DEBUG logs, you should see something like this (example from bare-bones spring app):
o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: webMvcObservationFilter urls=[/*] order=-2147483647, springSecurityFilterChain urls=[/*] order=-100, characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105, authorizationFilter urls=[/*] order=2147483647
It's been awhile since I had to actually debug the order of filters, and I'm confident there's other ways, this one just came to mind.
Secondly, from what you included in your post, I would recommend ordering your custom filter before AuthorizationFilter::class.java (org.springframework.security.web.access.intercept.AuthorizationFilter). Until something major changes, that should always be the last auth filter in the chain (always after both OAuth2LoginAuthenticationFilter and BearerTokenAuthenticationFilter). This is where I fully populate my user information (as well as set groups/roles/etc...) from an external source, not unlike what you're trying to do sounds like. I've had my custom auth filters ordered before AuthorizationFilter since SpringBoot ~2.7, and as far as I'm aware that ordering is the proper pattern for custom auth filters.
Hope some of this info might be of use to you!
Comment From: dopsun
Thanks @katnova
What you suggested (add my custom filter _before AuthorizationFilter_) is a better option for my specific use case. 💡
Here is a bit more details around this ordering:
According to FilterOrderRegistration.java#119, AuthorizationFilter is second to last. From it's definition, it should imply all authentication filter should be before this filter.