'Authenticate certain endpoints with custom Filter | Spring Security [duplicate]
1. Overview
First things first - I am pretty new to Spring Security so if you see something trivial, please keep that in mind 🤓 I am trying to create a custom way for my server side project to authenticate incoming requests following this tutorial.
2. Problem
The authentication works for every incoming request. I want it to work only for specified ones, just like when using basic authentication.
3. Code
Filter
@Component
@RequiredArgsConstructor
public class MyCustomAuthenticationFilter implements Filter {
private final AuthenticationManager authenticationManager;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String authorization = httpRequest.getHeader("CustomAuth");
MyCustomAuthentication myCustomAuthentication = new MyCustomAuthentication(authorization, null);
Authentication authResult = authenticationManager.authenticate(myCustomAuthentication);
if (authResult.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(authResult);
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
Manager / config
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public APIKeyAuthenticationProvider apiKeyAuthenticationProvider() {
return new APIKeyAuthenticationProvider();
}
public MyCustomAuthenticationFilter myCustomAuthenticationFilter() throws Exception {
return new MyCustomAuthenticationFilter(authenticationManagerBean());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(apiKeyAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().permitAll()
.and()
.addFilterAt(myCustomAuthenticationFilter(), BasicAuthenticationFilter.class)
.csrf().disable();
}
}
Provider
public class APIKeyAuthenticationProvider implements AuthenticationProvider {
private final String secretKey = "password";
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String requestKey = authentication.getName();
if (requestKey.equals(secretKey)) {
MyCustomAuthentication fullyAuthenticated = new MyCustomAuthentication(null, null, null);
return fullyAuthenticated;
} else {
throw new BadCredentialsException("Header value is not correct");
}
}
/**
* If authentication type is MyCustomAuthentication, then
* apply this provider
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return MyCustomAuthentication.class.equals(authentication);
}
}
Authentication
public class MyCustomAuthentication extends UsernamePasswordAuthenticationToken {
public MyCustomAuthentication(Object principal, Object credentials) {
super(principal, credentials);
}
public MyCustomAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
4. Question
What do I need to change to make this custom filter authenticate only endpoints specified in the configure
method?
Thank you in advance for any help ✌️
5. Versions
- java - 11
- spring boot - 2.6.6
- spring framework - 5.3.18
Solution 1:[1]
TLDR; Remove @Component
from your filter to ensure only the http.antMatchers()
or http.mvcMatchers()
applies since Spring automatically registers Filter
s with the container. To restrict the filter to only certain endpoints within the security filter chain, see below.
See this answer for a quick summary of authorization (e.g. permitAll()
, authenticated()
) vs. other features such as authentication and CSRF protection. If you need more detail, keep reading.
There are two issues touched on in this question:
- How do I only process certain requests in a Servlet filter?
- How do I use Spring Security to authenticate requests using an API key?
#1 - Believe it or not, this question isn't actually Spring Security specific, because any Servlet filter can choose to either act on a request, reject the request, or ignore the request and pass the request to the next filter in the chain. This is true whether it's a regular filter chain, or specifically the Security Filter Chain. This is typically done with a RequestMatcher
, for example:
public class MyFilter extends OncePerRequestFilter {
private RequestMatcher requestMatcher = new AntPathRequestMatcher("/some-endpoint", HttpMethod.POST.name());;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!this.requestMatcher.matches(request)) {
// Ignore the request and pass to the next filter
filterChain.doFilter(request, response);
// Note: If you want to reject the request, you can do something and then return without calling filterChain.doFilter()
return;
}
// Handle the request and call the next filter afterwards if desired...
// ...
filterChain.doFilter(request, response);
}
}
#2 - There are many ways to achieve a goal like authenticating using an API key. It is helpful here to define what exactly you mean by "API key". Sometimes this is a bearer token, e.g. Authorization: Bearer xyz123
which could be an opaque token or JWT for OAuth2 or even a custom bearer token. It could also be a completely custom scheme, e.g. X-API-KEY: xyz123
which comes from a database table of tokens managed by your organization, etc. Or something else?
Unless you have a non-negotiable requirement that requires you to write your own custom filter, I'd recommend the BearerTokenAuthenticationFilter
, provided in the oauth2-resource-server
module which you can read about in the reference docs. It is very customizable and can actually do much more than OAuth 2.0.
You can also instantiate this filter yourself instead of a custom filter, and configure it with a BearerTokenResolver
allowing you to resolve a custom header (e.g. CustomAuth
) and an instance of AuthenticationManager
.
Here's a (working) complete configuration based on your provided example:
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.mvcMatcher("/api/**")
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.addFilter(bearerTokenAuthenticationFilter());
// @formatter:on
return http.build();
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() {
BearerTokenAuthenticationProvider authenticationProvider =
new BearerTokenAuthenticationProvider();
ProviderManager authenticationManager =
new ProviderManager(authenticationProvider);
BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter =
new BearerTokenAuthenticationFilter(authenticationManager);
BearerTokenResolver bearerTokenResolver =
(request) -> request.getHeader("CustomAuth");
bearerTokenAuthenticationFilter.setBearerTokenResolver(bearerTokenResolver);
return bearerTokenAuthenticationFilter;
}
public static class BearerTokenAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// We obviously need to do a better check than this here...
if (!authentication.getCredentials().equals("password")) {
throw new BadCredentialsException("Header value is not correct");
}
return new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), Collections.emptyList());
}
@Override
public boolean supports(Class<?> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |