Combining multiple spring security authentication providers into custom single one - spring-security

My goal is to make two step authentication with credentials. First step is to check whether the user with principal has a role in a special database table. Second is to perform standard ldap authentication.
What I need is to perform both checks together but a common approach with authentication providers is to claim the authentication success after first success from any authentication provider. So I decided to create a custom AuthenticationProvider implementation which calls for LdapAuthenticationProvider and then performs DB check logic, but it doesn't work since there is nothing to autowire with AbstractLdapAuthenticationProvider.
Please, tell me whether
The approch to solve such problem is rational
If it is rational how can I inject AbstractLdapAuthenticationProvider?
Security configuration code is
#Autowired
private DBRoleAuthenticationProvider dbRoleAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url("...")
.managerDn("...")
.managerPassword("...")
.and()
.userSearchFilter("uid={0}");
auth.authenticationProvider(dbRoleAuthenticationProvider);
}
Custom authentication provider is
#Component
public class DBRoleAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserHasRoleInDBService userHasRoleInDBService;
#Autowired
private AbstractLdapAuthenticationProvider ldapAuthenticationProvider;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication = ldapAuthenticationProvider.authenticate(authentication);
if (!authentication.isAuthenticated()) {
return authentication;
}
try {
String loginToSearch = (String) authentication.getPrincipal();
if (!userHasRoleInDBService.userHasRole(loginToSearch)) {
authentication.setAuthenticated(false);
}
} catch (Exception e) {
authentication.setAuthenticated(false);
}
return authentication;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Thanks in advance!

Looks like auth.authenticationProvider(dbRoleAuthenticationProvider);
is defeating auth.ldapAuthentication()
You should customize the way you retrieve authorities for the authenticated user by looking at the doc of
userDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper)
and
ldapAuthoritiesPopulator(LdapAuthoritiesPopulator ldapAuthoritiesPopulator)
of LdapAuthenticationProviderConfigurer

Related

Altering URL for Spring Security SAML2 Login

I have an application with multiple authentication types (i.e. Basic and a special Preauthorized login). I am attempting to add a SAML2 RelyingParty registration in my security configuration, where I am attempting to change the default path from:
/login/saml2/sso/{registrationId}
to
/auth/saml2/{registrationId}
So, I have the following setup:
public RelyingPartyRegistration provder1RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation("classpath:provider1/metadata.xml")
.registrationId("provider1")
.assertionConsumerServiceLocation("{baseUrl}/auth/saml2/{registrationId}")
.build();
return registration;
}
// #Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
Collection<RelyingPartyRegistration> registrations = Collections.unmodifiableList(Arrays.asList(provider1RelyingPartyRegistration()));
InMemoryRelyingPartyRegistrationRepository repository = new InMemoryRelyingPartyRegistrationRepository(registrations);
return repository;
}
// fluff
#Override
protected void configure(HttpSecurity http) throws Exception {
final RequestMatcher filterRequestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher("/auth/basic"),
new AntPathRequestMatcher("/auth/preauth")
);
ApplicationAuthenticationProcessingFilter filter = new ApplicationAuthenticationProcessingFilter(filterRequestMatcher, authenticationManagerBean());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(failureHandler());
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.and()
.addFilterAfter(filter, LogoutFilter.class)
// fluff
.and()
.saml2Login()
.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository())
.loginProcessingUrl("/auth/saml2/{registrationId}")
;
}
Unfortunately, I get this:
14 Dec 10:55:34 WARN [https-openssl-nio-127.0.0.1-444-exec-2] (DispatcherServlet.java:1278) - No mapping for POST /svc/auth/saml2/provider1
Can anyone tell me what I'm doing wrong trying to change that path? My application does NOT use Spring Boot, so I'm stuck with manual configuration.
EDIT
Some debugging has led to this hitting this line in the Saml2LoginConfigurer:
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(
this.authenticationRequestEndpoint.filterProcessingUrl, this.relyingPartyRegistrationRepository);
Somehow, there's a default authenticationRequestEndpoint (since I didn't define one) setting the filterProcessingUrl to a value of /saml2/authenticate/{registrationId}. So, how do I override this?
The loginProcessingUrl is called by the asserting party after the authentication succeeds, which contains in the request the SAMLResponse parameter.
What you are trying to change is the URL to process an authentication request (create the SAMLRequest and send to the asserting party), this is done by the Saml2WebSsoAuthenticationRequestFilter class. To change the redirectMatcher you have to provide an ObjectPostProcessor, see this issue.
ObjectPostProcessor<Saml2WebSsoAuthenticationRequestFilter> processor = new ObjectPostProcessor<>() {
#Override
public <O extends Saml2WebSsoAuthenticationRequestFilter> O postProcess(O filter) {
filter.setRedirectMatcher(new AntPathRequestMatcher("/my/custom/url"));
return filter;
}
};
http.saml2Login().addObjectPostProcessor(processor);
Take a look at SAML 2.0 Login Overview for more detail about the flow.

