To integrate the new HTTP stack into the existing environment, we can use LTPA tokens. These tokens are cookies which store the authentication information and allow to share them betweeen different participating Domino servers. A users must log on only once, and existing applications and data/views can be accessed without a relogin.
Validating an existing LTPA token with Spring can be done with our own PreAuthentificationFilter which checks for an existing LTPA token and extracts the authentication details from the cookie and creates a new Principal instance.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
public class LtpaPreAuthenticatedFilter extends AbstractPreAuthenticatedProcessingFilter {
@Value("${ltpa.secret}")
private String ltpaSecret;
@Value("${ltpa.cookieName}")
private String ltpaCookieName;
@Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if( cookies == null ) {
return null;
}
for( int i= 0; i<cookies.length ; i++ ){
String name = cookies[i].getName();
String value = cookies[i].getValue();
if( ltpaCookieName.equalsIgnoreCase(name) ){
DominoLtpaToken ltpaToken = new DominoLtpaToken( value, ltpaSecret );
if( ltpaToken.isValid() ){
return ltpaToken.getDistinguishedName();
}
}
}
return null;
}
@Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// is required to return an empty string
return "";
}
}
The filter implements two methods, one for extraction of the principal, and the other for the credentials (which we don’t have with LTPA tokens). In the getPreAuthenticatedPrincipal method, existinig LTPA tokens are searched, then the user extracted and the token validated.
The secret of the LTPA token and the name are stored in application.properties:
The second part is implementing a AuthenticationUserDetailsService. This service is for getting additional details for the authenticated user, for example the ACL roles or groups the user belongs to.
import java.util.Collection;
import java.util.HashSet;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class LtpaUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
@Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token)
throws UsernameNotFoundException {
String userName=(String)token.getPrincipal();
Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>() ;
authorities.add(new LtpaUserAuthority());
User user = new User(userName,"",authorities);
return user;
}
}
In our case, we are just adding an LtpaUserAuthority to the user information. Don’t worry about the usage of the LtpaUserAuthority. We come back to this in another post.
import org.springframework.security.core.GrantedAuthority;
public class LtpaUserAuthority implements GrantedAuthority {
private static final long serialVersionUID = 1L;
@Override
public String getAuthority() {
return "ROLE_USER_LTPA";
}
}
In the last step we have to update the SecurityConfig.java to activate the filter:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Configuration
@Order(1)
static class DominoLtpaSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService() {
return new LtpaUserDetailsService();
}
@Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(authenticationUserDetailsService());
provider.setUserDetailsChecker(new AccountStatusUserDetailsChecker());
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
@Bean
public AbstractPreAuthenticatedProcessingFilter preAuthenticatedProcessingFilter() throws Exception {
LtpaPreAuthenticatedFilter filter = new LtpaPreAuthenticatedFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(preAuthenticatedProcessingFilter())
.authorizeRequests()
.antMatchers("/**").permitAll() ;
}
}
...
}
This includes the filter in any request. Now, the Principal contains the user name stored in the LTPA token.