Trusted space issue has similar solution to anonymous user identification (I've done this conclusion when I was working on it.)
Short answer
Trusted space does not need authorization, but no UserDetailsService will be called, because of using only AnonymousAuthenticationProvider
and AnonymousAuthenticationFilter
by default. It is good enough implement custom filter based on AnonymousAuthenticationFilter
overriding createAuthentication
and replace default (AnonymousAuthenticationFilter
) with custom one (CustomAnonymousAuthenticationFilter
):
@Configuration
public static class NoAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private IdentifiableAnonymousAuthenticationFilter identifiableAnonymousAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().authenticationFilter(identifiableAnonymousAuthenticationFilter);
http.antMatcher("/**").authorizeRequests()
.anyRequest().permitAll();
}
}
Full answer
I found out that CustomUserDetailsService will never be called if user is not authorized. Continuing research pay attention on the AnonymousAuthenticationFilter which is responsible for creating anonymous user info. So in the very and purpose is to replace the AnonymousAuthenticationFilter with my IdentifiableAnonymousAuthenticationFilter where some methods should be overridden:
@Component
public class IdentifiableAnonymousAuthenticationFilter extends AnonymousAuthenticationFilter {
public static final String KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER
= "Key.IdentifiableAnonymousAuthenticationFilter";
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private GrantedAuthorityService grantedAuthorityService;
private AuthenticationDetailsSource authenticationDetailsSource
= new WebAuthenticationDetailsSource();
public IdentifiableAnonymousAuthenticationFilter() {
this(KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER);
}
public IdentifiableAnonymousAuthenticationFilter(String key) {
super(key);
}
@Override
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(
KEY_IDENTIFIABLE_ANONYMOUS_AUTHENTICATION_FILTER,
userDetailsService.loadCurrentUser(request),
grantedAuthorityService.getGrantedAuthoritiesForCurrentUser());
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
}
to inject it into configuration
@Configuration
public class IdentifyAnonymousConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private IdentifiableAnonymousAuthenticationFilter identifiableAnonymousAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().authenticationFilter(identifiableAnonymousAuthenticationFilter);
// ... some other configurations
}
}
Now it seems much better, because identifiableAnonymousAuthenticationFilter is injected in AnonymousConfigurer. Pay your attention to your configurations based on WebSecurityConfigurerAdapter
. If you have few ones and one of them will not set customAnonymousAuthenticationFilter but configured earlier than custom.. you'll get default instance of AnonymousAuthenticationFilter (configured in WebSecurityConfigurerAdapter
by default):
protected final HttpSecurity getHttp() throws Exception {
//...
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
// ...
I would not care about it if application fixed, but AnonymousAuthenticationFilter called earlier than IdentifiableAnonymousAuthenticationFilter. And doFilter puts into SecurityContextHolder incorrect
Authentication.
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if(SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req));
if(this.logger.isDebugEnabled()) {
this.logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
} else if(this.logger.isDebugEnabled()) {
this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(req, res);
}
So when next time doFilter is called for IdentifiableAnonymousAuthenticationFilter it does not replace the Authentication
because of condition if(SecurityContextHolder.getContext().getAuthentication() == null)
(see method before).
As result would be really good to provide configuration where fix for WebSecurityConfigurerAdapter
configuration using magic annotation @Order to manage configuration loading order.
Warning
Or someone could think - add doFilter
overriding in IdentifiableAnonymousAuthenticationFilter without condition (it is hack):
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(req, res);
}
It is not acceptable if you need spring security with handling authorized/authenticated user but in some cases it is enough.
P.S.
Some parts of the solution could be improved but I hope that idea is clear in general.