using wss4jsecurityinterceptor for spring security- Configuring securement for signature and encryption with two keys

I am trying to add interceptors for securing spring-ws by reading this tutorial at https://memorynotfound.com/spring-ws-certificate-authentication-wss4j/
I need to use two seperate public-private keys (one for signing,second for encryption) in a single keystore(server.jks- file).But i am not able to configure the security interceptor.
It works fine as in example if use a single keystore , but how should i set the following when seperate keys for signing and encryption
#Bean
public KeyStoreCallbackHandler securityCallbackHandler(){
KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler();
callbackHandler.setPrivateKeyPassword("changeit");
return callbackHandler;
}
#Bean
public Wss4jSecurityInterceptor securityInterceptor() throws Exception {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
// validate incoming request
securityInterceptor.setValidationActions("Timestamp Signature Encrypt");
securityInterceptor.setValidationSignatureCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationDecryptionCrypto(getCryptoFactoryBean().getObject());
securityInterceptor.setValidationCallbackHandler(securityCallbackHandler());
// encrypt the response
securityInterceptor.setSecurementEncryptionUser("client-public");
securityInterceptor.setSecurementEncryptionParts("{Content}{https://memorynotfound.com/beer}getBeerResponse");
securityInterceptor.setSecurementEncryptionCrypto(getCryptoFactoryBean().getObject());
// sign the response
securityInterceptor.setSecurementActions("Signature Encrypt");
securityInterceptor.setSecurementUsername("server");
securityInterceptor.setSecurementPassword("changeit");
securityInterceptor.setSecurementSignatureCrypto(getCryptoFactoryBean().getObject());
return securityInterceptor;
}
#Bean
public CryptoFactoryBean getCryptoFactoryBean() throws IOException {
CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
cryptoFactoryBean.setKeyStorePassword("changeit");
cryptoFactoryBean.setKeyStoreLocation(new ClassPathResource("server.jks"));
return cryptoFactoryBean;
}
For encryption we have the method setSecurementEncryptionUser, but how do we configure setValidationDecryptionCrypto and setValidationSignatureCrypto with the alias to decrypt/validate
Could you try having 2 securityInterceptor with 2 keystores? One for signature and one for encryption. Then add both interceptors to the list of interceptors.
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
try {
interceptors.add(signatureSecurityInterceptor());
interceptors.add(encryptionSecurityInterceptor());
} catch (Exception e) {
throw new RuntimeException("could not initialize security interceptor");
}
}

Spring Boot Oauth2 Validating Access Token for Resource Owner Password Credentials Grant

