mirror of https://github.com/apache/nifi.git
NIFI-10217 Refactored Registry Spring Security Configuration
Signed-off-by: Nathan Gough <thenatog@gmail.com> This closes #6196.
This commit is contained in:
parent
6b83bb6a6c
commit
a4ea92ea3b
|
@ -33,28 +33,24 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NiFi Registry Web Api Spring security
|
* Spring Security Filter Configuration
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
|
public class NiFiRegistrySecurityConfig {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
|
private static final Logger logger = LoggerFactory.getLogger(NiFiRegistrySecurityConfig.class);
|
||||||
|
|
||||||
|
@ -67,28 +63,32 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
@Autowired
|
@Autowired
|
||||||
private Authorizer authorizer;
|
private Authorizer authorizer;
|
||||||
|
|
||||||
private final AnonymousIdentityFilter anonymousAuthenticationFilter = new AnonymousIdentityFilter();
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private X509IdentityProvider x509IdentityProvider;
|
private X509IdentityProvider x509IdentityProvider;
|
||||||
private IdentityFilter x509AuthenticationFilter;
|
|
||||||
private IdentityAuthenticationProvider x509AuthenticationProvider;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JwtIdentityProvider jwtIdentityProvider;
|
private JwtIdentityProvider jwtIdentityProvider;
|
||||||
private IdentityFilter jwtAuthenticationFilter;
|
|
||||||
private IdentityAuthenticationProvider jwtAuthenticationProvider;
|
|
||||||
|
|
||||||
private ResourceAuthorizationFilter resourceAuthorizationFilter;
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
|
||||||
public NiFiRegistrySecurityConfig() {
|
return http
|
||||||
super(true); // disable defaults
|
.addFilterBefore(x509AuthenticationFilter(), AnonymousAuthenticationFilter.class)
|
||||||
}
|
.addFilterBefore(jwtAuthenticationFilter(), AnonymousAuthenticationFilter.class)
|
||||||
|
// Add Resource Authorization after Spring Security but before Jersey Resources
|
||||||
@Override
|
.addFilterAfter(resourceAuthorizationFilter(), FilterSecurityInterceptor.class)
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
.anonymous().authenticationFilter(new AnonymousIdentityFilter()).and()
|
||||||
http
|
.csrf().disable()
|
||||||
|
.logout().disable()
|
||||||
.rememberMe().disable()
|
.rememberMe().disable()
|
||||||
|
.requestCache().disable()
|
||||||
|
.servletApi().disable()
|
||||||
|
.securityContext().disable()
|
||||||
|
.sessionManagement().disable()
|
||||||
|
.headers()
|
||||||
|
.xssProtection().and()
|
||||||
|
.contentSecurityPolicy("frame-ancestors 'self'").and()
|
||||||
|
.httpStrictTransportSecurity().maxAgeInSeconds(31540000).and()
|
||||||
|
.frameOptions().sameOrigin().and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(
|
.antMatchers(
|
||||||
"/access/token",
|
"/access/token",
|
||||||
|
@ -101,116 +101,55 @@ public class NiFiRegistrySecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
.anyRequest().fullyAuthenticated()
|
.anyRequest().fullyAuthenticated()
|
||||||
.and()
|
.and()
|
||||||
.exceptionHandling()
|
.exceptionHandling()
|
||||||
.authenticationEntryPoint(http401AuthenticationEntryPoint())
|
.authenticationEntryPoint(http401AuthenticationEntryPoint()).and()
|
||||||
.and()
|
.build();
|
||||||
.sessionManagement()
|
|
||||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
|
||||||
|
|
||||||
// Apply security headers for registry API. Security headers for docs and UI are applied with Jetty filters in registry-core.
|
|
||||||
http.headers().xssProtection();
|
|
||||||
http.headers().contentSecurityPolicy("frame-ancestors 'self'");
|
|
||||||
http.headers().httpStrictTransportSecurity().maxAgeInSeconds(31540000);
|
|
||||||
http.headers().frameOptions().sameOrigin();
|
|
||||||
|
|
||||||
// x509
|
|
||||||
http.addFilterBefore(x509AuthenticationFilter(), AnonymousAuthenticationFilter.class);
|
|
||||||
|
|
||||||
// jwt
|
|
||||||
http.addFilterBefore(jwtAuthenticationFilter(), AnonymousAuthenticationFilter.class);
|
|
||||||
|
|
||||||
// otp
|
|
||||||
// todo, if needed one-time password auth filter goes here
|
|
||||||
|
|
||||||
// add an anonymous authentication filter that will populate the authenticated,
|
|
||||||
// anonymous user if no other user identity is detected earlier in the Spring filter chain
|
|
||||||
http.anonymous().authenticationFilter(anonymousAuthenticationFilter);
|
|
||||||
|
|
||||||
// After Spring Security filter chain is complete (so authentication is done),
|
|
||||||
// but before the Jersey application endpoints get the request,
|
|
||||||
// insert the ResourceAuthorizationFilter to do its authorization checks
|
|
||||||
http.addFilterAfter(resourceAuthorizationFilter(), FilterSecurityInterceptor.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
auth
|
|
||||||
.authenticationProvider(x509AuthenticationProvider())
|
|
||||||
.authenticationProvider(jwtAuthenticationProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide Authentication Manager Bean to disable unnecessary UserDetailsServiceAutoConfiguration
|
|
||||||
* @return Authentication Manager
|
|
||||||
* @throws Exception Thrown when failing to initialize Authentication Manager
|
|
||||||
*/
|
|
||||||
@Bean
|
@Bean
|
||||||
@Override
|
public AuthenticationManager authenticationManager() {
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
return new ProviderManager(x509AuthenticationProvider(), jwtAuthenticationProvider());
|
||||||
return super.authenticationManagerBean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityFilter x509AuthenticationFilter() {
|
private IdentityFilter x509AuthenticationFilter() {
|
||||||
if (x509AuthenticationFilter == null) {
|
return new IdentityFilter(x509IdentityProvider);
|
||||||
x509AuthenticationFilter = new IdentityFilter(x509IdentityProvider);
|
|
||||||
}
|
|
||||||
return x509AuthenticationFilter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityAuthenticationProvider x509AuthenticationProvider() {
|
private IdentityAuthenticationProvider x509AuthenticationProvider() {
|
||||||
if (x509AuthenticationProvider == null) {
|
return new X509IdentityAuthenticationProvider(authorizer, x509IdentityProvider, identityMapper);
|
||||||
x509AuthenticationProvider = new X509IdentityAuthenticationProvider(authorizer, x509IdentityProvider, identityMapper);
|
|
||||||
}
|
|
||||||
return x509AuthenticationProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityFilter jwtAuthenticationFilter() {
|
private IdentityFilter jwtAuthenticationFilter() {
|
||||||
if (jwtAuthenticationFilter == null) {
|
return new IdentityFilter(jwtIdentityProvider);
|
||||||
jwtAuthenticationFilter = new IdentityFilter(jwtIdentityProvider);
|
|
||||||
}
|
|
||||||
return jwtAuthenticationFilter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityAuthenticationProvider jwtAuthenticationProvider() {
|
private IdentityAuthenticationProvider jwtAuthenticationProvider() {
|
||||||
if (jwtAuthenticationProvider == null) {
|
return new IdentityAuthenticationProvider(authorizer, jwtIdentityProvider, identityMapper);
|
||||||
jwtAuthenticationProvider = new IdentityAuthenticationProvider(authorizer, jwtIdentityProvider, identityMapper);
|
|
||||||
}
|
|
||||||
return jwtAuthenticationProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceAuthorizationFilter resourceAuthorizationFilter() {
|
private ResourceAuthorizationFilter resourceAuthorizationFilter() {
|
||||||
if (resourceAuthorizationFilter == null) {
|
return ResourceAuthorizationFilter.builder()
|
||||||
resourceAuthorizationFilter = ResourceAuthorizationFilter.builder()
|
|
||||||
.setAuthorizationService(authorizationService)
|
.setAuthorizationService(authorizationService)
|
||||||
.addResourceType(ResourceType.Actuator)
|
.addResourceType(ResourceType.Actuator)
|
||||||
.addResourceType(ResourceType.Swagger)
|
.addResourceType(ResourceType.Swagger)
|
||||||
.build();
|
.build();
|
||||||
}
|
|
||||||
return resourceAuthorizationFilter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationEntryPoint http401AuthenticationEntryPoint() {
|
private AuthenticationEntryPoint http401AuthenticationEntryPoint() {
|
||||||
// This gets used for both secured and unsecured configurations. It will be called by Spring Security if a request makes it through the filter chain without being authenticated.
|
// This gets used for both secured and unsecured configurations. It will be called by Spring Security if a request makes it through the filter chain without being authenticated.
|
||||||
// For unsecured, this should never be reached because the custom AnonymousAuthenticationFilter should always populate a fully-authenticated anonymous user
|
// For unsecured, this should never be reached because the custom AnonymousAuthenticationFilter should always populate a fully-authenticated anonymous user
|
||||||
// For secured, this will cause attempt to access any API endpoint (except those explicitly ignored) without providing credentials to return a 401 Unauthorized challenge
|
// For secured, this will cause attempt to access any API endpoint (except those explicitly ignored) without providing credentials to return a 401 Unauthorized challenge
|
||||||
return new AuthenticationEntryPoint() {
|
return (request, response, authenticationException) -> {
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
AuthenticationException authenticationException)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
// return a 401 response
|
// return a 401 response
|
||||||
final int status = HttpServletResponse.SC_UNAUTHORIZED;
|
final int status = HttpServletResponse.SC_UNAUTHORIZED;
|
||||||
logger.info("Client could not be authenticated due to: {} Returning 401 response.", authenticationException.toString());
|
logger.info("Client could not be authenticated due to: {} Returning 401 response.", authenticationException.toString());
|
||||||
logger.debug("", authenticationException);
|
logger.debug("", authenticationException);
|
||||||
|
|
||||||
if (!response.isCommitted()) {
|
if (!response.isCommitted()) {
|
||||||
response.setStatus(status);
|
response.setStatus(status);
|
||||||
response.setContentType("text/plain");
|
response.setContentType("text/plain");
|
||||||
response.getWriter().println(String.format("%s Contact the system administrator.", authenticationException.getLocalizedMessage()));
|
response.getWriter().println(String.format("%s Contact the system administrator.", authenticationException.getLocalizedMessage()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue