Ltpa2Configurer.java
/*
* Copyright 2018 Ronny "Sephiroth" Perinke <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.sephirothj.spring.security.ltpa2;
import java.security.PublicKey;
import java.util.Optional;
import javax.crypto.SecretKey;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.util.Assert;
/**
* <p>
* A convenient way to configure {@link Ltpa2Filter the pre-authentication filter}.</p>
* <p>
* How to use:</p>
* <pre>
* public class WebSecurityConfig extends WebSecurityConfigurerAdapter
* {
*
* @Override
* protected void configure(HttpSecurity http) throws Exception
* {
* http
* .authorizeRequests()
* .antMatchers("/", "/home").permitAll()
* .antMatchers("/hello").hasRole("DEVELOPERS")
* .and()
* .apply(new Ltpa2Configurer())
* .sharedKey(sharedKey())
* .signerKey(signerKey())
* ;
* http.userDetailsService(userDetailsService());
* }
* }
* </pre>
* <p>
* <b>A {@link UserDetailsService} is required!</b> In case no instance is exposed, you have to provide one using {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#userDetailsService(org.springframework.security.core.userdetails.UserDetailsService) AuthenticationManagerBuilder#userDetailsService}.</p>
*
* @author Sephiroth
*/
@Accessors(fluent = true)
public class Ltpa2Configurer extends AbstractHttpConfigurer<Ltpa2Configurer, HttpSecurity>
{
/**
* <p>
* the name of the cookie expected to contain the LTPA2 token</p>
* <p>
* default: {@code "LtpaToken2"}</p>
*/
private String cookieName = "LtpaToken2";
/**
* <p>
* the name of header expected to contain the LTPA2 token</p>
* <p>
* default: {@value HttpHeaders#AUTHORIZATION}</p>
*/
private String headerName = HttpHeaders.AUTHORIZATION;
/**
* <p>
* the prefix in {@link #headername the header} preceding the LTPA2 token (may be empty)</p>
* <p>
* default: {@code cookieName + " "}</p>
*
* @see #cookieName
*/
@Setter
@NonNull
private String headerValueIdentifier = cookieName + " ";
/**
* the public key from the identity provider that sends the LTPA2-tokens. required for signature validation.
*/
@Setter
@NonNull
private PublicKey signerKey;
/**
* the shared secret key that is used to encrypt LTPA2 tokens
*/
@Setter
@NonNull
private SecretKey sharedKey;
/**
* <p>
* allow expired tokens</p>
* <p>
* <b>Do not use in prodcution mode, only for testing!</b></p>
*/
@Setter
private boolean allowExpiredToken = false;
/**
* allows to change the default behaviour when an authentication failure occurs.
* <p>
* The default is to respond with 403 status code</p>
*/
@Setter
@Nullable
private AuthenticationFailureHandler authFailureHandler;
@Override
public void configure(HttpSecurity builder) throws Exception
{
UserDetailsService userDetailsService = Optional.ofNullable(builder.getSharedObject(ApplicationContext.class))
.map(ctx -> ctx.getBean(UserDetailsService.class))
.orElseThrow(() -> new IllegalStateException("A UserDetailsService must be known in this context"));
Ltpa2Filter ltpaFilter = new Ltpa2Filter();
ltpaFilter.setUserDetailsService(userDetailsService);
ltpaFilter.setCookieName(cookieName);
ltpaFilter.setHeaderName(headerName);
ltpaFilter.setHeaderValueIdentifier(headerValueIdentifier);
ltpaFilter.setSharedKey(sharedKey);
ltpaFilter.setSignerKey(signerKey);
ltpaFilter.setAllowExpiredToken(allowExpiredToken);
if (authFailureHandler != null) {
ltpaFilter.setAuthFailureHandler(authFailureHandler);
}
ltpaFilter.afterPropertiesSet();
builder.addFilterAt(ltpaFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
/**
* configures the name of the cookie expected to contain the LTPA2 token
*
* @param cookieName the cookie name
* @return this instance
* @throws IllegalArgumentException if {@code cookieName} is empty
*/
public Ltpa2Configurer cookieName(@NonNull final String cookieName)
{
Assert.hasText(cookieName, "A cookieName is required");
this.cookieName = cookieName;
return this;
}
/**
* configures the name of the header expected to contain the LTPA2 token
*
* @param headerName the name of the header
* @return this instance
* @throws IllegalArgumentException if {@code headerName} is empty
*/
public Ltpa2Configurer headerName(@NonNull final String headerName)
{
Assert.hasText(headerName, "A headerName is required");
this.headerName = headerName;
return this;
}
}