I'm writing a filter that would intercept an Restful API call , extract a Bearer token and make a call to an Authorization Server for validation.
I couldn't find one in Spring Boot that does it out of the box, but I'm sure there is a cleaner way to do this.
here is what I have (pseudo code):
public class SOOTokenValidationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String xAuth = request.getHeader("Authorization");
// validate the value in xAuth
if(isValid(xAuth) == false){
throw new SecurityException();
}
// Create our Authentication and set it in Spring
Authentication auth = new Authentication ();
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
}
private boolean isValid (String token){
// make a call to SSO passing the access token and
// return true if validated
return true;
}
}
Lessons learned, Spring Security Oauth2 documentation is woefully inadequate, forget about trying to use the framework without fully combing through the source code. On the flip side the code is well written and easy to follow kudos to Dave Syer.
Here is my config:
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/")
.permitAll()
.and()
.addFilterBefore(getOAuth2AuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling();
}
Here is my getOAuth2AuthenticationProcessingFilter method:
private OAuth2AuthenticationProcessingFilter getOAuth2AuthenticationProcessingFilter() {
// configure token Extractor
BearerTokenExtractor tokenExtractor = new BearerTokenExtractor();
// configure Auth manager
OAuth2AuthenticationManager manager = new OAuth2AuthenticationManager();
// configure RemoteTokenServices with your client Id and auth server endpoint
manager.setTokenServices(remoteTokenServices);
OAuth2AuthenticationProcessingFilter filter = new OAuth2AuthenticationProcessingFilter();
filter.setTokenExtractor(tokenExtractor);
filter.setAuthenticationManager(manager);
return filter;
}

What login name to use for Spring LDAP authentication

I created a local LDAP server and added the user "djiao" with password "123456
Trying to implement authentication with Spring Security with Spring Boot. My webconfig class is as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("", "ldap://localhost:10389");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
#Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
However I can't seem to login from the login page.
If I use djiao (cn) or djiao1 (uid), I will get 500.
[LDAP: error code 34 - Incorrect DN given : djiao1 (0x64 0x6A 0x69 0x61 0x6F 0x31 ) is invalid]; nested exception is javax.naming.InvalidNameException: [LDAP: error code 34 - Incorrect DN given : djiao1 (0x64 0x6A 0x69 0x61 0x6F 0x31 ) is invalid]
If I use dn "cn=djiao,ou=Users,dc=example,dc=com" as the username I will get "Bad credentials" error. And the password is simply 123456.
What should the username for login? Or am I missing something in websecurityconfig class?
Since from your code I could identify that you're using Spring-Boot.
This is what was working for us connecting to LDAP
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
authBuilder
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.userSearchBase("dc=some,dc=domain,dc=com")
.groupSearchBase("ou=groups,dc=some,dc=domain,dc=com")
.groupSearchFilter("member={0}")
.contextSource()
.url("ldaps://<ldap-server>")
.port(639)
.managerDn("cn=binduser,ou=users,dc=some,dc=domain,dc=com")
.managerPassword("some pass")
;
}
}
So in essence going for the userSearchFilter you'd have to define different values. If you use any LDAP besides AD your filter should by "(uid={0})" or if you wan't people to be able to use the email you could also go for "(mail={0})" or a combination like "(|(uid={0})(mail={0}))" which woul allow to use both.
If you go for ActiveDirectory – which I assume you do not based on what you have written above – it should be the sAMAccountName as stated above to allow people to just enter their ID in the domain like MYDOMAIN\myusername so the login would just be myusername.
If you need to connect to multiple LDAP-Server who share the same information for HA purposes you can do this through the .contextSource().url() call. If they carry different ones, e.g. 'EMEA', 'US', 'AP' you can combine these calls using:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
authBuilder
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.userSearchBase("dc=emea,dc=domain,dc=com")
.groupSearchBase("ou=groups,dc=emea,dc=domain,dc=com")
.groupSearchFilter("member={0}")
.contextSource()
.url("ldaps://<emea-ldap-server>")
.port(639)
.managerDn("cn=binduser,ou=users,dc=emea,dc=domain,dc=com")
.managerPassword("some pass")
.and()
.and()
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.userSearchBase("dc=ap,dc=domain,dc=com")
.groupSearchBase("ou=groups,dc=ap,dc=domain,dc=com")
.groupSearchFilter("member={0}")
.contextSource()
.url("ldaps://<ap-ldap-server>")
.port(639)
.managerDn("cn=binduser,ou=users,dc=ap,dc=domain,dc=com")
.managerPassword("some pass")
;
}
BTW: this also allows you to combine different authentication mechanisms like InMemory (Default-Admin-Backdoor) with LDAP and/or JDBC.

Securing exclusively the REST access to a Spring Data Rest Repository

