Reputation: 5606
I am trying to add a custom filter to a Spring HttpSecurity
. This filter must check that the username is in a list provided externaly and injected into the filter as a Set
.
No matter where I put the filter, its method attemptAuthentication
is never called. Here is the filter code:
import java.io.IOException;
import java.util.Base64;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class MyRoleFilter extends AbstractAuthenticationProcessingFilter {
final Set<String> authorisedUsers;
public WhoRoleFilter(String url, AuthenticationManager authenticationManager, Set<String> authorisedUsers) {
super(new AntPathRequestMatcher(url));
this.authorisedUsers= authorisedUsers;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// In BASIC authentication user:password come as Base64 in the Authorization header
final String authorization = request.getHeader("Authorization");
final String[] userPasswd = new String(Base64.getDecoder().decode(authorization)).split(":");
// The docs of AbstractAuthenticationProcessingFilter says it must throw an exception in case authentication fails
// https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#attemptAuthentication-javax.servlet.http.HttpServletRequest-javax.servlet.http.HttpServletResponse-
if (userPasswd.length!=2)
throw new BadCredentialsException("Bad Credentials");
if (authorisedUsers.contains(userPasswd[0])) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userPasswd[0], userPasswd[1]);
return this.getAuthenticationManager().authenticate(authRequest);
} else {
throw new BadCredentialsException("User has not the correct role");
}
}
}
And this is how I am trying to add it to HttpSecurity
:
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/disabled")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new MyRoleFilter("**/path/services/whatever/**", this.authenticationManager() ,myUserNamesSet), BasicAuthenticationFilter.class)
.httpBasic();
}
}
I am not certain where during the build chain must the addFilterBefore()
go. Moreover, a standard user+password aginst an LDAP server is required additionally to the usernames list filter. The LDAP authentication was alredy in place and in working order.
Update, this is the configureGlobal(AuthenticationManagerBuilder)
at SecurityConfig
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth.ldapAuthentication()
.userDnPatterns(ConfigurationProvider.get().getProperty(Property.PROP_LDAP_USER_BASE_DN))
.contextSource(contextSource())
.passwordCompare()
.passwordAttribute(ConfigurationProvider.get().getProperty(Property.PROP_LDAP_PASSWORD_ATTRIBUTE));
} catch (Exception exc) {
LOG.error(exc.getMessage(), exc);
}
}
Upvotes: 1
Views: 2122
Reputation: 1
add and().addFilter(new AuthenticationFilter(authenticationManager()))
under configure(HttpSecurity http)
as below. It should call the authentication Filter explicitly.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll().anyRequest()
.authenticated().and().addFilter(new AuthenticationFilter(authenticationManager())).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Upvotes: 0
Reputation: 5606
Answering my own question after research. This is how I had to implement SecurityConfig
and my authentication processing filter in order to add a custom role check on to of Basic authentication using LDAP for storing user credentials.
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private String Set<String> myUsers;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
myUsers = new HashSet<>(Arrays.asList("John","James","Jeremy"));
try {
auth.ldapAuthentication()
.userDnPatterns("ldap.user.base.dn")
.contextSource(contextSource())
.passwordCompare()
.passwordAttribute("ldap.password.attribute");
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
} catch (Exception exc) {
// LOG.error(exc.getMessage(), exc);
}
}
@Override
@Autowired
public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
super.setAuthenticationConfiguration(authenticationConfiguration);
}
@Override
@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
super.setObjectPostProcessor(objectPostProcessor);
}
@Override
protected void configure(HttpSecurity http) {
try {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/disabled")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterAfter(new MyFilter("/services/my/path", this.authenticationManager(), myUsers), BasicAuthenticationFilter.class)
.httpBasic();
http.logout().deleteCookies("JSESSIONID")
.clearAuthentication(true)
.invalidateHttpSession(true);
} catch (Exception exc) {
// LOG.error(exc.getMessage(), exc);
}
}
}
Note that I had to override successfulAuthentication
from AbstractAuthenticationProcessingFilter
as it was always trying to perform a redirect looking for a request header containing the target URL.
public class MyFilter extends AbstractAuthenticationProcessingFilter {
final Set<String> authorisedUsers;
public MyFilter(String url, AuthenticationManager authenticationManager, Set<String> authorisedUsers) {
super(new AntPathRequestMatcher(url));
this.authorisedUsers = authorisedUsers;
this.setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// In BASIC authentication user:password come as Base64 in the Authorization header
final String authorization = request.getHeader("Authorization");
String authorizationBasic = authorization;
if (authorization.startsWith("Basic")) {
authorizationBasic = authorization.split(" ")[1];
}
final String[] userPasswd = new String(Base64.getDecoder().decode(authorizationBasic)).split(":");
// The docs of AbstractAuthenticationProcessingFilter says it must throw an exception in case authentication fails
// https://docs.spring.io/spring-security/site/docs/4.2.6.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#attemptAuthentication-javax.servlet.http.HttpServletRequest-javax.servlet.http.HttpServletResponse-
if (userPasswd==null || userPasswd.length!=2)
throw new BadCredentialsException("Bad Credentials");
if (authorisedUsers.contains(userPasswd[0])) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userPasswd[0], userPasswd[1]);
return getAuthenticationManager().authenticate(authRequest);
} else {
throw new BadCredentialsException("User " + userPasswd[0] + " has not the WHO role");
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
Upvotes: 1