From 7a738680096f63782d2367411604a146163349c4 Mon Sep 17 00:00:00 2001 From: Paurav Munshi Date: Thu, 23 Apr 2020 02:03:43 -0400 Subject: [PATCH] Adding client credentials authorization filter - Adding a templated OAuth filter which performs following actions - Identifying if support a request using RequestMatcher - Validating the request using RequestValidator - Converting the request to Authentication using AuthenticationConverter - Performing the authenticaion using supplied AuthenticaionManager - Adding a configuration which will build the templated OAuth filter to work as Client Credentials filter using custom validators, converters, matchers and auth managers --- build.gradle | 2 + .../authz/server}/Application.java | 2 +- .../AuthorizationFilterConfigurations.java | 105 ++++++++++++++++ .../authz/server/config/OAuthConstants.java | 6 + .../server/config/SecurityConfigurer.java | 48 +++++++ .../OAuthGrantBasedAuthenticationFilter.java | 119 ++++++++++++++++++ .../filter/matcher/GrantTypeReqMatcher.java | 26 ++++ .../ClientCredentialRequestValidator.java | 28 +++++ .../filter/validator/RequestValidator.java | 9 ++ .../filter/validator/ValidationException.java | 7 ++ 10 files changed, 351 insertions(+), 1 deletion(-) rename src/main/java/{sample => org/springframework/authz/server}/Application.java (95%) create mode 100644 src/main/java/org/springframework/authz/server/config/AuthorizationFilterConfigurations.java create mode 100644 src/main/java/org/springframework/authz/server/config/OAuthConstants.java create mode 100644 src/main/java/org/springframework/authz/server/config/SecurityConfigurer.java create mode 100644 src/main/java/org/springframework/authz/server/filter/OAuthGrantBasedAuthenticationFilter.java create mode 100644 src/main/java/org/springframework/authz/server/filter/matcher/GrantTypeReqMatcher.java create mode 100644 src/main/java/org/springframework/authz/server/filter/validator/ClientCredentialRequestValidator.java create mode 100644 src/main/java/org/springframework/authz/server/filter/validator/RequestValidator.java create mode 100644 src/main/java/org/springframework/authz/server/filter/validator/ValidationException.java diff --git a/build.gradle b/build.gradle index 8bd97c3a9..dcadca05a 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } diff --git a/src/main/java/sample/Application.java b/src/main/java/org/springframework/authz/server/Application.java similarity index 95% rename from src/main/java/sample/Application.java rename to src/main/java/org/springframework/authz/server/Application.java index e376b275d..1f81a9e7f 100644 --- a/src/main/java/sample/Application.java +++ b/src/main/java/org/springframework/authz/server/Application.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package sample; +package org.springframework.authz.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/org/springframework/authz/server/config/AuthorizationFilterConfigurations.java b/src/main/java/org/springframework/authz/server/config/AuthorizationFilterConfigurations.java new file mode 100644 index 000000000..4489c39f2 --- /dev/null +++ b/src/main/java/org/springframework/authz/server/config/AuthorizationFilterConfigurations.java @@ -0,0 +1,105 @@ +package org.springframework.authz.server.config; + +import java.util.Arrays; +import java.util.Collections; + +import org.springframework.authz.server.filter.OAuthGrantBasedAuthenticationFilter; +import org.springframework.authz.server.filter.matcher.GrantTypeReqMatcher; +import org.springframework.authz.server.filter.validator.ClientCredentialRequestValidator; +import org.springframework.authz.server.filter.validator.RequestValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.BeanIds; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.www.BasicAuthenticationConverter; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.util.matcher.RequestMatcher; + +@Configuration +public class AuthorizationFilterConfigurations { + + @Autowired + RequestMatcher clientCredReqGrantMatcher; + + @Autowired + PasswordEncoder passwordEncoder; + + @Autowired + UserDetailsService clientCredentialsUserDetailsService; + + @Autowired + AuthenticationManager clientCredentialsAuthenticationManager; + + @Autowired + AuthenticationFailureHandler clientCredentialAuthFailureHandler; + + @Autowired + RequestValidator clientCredentialRequestValidator; + + @Bean + public OAuthGrantBasedAuthenticationFilter clientCredentialsFilter() { + + OAuthGrantBasedAuthenticationFilter clientCredentialsFilter = new OAuthGrantBasedAuthenticationFilter(); + clientCredentialsFilter.setAuthenticationManager(clientCredentialsAuthenticationManager); + clientCredentialsFilter.setAuthenticationFailureHandler(clientCredentialAuthFailureHandler); + clientCredentialsFilter.setReqConverter(new BasicAuthenticationConverter()); + clientCredentialsFilter.setReqGrantMatcher(clientCredReqGrantMatcher); + clientCredentialsFilter.setReqValidator(clientCredentialRequestValidator); + + return clientCredentialsFilter; + + } + + @Bean + public UserDetailsService clientCredentialsUserDetailsService() { + InMemoryUserDetailsManager ccUserDetailsService = new InMemoryUserDetailsManager(); + ccUserDetailsService.createUser(new User("registered_client", passwordEncoder.encode("client_secret"), Collections.EMPTY_SET)); + return ccUserDetailsService; + } + + @Bean + public AuthenticationManager clientCredentialsAuthenticationManager() + { + DaoAuthenticationProvider ccAuthProvider = new DaoAuthenticationProvider(); + ccAuthProvider.setUserDetailsService(clientCredentialsUserDetailsService); + ccAuthProvider.setPasswordEncoder(passwordEncoder); + ProviderManager ccAuthManager = new ProviderManager(Arrays.asList(ccAuthProvider)); + return ccAuthManager; + } + + + @Bean + public RequestMatcher clientCredReqGrantMatcher() { + + GrantTypeReqMatcher grantReqMatcher = new GrantTypeReqMatcher(new String[] {OAuthConstants.CLIENT_CRED_GRANT}); + return grantReqMatcher; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationFailureHandler clientCredentialAuthFailureHandler() { + BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint(); + AuthenticationEntryPointFailureHandler ccAuthFailureHandler = new AuthenticationEntryPointFailureHandler(basicAuthEntryPoint); + return ccAuthFailureHandler; + } + + @Bean + public RequestValidator clientCredentialRequestValidator() { + return new ClientCredentialRequestValidator(); + } + +} diff --git a/src/main/java/org/springframework/authz/server/config/OAuthConstants.java b/src/main/java/org/springframework/authz/server/config/OAuthConstants.java new file mode 100644 index 000000000..8f478fc05 --- /dev/null +++ b/src/main/java/org/springframework/authz/server/config/OAuthConstants.java @@ -0,0 +1,6 @@ +package org.springframework.authz.server.config; + +public class OAuthConstants { + public static final String GARNT_TYPE_PARAM = "grant_type"; + public static final String CLIENT_CRED_GRANT = "client_credentials"; +} diff --git a/src/main/java/org/springframework/authz/server/config/SecurityConfigurer.java b/src/main/java/org/springframework/authz/server/config/SecurityConfigurer.java new file mode 100644 index 000000000..3ac88f29d --- /dev/null +++ b/src/main/java/org/springframework/authz/server/config/SecurityConfigurer.java @@ -0,0 +1,48 @@ +package org.springframework.authz.server.config; + +import org.springframework.authz.server.filter.OAuthGrantBasedAuthenticationFilter; +import org.springframework.authz.server.filter.matcher.GrantTypeReqMatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.www.BasicAuthenticationConverter; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; + +@EnableWebSecurity +public class SecurityConfigurer extends WebSecurityConfigurerAdapter { + + @Autowired + private OAuthGrantBasedAuthenticationFilter clientCredentialsFilter; + + @Autowired + private RequestMatcher clientCredReqGrantMatcher; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http.authorizeRequests() + .antMatchers("/token").permitAll(); + + http.addFilterBefore(clientCredentialsFilter, BasicAuthenticationFilter.class); + // BasicAuthenticationFilter.class); + + /*http.authorizeRequests() + .antMatchers("/.well-known/openid-configuration").permitAll() + .antMatchers("/authorize").authenticated() + .and() + .formLogin();*/ + + + } + + + +} diff --git a/src/main/java/org/springframework/authz/server/filter/OAuthGrantBasedAuthenticationFilter.java b/src/main/java/org/springframework/authz/server/filter/OAuthGrantBasedAuthenticationFilter.java new file mode 100644 index 000000000..f623b23e9 --- /dev/null +++ b/src/main/java/org/springframework/authz/server/filter/OAuthGrantBasedAuthenticationFilter.java @@ -0,0 +1,119 @@ +package org.springframework.authz.server.filter; + +import java.io.IOException; +import java.util.Optional; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.authz.server.filter.validator.RequestValidator; +import org.springframework.authz.server.filter.validator.ValidationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; + +public class OAuthGrantBasedAuthenticationFilter extends AbstractAuthenticationProcessingFilter implements InitializingBean { + + private static final String DEFAULT_TOKEN_ENDPOINT = "/token"; + + private static final RequestValidator SILENT_VALIDATOR = new SilentRequestValidator(); + + private AuthenticationConverter reqConverter; + private RequestMatcher reqGrantMatcher; + private RequestValidator reqValidator = SILENT_VALIDATOR; + + public RequestValidator getReqValidator() { + return reqValidator; + } + + public void setReqValidator(RequestValidator reqValidator) { + this.reqValidator = reqValidator; + } + + public AuthenticationConverter getReqConverter() { + return reqConverter; + } + + public void setReqConverter(AuthenticationConverter reqConverter) { + this.reqConverter = reqConverter; + } + + public RequestMatcher getReqGrantMatcher() { + return reqGrantMatcher; + } + + public void setReqGrantMatcher(RequestMatcher reqGrantMatcher) { + this.reqGrantMatcher = reqGrantMatcher; + } + + + + public OAuthGrantBasedAuthenticationFilter() { + super(DEFAULT_TOKEN_ENDPOINT); + + } + + public OAuthGrantBasedAuthenticationFilter(String endpoint) { + super(endpoint); + + } + + public void afterPropertiesSet() { + Assert.notNull(reqConverter, "RequestConverter cannot be null"); + Assert.notNull(reqGrantMatcher, "Request grant matcher cannot be null"); + Assert.notNull(reqValidator, "Request Validator cannot be null"); + } + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException, IOException, ServletException { + + validateRequest(request); + Authentication authenticationReq = reqConverter.convert(request); + + return Optional.ofNullable(authenticationReq) + .map(auth -> this.getAuthenticationManager().authenticate(auth)) + .orElseThrow(() -> new AuthenticationServiceException("Error Authenticating OAuth request")); + } + + private boolean validateRequest(HttpServletRequest request) throws AuthenticationException { + boolean isValid = false; + try { + isValid = reqValidator.isValidRequest(request); + }catch(ValidationException vexp) { + throw new AuthenticationServiceException(vexp.getMessage()); + } + + return isValid; + + } + + protected boolean requiresAuthentication(HttpServletRequest request, + HttpServletResponse response) { + boolean isRequired = super.requiresAuthentication(request, response); + if(isRequired) isRequired = reqGrantMatcher.matches(request); + return isRequired; + } + + static class SilentRequestValidator implements RequestValidator { + + @Override + public boolean isValidRequest(HttpServletRequest request) throws ValidationException { + // TODO Auto-generated method stub + return true; + } + + } + + + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/authz/server/filter/matcher/GrantTypeReqMatcher.java b/src/main/java/org/springframework/authz/server/filter/matcher/GrantTypeReqMatcher.java new file mode 100644 index 000000000..511af8eb1 --- /dev/null +++ b/src/main/java/org/springframework/authz/server/filter/matcher/GrantTypeReqMatcher.java @@ -0,0 +1,26 @@ +package org.springframework.authz.server.filter.matcher; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.authz.server.config.OAuthConstants; +import org.springframework.security.web.util.matcher.RequestMatcher; + +public class GrantTypeReqMatcher implements RequestMatcher{ + + private Set grantsAllowed = new HashSet(); + public GrantTypeReqMatcher(String[] allowedGrants) { + if(allowedGrants.length > 0) + Stream.of(allowedGrants).forEach(grant -> grantsAllowed.add(grant)); + } + + @Override + public boolean matches(HttpServletRequest request) { + String grantType = request.getParameter(OAuthConstants.GARNT_TYPE_PARAM); + return grantsAllowed.contains(grantType); + } + +} diff --git a/src/main/java/org/springframework/authz/server/filter/validator/ClientCredentialRequestValidator.java b/src/main/java/org/springframework/authz/server/filter/validator/ClientCredentialRequestValidator.java new file mode 100644 index 000000000..593a951d2 --- /dev/null +++ b/src/main/java/org/springframework/authz/server/filter/validator/ClientCredentialRequestValidator.java @@ -0,0 +1,28 @@ +package org.springframework.authz.server.filter.validator; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.logging.log4j.util.Strings; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; + +public class ClientCredentialRequestValidator implements RequestValidator{ + + private static final ValidationException AUTH_HEADER_ABSENT_EXP = new ValidationException("Authorization Header is absent from the request. Client Credential check cannot be performed"); + private static final ValidationException AUTH_HEADER_INVALID_TYPE_EXP = new ValidationException("Authorization Header authorization type should be Basic for Client Credentials check."); + + private static final String AUTHORIZATION_TYPE = "Basic"; + + @Override + public boolean isValidRequest(HttpServletRequest request) throws ValidationException { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + if(StringUtils.isEmpty(authorizationHeader)) + throw AUTH_HEADER_ABSENT_EXP; + + if (!StringUtils.startsWithIgnoreCase(authorizationHeader, AUTHORIZATION_TYPE)) + throw AUTH_HEADER_INVALID_TYPE_EXP; + + return true; + } + +} diff --git a/src/main/java/org/springframework/authz/server/filter/validator/RequestValidator.java b/src/main/java/org/springframework/authz/server/filter/validator/RequestValidator.java new file mode 100644 index 000000000..c1acfe8bd --- /dev/null +++ b/src/main/java/org/springframework/authz/server/filter/validator/RequestValidator.java @@ -0,0 +1,9 @@ +package org.springframework.authz.server.filter.validator; + +import javax.servlet.http.HttpServletRequest; + +public interface RequestValidator { + + public boolean isValidRequest(HttpServletRequest request) throws ValidationException; + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/authz/server/filter/validator/ValidationException.java b/src/main/java/org/springframework/authz/server/filter/validator/ValidationException.java new file mode 100644 index 000000000..af180971b --- /dev/null +++ b/src/main/java/org/springframework/authz/server/filter/validator/ValidationException.java @@ -0,0 +1,7 @@ +package org.springframework.authz.server.filter.validator; + +public class ValidationException extends Exception { + public ValidationException(String message) { + super(message); + } +}