I'm using Spring Data Rest to expose a repository. I'm using #PreAuthorize and #PostFilter to restrict the access to the REST end points to exclusively admin users and filter the results.
#PreAuthorize("hasRole('ROLE_ADMIN')")
#PostFilter("hasPermission(filterObject, 'read')
public interface SomeRepository extends CrudRepository<SomeEntity, Long> {
}
At the same time I have another Controller that doesn't require any authentication but is using the repository.
#Controller
public class SomeController {
#Autowired
SomeRepository repository;
#RequestMapping(value = "/test")
public ResponseEntity test () {
// Do something
repository.findAll();
// Do something else
}
}
This doesn't work because the user that send the request to "/test" is not admin so it doesn't have access to the repository.
My question is, it is possible to add security exclusively to the REST interface of the repository and not when the repository is used internally in the application?
Thanks
Please evaluate these possibilities:
Security checks in REST event handlers
Adding custom repository methods for internal use
Using RunAsManager (or temporarily switching SecurityContext to perform a privileged operation)
Securing modifying requests using REST event handlers:
#Service
#RepositoryEventHandler
public class FooService {
/**
* Handles before-* events.
*/
#HandleBeforeCreate
#HandleBeforeSave
#HandleBeforeDelete
#PreAuthorize("hasRole('ADMIN')")
public void onBeforeModify(final Foo entity){
// noop
}
/**
* Handles before-* events.
*/
#HandleBeforeLinkSave
#HandleBeforeLinkDelete
#PreAuthorize("hasRole('ADMIN')")
public void onBeforeModifyLink(final Foo entity, final Object linked){
// noop
}
}
Securing standard CRUD methods while adding non-secure custom methods on repository for internal use:
public interface FooDao extends CrudRepository<Foo, Long> {
#Override
#PreAuthorize("hasRole('ADMIN')")
<S extends Foo> S save(final S entity);
/**
* Saves entity without security checks.
*/
#Transactional
#Modifying
default <S extends Foo> S saveInternal(final S entity) {
return save(entity);
}
}
One solution would be to remove the #PreAuthorize annotation from your repository interface, and in a configuration class, extend WebSecurityConfigAdaptor and override the configure(HttpSecurity security) method. From here you can use AntMatchers to impose access restrictions to the REST endpoints as required. For example:
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/someEntities/**").hasRole('ADMIN')
.anyRequest().permitAll();
}
See http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-httpsecurity for more details.
I ran into the same problem and came up with a workaround that doesn't feel completely right but does its job for the time being.
I basically created a security utils bean which can be used to check if a method was called internally or externally using the Spring Data REST API (remark: my repositories are prefixed /api/, if you have another prefix you need to change the regex accordingly).
#Component("securityUtils")
public class SecurityUtils {
public boolean isRestRequest(){
HttpServletRequest r = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return Pattern.matches("^/api/", UrlUtils.buildRequestUrl(r));
}
}
To make this work, you need to add the following line to your listeners in the web.xml:
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
And use the method in your expression based access control like so (where the last line in the expression allows you to use the save method from any controller methods that are mapped against URLs which do not start with /api/:
#Override
#PreAuthorize("hasRole('ROLE_ADMINISTRATOR') " +
"or hasPermission(#user, 'WRITE') " +
"or !#securityUtils.isRestRequest()")
<S extends User> S save(#P("user") S user);
Caveats:
You cannot use this when you want to expose custom functionality over the /api route as this is merely a simple regex check against the route
The check has to be explicitly added to each repository or repository method for which you want to omit the authorization check internally (might be an advantage as well)
In my opinion the right solution would be to have two Repositories, one that is called EntityRepository and one SecuredEntityRepository.
Example:
#RestResource(exported = false)
public abstract interface CustomerRepository extends JpaRepository<Customer, Long> {
}
and the secured version:
#RestResource(exported = true)
public abstract interface SecuredCustomerRepository extends CustomerRepository {
#Override
#PreAuthorize("#id == principal.customer.id or hasAuthority('ADMIN_CUSTOMER_ONE')")
public Customer findOne(#Param("id") Long id);
#Override
#Query("SELECT o FROM #{#entityName} o WHERE o.id = ?#{principal.customer.id} or 1 = ?#{ hasAuthority('ADMIN_CUSTOMER_LIST') ? 1 : 0 }")
public Page<Customer> findAll(Pageable pageable);
#Override
#SuppressWarnings("unchecked")
#PreAuthorize("#customer.id == principal.customer.id or hasAuthority('ADMIN_CUSTOMER_SAVE')")
public Customer save(#P("customer") Customer customer);
#Override
#PreAuthorize("hasAuthority('ADMIN_CUSTOMER_DELETE')")
public void delete(#Param("id") Long id);
#Override
#PreAuthorize("hasAuthority('ADMIN_CUSTOMER_DELETE')")
public void delete(Customer customer);
}
This is currently not possible due to an issue with the auto-wiring mechanism in SD REST: https://jira.spring.io/browse/DATAREST-923
Sure. Just change the location of the #PreAuthorize annotation. This annotation can be placed in classes or single methods.
For example
#Controller
public class SomeController {
#Autowired
SomeRepository repository;
#RequestMapping(value = "/test")
#PreAuthorize(....)
public ResponseEntity test () {
// Do something
repository.findAll();
// Do something else
}
}
is perfectly legit (note the annotation on the test() method.
I decorated the repository class with this:
#PreAuthorize("hasRole('admin')")
It locked down everything.
Then whatever I wanted to enable for internal use but not rest, I decorated like this:
#Transactional
#Modifying
#PreAuthorize("hasRole('user')")
#RestResource(exported = false)
default <S extends SomeEntity> S saveInternal(final S entity) {
return save(entity);
}
And whatever I wanted to expose via the Rest interface (handpicked few) I exposed with something like this:
#PreAuthorize("(hasRole('user')) and
(#entity.user.username == principal.name)")
#Override
<S extends SomeEntity> S save(#Param("entity") S entity);
Note that this also validates that you are saving a record you are authorized to save.
I solved this problem by adding my own check
I created my AbstractHttpConfigurer class with global security. I have declared methods that can be public.
public class CommonSpringKeycloakTutorialsSecurityAdapter extends AbstractHttpConfigurer<CommonSpringKeycloakTutorialsSecurityAdapter, HttpSecurity> {
public static String[] PERMIT_ALL_URL = {"/api/user/createUser"};
#Override
public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer
// must be done in the init method
http
// disable csrf because of API mode
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// manage routes securisation here
.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
// manage routes securisation here
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/swagger-ui.html*", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.antMatchers(PERMIT_ALL_URL).permitAll()
.anyRequest().authenticated();
}
Then I created my own check based on global permissions.
#Component("securityUtils")
public class SecurityUtils {
public boolean isPermitRestRequest(){
HttpServletRequest r = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String currentUrl = UrlUtils.buildRequestUrl(r);
for(String url: CommonSpringKeycloakTutorialsSecurityAdapter.PERMIT_ALL_URL) {
if(currentUrl.equals(url)) {
return true;
}
}
return false;
}
}
For native validation to work, include a listener
#WebListener
public class MyRequestContextListener extends RequestContextListener {
}
In my team we evaluated several of the answers in this post and they didn't fit to our scenario.
A variation of Johannes Hiemer answer worked for us. We configured Spring Data REST to only expose annotated repositories:
data.rest:
detection-strategy: annotated
Then we defined 2 repositories without hierarchical relationship.
One of the repos will be exposed by adding the #RepositoryRestResource annotation to it. For this one, we deny access to every method by default so auth will have to be specified on a method level to reduce the chances of exposing methods by mistake. For example, initially we extended CrudRepository and didn't want to expose the deletion operation:
#RepositoryRestResource
#PreAuthorize("denyAll()")
interface SomeRestResourceRepository : Repository<SomeEntity, Long> {
}
The repository to be used for internal calls is defined as a regular Spring Data Repository:
interface SomeRepository : Repository<SomeEntity, Long> {
}
We are using spring-boot-starter-data-rest 2.6.3.

Resources