How to configure two security configs with two filters in spring boot correctly? - spring-boot

I've implmemented security in my spring boot microservices project, the requirment is to have
two types of configurations, one for user request (from angular) and one from other services.
The design is to use JWT token for user request and API key for system calls.
Here is the config file (one file) but have also try to split it to two files with no impact:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class APISecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${my.api.key.header}")
private String principalRequestHeader;
#Value("${my.api.key.token}")
private String principalRequestValue;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.antMatcher("/api/users/**")
.authorizeRequests() //
.anyRequest().authenticated()
.and()
.addFilterBefore(new APIKeyAuthFilter(principalRequestHeader, principalRequestValue), UsernamePasswordAuthenticationFilter.class);
}
}
#Order(2)
#Configuration
public static class MySecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/users/**");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().disable().csrf().disable();
httpSecurity
.authorizeRequests()
.antMatchers("/users/UserEmailExist", "/users/User/Add", "/users/Authenticate",
"/users/User/ChangePassword")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/users/**").hasAnyRole(ROLE_ADMIN_USER, ROLE_MANAGER_USER)
.anyRequest().authenticated()
.and()
.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
}
Each config has a filter attached to it, here the api one:
public class APIKeyAuthFilter extends GenericFilterBean {
private String principalRequestHeader;
private String principalRequestValue;
public APIKeyAuthFilter(String principalRequestHeader, String principalRequestValue) {
super();
this.principalRequestHeader = principalRequestHeader;
this.principalRequestValue = principalRequestValue;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
String apiKey = getApiKey((HttpServletRequest) request);
if(apiKey != null) {
if(apiKey.equals(principalRequestValue)) {
ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(apiToken);
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(401);
httpResponse.getWriter().write("Invalid API Key");
return;
}
}
}
chain.doFilter(request, response);
}
}
Here is the filter for jwt (normal user from angular):
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
MSUserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
}
I've created two different controllers, one with prefix /api/users and second /users.
Here is what happen in two different scenarios:
The user login from Angular, get jwt token and process request which end up in the Jwt filter,
this scenarion looking good with no issues as the user is able to process request as long
he is authenticate.
Microservice send a request with api-key to url with /api/users prefix, it ended up on the same
filter the normal user ended which is not correct and without JWT token he is actually
able to proceed to the controller and process the request without going
to the correct filter.
The only solution I have is to have only one filter and process the header
for api-key and jwt but it doesn't seem right.
I've looked online and try to figure out what I'm doing wrong but no clue as of now.

An update on this issue so I hope it will help to the community.
Firstly, I removed the following code and this mainly fix the problem:
// #Override
// public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/api/users/**");
// }
The way the solution work as a whole is that the first configuration #Order(1) you
define .antMatcher which means the configuration will work only for urls that match
the prefix.
So now, scenario 1. User from Angular go the the JWT filter only.
scenario 2. API user will lend in the API filter first! But once it's done (After succesfull authentication) it still
continue to the JWT filter but becuase it doesn't have JWT the filter not doing anything.
I would like to avoid to other filter in case of API call but the solution work,
problem solved.
I must say that security in spring boot is the most complex I came across so far from other features.

Because the AuthTokenFilter is instantiated with #Bean, which causes the filter to be added to the ApplicationFilterChain, after the APIKeyAuthFilter is processed, it can also enter the AuthTokenFilter.

Related

Authorize using acess token

I have secured my application using oauth2 standard flow with amazon cognito. It works fine.
Sometimes i want to link to my webapp through my mobile app, and i dont want the user to sign in again. How do i create an #AnonymousAllowed endpoint that takes in an access token, authenticates using spring security and redirects to the homepage of my application?
I've tried creating an endpoint that returns the jsessionid, then another anonymous endpoint that accepts a jsessionid, sets the cookie and redirects to the homepage, but it didnt work.
I've tried setting the authorization header to the access token. But i feel like that wont work properly because the token will expire after few minutes, and spring wont handle authorization for me, rather i have to do it "manually"
I don't know if this can help in your situation. But you can check if anything you can refer from below. It's a custom filter with custom token validator.
You can use Custom TokenAuthenticationFilter by extending AbstractAuthenticationProcessingFilter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenValidatorService tokenValidatorService;
public CustomTokenAuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token= httpServletRequest.getHeader(CommonConstants.OAUTH_HEADER_KEY_AUTHORIZATION);
//Get the custom header
//Validate token using custom validator based on header value
Optional<OAuthResponseData> oauthResponseData = tokenValidatorService.validateAccessToken(token, provider);
...
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(oauthResponseData.get(), oauthResponseData.get().getOauthToken());
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
In TokenValidatorService you can implement OAuth2TokenValidator to validate the token
Spring Security config class:
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {
#Configuration
public static class CustomTokenSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
protected CustomTokenAuthenticationProvider customTokenAuthenticationProvider;
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring()
...
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS).authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
public CustomTokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
final CustomTokenAuthenticationFilter filter = new CustomTokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
// filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customTokenAuthenticationProvider);
}
}
}

AbstractAuthenticationProcessingFilter triggered no matter what SecurityFilterChain it is a part of

I am experimenting with spring security and came across a strange behavior.
My idea is to create a security filter that authenticates requests based on JWT (or JWS) tokens:
public class JWTokenFilter extends AbstractAuthenticationProcessingFilter {
public JWTokenFilter(AuthenticationManager authenticationManager) {
super("/**"); //doesn't have any effect, every request still gets considered by this filter
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token)) {
throw new TokenException("Token is empty");
}
var authentication = determineAuthentication(token.replace("Bearer","").trim());
//the AbstractAuthenticationProcessingFilter fills the Security context
return this.getAuthenticationManager().authenticate(authentication);
}
#Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Asked for "+request.getRequestURI());
return request.getHeader("Authorization") != null;
}
private TokenAuthentication<UserInfo> determineAuthentication(String token) {
var split = token.split("\\.");
if (split.length < 2 || split.length > 3) {
throw new TokenException("Token malformed");
}
if (split.length == 2){
return new JWTAuthentication<>(token);
}else {
return new JWSAuthentication<>(token);
}
}
}
I have 3 #RestController classes which have their paths mapped:
#RequestMapping("/admin")
#RequestMapping("/all")
#RequestMapping("/anon")
Along with this, I have the following security configuration:
#Configuration
#Order(98)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/all/**","/anon/**")
.and()
.authorizeRequests().antMatchers("/all/**").permitAll()
.and()
.authorizeRequests().antMatchers("/anon/**").anonymous();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().mvcMatchers("/webjars/**", "/css/**");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Configuration
#Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Lazy
#Autowired
private JWTokenFilter tokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter,ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
#Bean
public JWTokenFilter tokenFilter(JWTokenAuthenticationProvider jwTokenAuthenticationProvider,JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
}
From this configuration here we can see that there are 2 SecurityFilterChans (not counting the /webjars and /css ones):
That matches all requests for "/all/**" and "/anon/**" REST routes
That matches any request
Since the 1. chain has lower #Order(98), than 2. #Order(99), that means that the 1. chain will be considered first which is shown by the debugger,
and matched if the incoming request looks like:
curl --request GET \
--url http://localhost:8080/all/hello \
Now what I am experiencing is that the JWTokenFilter method boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) is always called no matter the request path !
And in the console output, I can find Asked for /all/hello.
Edit:
My spring boot version is 2.3.6.RELEASE
My question is:
Why is the JWTokenFIlter even asked if it should authenticate requests with paths that are not matched by the SecurityFilterChain it is a part of?
I believe I have a better answer, but I wanted to answer your original question as well. I split this into two sections.
Improved Answer
I realize this doesn't answer the original question, but I think you may be better off using the built in support for JWT based authentication. I'd check out the OAuth 2.0 Resource Server section of the reference documentation.
Answer to Original Question
Spring Boot will automatically register any Filter exposed as a #Bean for every request directly with the Servlet Container.
You have two options that I see. The first is to avoid exposing the JwtTokenFilter as a #Bean.
#Configuration
#Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
JWTokenAuthenticationProvider jwTokenAuthenticationProvider;
#Autowired
JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter(),ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
public JWTokenFilter tokenFilter(){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
Alternatively, you can continue exposing JwtTokenFilter as a #Bean and create a FilterRegistrationBean that disables registration.
#Bean
public FilterRegistrationBean registration(JWTokenFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

URL access denying when implementing the Spring Security for URL authentication

I am trying to implement URL authentication before it giving the response through business logic. For that I am using the authentication provider from Spring Security and trying to do one simple demo for testing authenticationProvider working properly. After this I am going to modify by adding my business logic.
My security config file SecurityConfig.java like the following,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.anyRequest()
.authenticated()
.and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(authenticationProvider);
}
}
And My CustomAuthenticationProvider.java implementation like the following,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider
{
#SuppressWarnings("unused")
#Override
public Authentication authenticate(Authentication authToken) throws AuthenticationException {
String userToken = (String) authToken.getName();
String responseString = "test";
String password = "test";
if(responseString.equals(userToken)) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userToken, password);
return auth;
}
else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
And my TestSecurity.java like the following,
#RestController
public class TestSecurity {
#GetMapping("/security/load")
public String LoadSecureUsers() {
return "hello spring security";
}
}
When I am calling the URL localhost:8585/security/load with headers authToken: "test" from POSTMAN application, I am getting the following,
{
"timestamp": "2019-10-30T07:24:25.165+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/security/load"
}
If the condition are satisfying in IF, then how the URL is not able to access? Did I make any mistake in authentication Provider implementation?
Instead of AuthenticationProvider use filter to process the request. This code might help you:
public class ApplicationAuthFilter extends BasicAuthenticationFilter {
public ApplicationAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = String bearerToken = req.getHeader("accessToken");
String username = "test";
String password = "test"
if (username != null && !username.isEmpty()) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
And your security config file like this:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new ApplicationAuthFilter(authenticationManager()))
}
}
Basically you need to read the header information which you are passing with request and based on that you have to take action.
Hope this helps.

How does Spring Security Filter Work With Custom Authentication and How To Combine It with Servlet Filter?

So I have a question regarding Spring Security. So I want to check authentication using custom header which then I want to check the token given in the custom header to redis value and set the data object as credentials at custom implementation of abstract authentication token.
I have already followed the tutorial in this web: https://shout.setfive.com/2015/11/02/spring-boot-authentication-with-custom-http-header/, but I can't update the authentication interface in SecurityContextHolder.getContext() (I set the credentials in my implementation of Authentication Interface, but when I get it in the service, the credentials is null).
I also found other problems, I actually want to order the filter like this:
ExceptionHandlerFilter (to catch exception error in the filter) -> Other filter or CustomWebSecurityConfigurerAdapter.
But when the url matches the antMatcher, I found that ExceptionHandlerFilter was skipped by the application.
I was so confused by this and could not find better tutorial in implementing custom authentication using Spring Security. So I want to ask whether you guys can tell me how Spring Security works and how to combine it with Filter?
Here is my first filter to catch exception
#Component
#Order(0)
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private JaminExceptionHandler exceptionHandler;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public ExceptionHandlerFilter(JaminExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Throwable exception) {
ResponseEntity<?> responseEntity = this.exceptionHandler.handleException(exception, request);
response.setStatus(responseEntity.getStatusCode().value());
response.setHeader("Content-Type", "application/json");
response.getWriter().write(this.objectMapper.writeValueAsString(responseEntity.getBody()));
}
}
}
Here is my Auth Filter
#Component
public class AuthFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("J-Auth");
if (token != null) {
Authentication auth = new JaminAuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
} else {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
}
}
Authentication Provider
#Component
public class JaminAuthenticationProvider implements AuthenticationProvider {
private RedisTemplate<String, String> authRedis;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public JaminAuthenticationProvider(#Qualifier("authRedis") RedisTemplate<String, String> authRedis) {
this.authRedis = authRedis;
}
private UserDTO getUserDTO(String token) throws IOException {
String userData = this.authRedis.opsForValue().get(token);
if (userData == null) {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
return this.objectMapper.readValue(userData, UserDTO.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JaminAuthenticationToken auth = (JaminAuthenticationToken) authentication;
try {
UserDTO userDTO = this.getUserDTO(auth.getToken());
auth.setCredentials(userDTO);
return auth;
} catch (IOException e) {
e.printStackTrace();
}
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
#Override
public boolean supports(Class<?> authentication) {
return JaminAuthenticationToken.class.isAssignableFrom(authentication);
}
}
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#Order(1)
public class JaminSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JaminAuthenticationProvider jaminAuthenticationProvider;
private void disableDefaultSecurity(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.formLogin().disable();
http.logout().disable();
http.httpBasic().disable();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
this.disableDefaultSecurity(http);
http.antMatcher("/auth/check")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), BasicAuthenticationFilter.class);
// http.authorizeRequests().anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jaminAuthenticationProvider);
}
}
Spring Security has some "before and after" steps. There are a few Handlers that can help. I don't know your code, but if you can get your authentication ok, maybe you just have to extend a SuccessHandler and set the authentication there, like i did in my blog project:
if(checkEmail(authentication)) {
val adminRole = SimpleGrantedAuthority("ROLE_ADMIN")
val oldAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities()
val updateAuthorities = mutableListOf<GrantedAuthority>()
updateAuthorities.add(adminRole)
updateAuthorities.addAll(oldAuthorities)
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(),
updateAuthorities))
}
And about the filters, maybe you can find your answer here. I don't like using filters and interceptors, but sometimes they are really necessary.

Security filters are overlapping

When multiple filters are added to the HttpSecurity configure method, they seem to be overlapping because only one works at the time.
This is the configure method:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.logout().and().antMatcher("/**")
.addFilterBefore(ssoFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
I've tried to specify the order but the issue still persists:
#Bean
public FilterRegistrationBean securityFilterChain(#Qualifier(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) Filter securityFilter) {
FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
registration.setOrder(Integer.MAX_VALUE - 2);
registration.setName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
return registration;
}
#Bean
public FilterRegistrationBean ssoFilterRegistrationBean() throws Exception {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(ssoFilter());
registrationBean.setOrder(Integer.MAX_VALUE-1);
return registrationBean;
}
#Bean
public FilterRegistrationBean csrfFilterRegistrationBean() throws Exception {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(csrfHeaderFilter());
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
I've followed the following thread with no success.
Filter order in spring-boot
https://github.com/spring-projects/spring-boot/issues/1640
https://github.com/spring-projects/spring-boot/issues/677
Any help will be appreciated!
UPDATE:
CSRF Filter definition
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request
.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null
|| token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
SSO Filter definition:
public class SSORequestHeaderAuthenticationFilter extends RequestHeaderAuthenticationFilter {
private boolean allowPreAuthenticatedPrincipals = true;
public SSORequestHeaderAuthenticationFilter() {
super();
//TODO Pull this value from a properties file (application.properties, or localstrings.properties)
//NOTE SM_USER is the default, but you can change it like this (your company may use some other header)
//this.setPrincipalRequestHeader("SM_USER");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
/**
* This is called when a request is made, the returned object identifies the
* user and will either be {#literal null} or a String. This method will throw an exception if
* exceptionIfHeaderMissing is set to true (default) and the required header is missing.
*
* #param request {#link javax.servlet.http.HttpServletRequest}
*/
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String userName = (String) (super.getPreAuthenticatedPrincipal(request));
if (userName == null || userName.trim().equals("")) {
return userName;
}
return userName;
}
public boolean isAllowPreAuthenticatedPrincipals() {
return allowPreAuthenticatedPrincipals;
}
}
My guess is that you are not always executing FilterChain.doFilter method inside both filters. Then the filter chain stops and only one of your custom filters is executed. In this simple example both filters executed:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new Filter1(), RequestHeaderAuthenticationFilter.class)
.addFilterAfter(new Filter2(), CsrfFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
static class Filter1 extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("executed filter 1");
filterChain.doFilter(request, response);
}
}
static class Filter2 extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("executed filter 2");
filterChain.doFilter(request, response);
}
}
}
You are confusing container registration (with FilterRegistrationBean) and registration in a Security filter chain (with HttpSecurity) and also possibly with the order of the filter chains within Spring Security. If a given filter chain is selected by Spring Security all the filters in it are not even necessarily fired anyway (filters can always switch off other downstream filters).
I suggest you stop worrying about the order in your FilterRegistrationBeans and use them to disable the container registration (by setting their enabled flag to false). Then think about the order of your filter chains, as specified by the #Order on your WebSecurityConfigurers. And finally you can decide if the order of the filters in a given chain matters, and if it does use the addFilter{Before,After} methods.
A Filter which does what you want to do does already exist. Its the OAuth2AuthenticationProcessingFilter.
This filter is used if you annotate your application with #EnableResourceServer. If you do so this will cause, that only token based authentication will work now.
You have to set the stateless flag of this filter to false to allow other ways of authentication, too.
What i did is to create a class ApiTokenAccessFilter which extends OAuth2AuthenticationProcessingFilter. This filter takes a ResourceServerTokenServices constructor parameter and sets the stateless flag to false.
public class ApiTokenAccessFilter extends OAuth2AuthenticationProcessingFilter {
public ApiTokenAccessFilter(ResourceServerTokenServices resourceServerTokenServices) {
super();
setStateless(false);
setAuthenticationManager(oauthAuthenticationManager(resourceServerTokenServices));
}
private AuthenticationManager oauthAuthenticationManager(ResourceServerTokenServices tokenServices) {
OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
oauthAuthenticationManager.setResourceId("oauth2-resource");
oauthAuthenticationManager.setTokenServices(tokenServices);
oauthAuthenticationManager.setClientDetailsService(null);
return oauthAuthenticationManager;
}
}
In my security config i used this Filter as follows:
#Configuration
#EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ResourceServerTokenServices tokenServices;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new ApiTokenAccessFilter(tokenServices), AbstractPreAuthenticatedProcessingFilter.class);
}
}
I think this could be easier so i opened an issue on the spring-security-oauth Github repo. I'm not sure whether this solution is the way to go, but i didn't find another alternative.

Resources