AbstractSecurityInterceptor must provide a non-null AccessDecisionManager - spring-security

Since upgrade of Spring Security to 5.6.2 I have issues running my application as I keep getting:
Caused by: java.lang.IllegalArgumentException: AbstractSecurityInterceptor must provide a non-null AccessDecisionManager
at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.16.jar:5.3.16]
at org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator.<init>(DefaultWebInvocationPrivilegeEvaluator.java:54) ~[spring-security-web-5.6.2.jar:5.6.2]
at org.springframework.security.config.annotation.web.builders.WebSecurity.getRequestMatcherPrivilegeEvaluatorsEntry(WebSecurity.java:338) ~[spring-security-config-5.6.2.jar:5.6.2]
at org.springframework.security.config.annotation.web.builders.WebSecurity.performBuild(WebSecurity.java:305) ~[spring-security-config-5.6.2.jar:5.6.2]
at org.springframework.security.config.annotation.web.builders.WebSecurity.performBuild(WebSecurity.java:90) ~[spring-security-config-5.6.2.jar:5.6.2]
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:305) ~[spring-security-config-5.6.2.jar:5.6.2]
at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) ~[spring-security-config-5.6.2.jar:5.6.2]
Until now I did not need the AccessDecisionManager bean and everything worked like a charm like this:
#Configuration
#EnableWebSecurity
open class OpenApiSecurityConfig() : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.requestMatchers()
.antMatchers("/docs")
.and()
.addFilter(OpenApiFilter(authService))
}
open class OpenApiFilter(private val authService: AuthService) : FilterSecurityInterceptor() {
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
if (userAuthorized()) {
chain.doFilter(request, response)
} else {
throw AccessDeniedException("Forbidden.")
}
}
}
}
So I guess this is just a some kind of new requirement. I added the configuration as:
#Configuration
#Import(AccessManager::class)
#EnableWebSecurity
open class OpenApiSecurityConfig() : WebSecurityConfigurerAdapter() { … }
…
#Configuration
open class AccessManager : AccessDecisionManager {
override fun decide(authentication: Authentication, `object`: Any?, configAttributes: MutableCollection<ConfigAttribute>?) {}
override fun supports(attribute: ConfigAttribute?): Boolean = false
override fun supports(clazz: Class<*>?): Boolean = false
}
However with no effect.
Is it possible to avoid the need for AccessManager?
What is the correct way of instantiating it?

This issue stems from creating a custom FilterSecurityInterceptor.
This filter is not meant to be replaced in the filter chain.
It would be best to create a different type of custom filter and insert it before the FilterSecurityInterceptor. For example, it could extend OncePerRequestFilter and instead of throwing an AccessDeniedException if the user is unauthorized it could simply return.

Related

Customizing OpenSaml4AuthenticationProvider in Spring Security SAML2

I need to use a legacy UserDetailsService with Spring Security SAML2, so I'm following these instructions from Spring. However, I get an error when I just try to replace the AuthenticationProvider with the supposedly "default" one according to that documentation:
public class WigWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
// I've tried removing these 2 lines and I get the same error
authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider.createDefaultAssertionValidator());
authenticationProvider.setResponseAuthenticationConverter(OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter());
httpSecurity.authorizeRequests(authz -> authz.anyRequest().authenticated())
.saml2Login(saml2 -> saml2.authenticationManager(new ProviderManager(authenticationProvider)));
}
}
When I do this, I get the following error when I try to authenticate:
java.lang.NoSuchMethodError: org.opensaml.saml.saml2.assertion.SAML20AssertionValidator.<init>(Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;Lorg/opensaml/saml/saml2/assertion/AssertionValidator;Lorg/opensaml/xmlsec/signature/support/SignatureTrustEngine;Lorg/opensaml/xmlsec/signature/support/SignaturePrevalidator;)V
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider$SAML20AssertionValidators$3.<init>(OpenSaml4AuthenticationProvider.java:732)
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider$SAML20AssertionValidators.<clinit>(OpenSaml4AuthenticationProvider.java:731)
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.lambda$createDefaultAssertionSignatureValidator$8(OpenSaml4AuthenticationProvider.java:572)
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.lambda$createAssertionValidator$11(OpenSaml4AuthenticationProvider.java:654)
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.process(OpenSaml4AuthenticationProvider.java:495)
at org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.authenticate(OpenSaml4AuthenticationProvider.java:448)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
at org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter.attemptAuthentication(Saml2WebSsoAuthenticationFilter.java:113)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:222)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
But when I use the same code without setting the authenticationManager, then the SAML authentication works fine. (Any page that wants to use my custom UserDetails fails of course, because it's not being populated, but all the SAML authentication steps are working fine.):
public class WigWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests(authz -> authz.anyRequest().authenticated())
.saml2Login();
}
}
It turns out that I was using org.opensaml:opensaml-api 3.4.6, and you need to be using 4.x to use the class OpenSaml4AuthenticationProvider. If you're using 3.x you need to use the deprecated class OpenSamlAuthenticationProvider. I wasn't able to upgrade the opensaml dependency because I'm using Java 8, so this is the code that works for me:
public class WigWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// This class is deprecated, but you have to use it if you're using OpenSAML < 4.0
OpenSamlAuthenticationProvider authenticationProvider = new OpenSamlAuthenticationProvider();
authenticationProvider.setAssertionValidator(OpenSamlAuthenticationProvider.createDefaultAssertionValidator());
authenticationProvider.setResponseAuthenticationConverter(OpenSamlAuthenticationProvider.createDefaultResponseAuthenticationConverter());
httpSecurity.authorizeRequests(authz -> authz.anyRequest().authenticated())
.saml2Login(saml2 -> saml2.authenticationManager(new ProviderManager(authenticationProvider)));
}
}
I finally found the answer when I discovered that that is what Saml2LoginConfigurer does internally.

Integrated Swagger and previously working code breaks: cannot deserialize from Object value (no delegate- or property-based Creator

I successfully integrated swagger to several spring boot services.
Had to allow the endpoints to bypass authentication by adding in respective #EnableWebSecurity class that extends WebSecurityConfigurerAdapter (this had worked for other services fine) :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1)
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher("/**")
...
.antMatchers("/actuator/**").permitAll()
.antMatchers("/v2/api-docs", "/configuration/**", "/webjars/**","/swagger*/**") // ADDED THIS for swagger
.permitAll() // ADDED THIS for swagger
.antMatchers("/challenge").permitAll()
.antMatchers("/token").permitAll() // ENDPOINT with complaint now, that was previously ok.
.anyRequest()
.authenticated()
.and()
.cors();
}
...
}
For a specific one, however, once I added the relevant swagger code & dependencies, it seems to have broken and complains on what was working initially.
this is endpoint with the complaint :
#PostMapping("/token")
public ResponseDto token(#Valid #RequestBody TokenRequest request) {
try {
return service.generateJwtFromCode(request.getId(), request.getCode());
}
...
catch (Exception exception) {..
}
}
nested exception is on no constructor found for this class:
#AllArgsConstructor
public class TokenRequest {
#NotEmpty
#JsonProperty
private final String id;
#NotEmpty
#Getter
private final String code;
public UUID getId() {
return UUID.fromString(id);
}
}
Could not resolve parameter [0] in responseDTO Controller.token(Service.TokenRequest): Type definition error: [simple type, class TokenRequest]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `service.TokenRequest` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (PushbackInputStream); line: 1, column: 2]
o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class service.TokenRequest]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `service.TokenRequest` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (PushbackInputStream); line: 1, column: 2]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in …
Not sure what it has to do with swagger integration. If i remove swagger integration code, it works fine with the same code, and doesn't complain about the type-conversion failure.
For resolving this, I also took someone's suggestion of
upgrading dependency for com.fasterxml.jackson.core
and rebuilding the code. But still no success.
compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.11.2'
Things I have tried but didn't resolve:
Added a default/empty constructor
(for most others with similar problem it worked by that, for me it complaint thereafter on
error: variable id might not have been initialized
}
Added this to the tokenRequest class:
#Value
#AllArgsConstructor(onConstructor = #__(#JsonCreator(mode = JsonCreator.Mode.PROPERTIES))
Had a different error:
c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class TokenRequest]]
InvalidDefinitionException: Invalid type definition for type `TokenRequest`: More than one argument (#0 and #1) left as delegating for Creator [constructor for TokenRequest, annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=#com.fasterxml.jackson.annotation.JsonCreator(mode=DELEGATING)}]: only one allowed
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62)...
Solution was to add a default constructor AND also remove final variables.
#AllArgsConstructor
public class TokenRequest {
#NotEmpty
#JsonProperty
private String id; // code fixed issue
#NotEmpty
#Getter
private String code; // code fixed issue
public TokenRequest(){} // code fixed issue
public UUID getId() {
return UUID.fromString(id);
}
}

#ConditionalOnBean(ClientRegistrationService::class) fails to match JdbcClientDetailsService

I'm writing a REST controller that exposes CRUD operations based on the type of OAuth2 services beans that are found, something like this:
#Bean
#ConditionalOnBean(ClientDetailsService::class)
fun clientServiceController(
clientDetailsService: ClientDetailsService
): ClientDetailsServiceController {
return ClientDetailsServiceController(clientDetailsService)
}
#Bean
#ConditionalOnBean(ClientRegistrationService::class)
fun clientRegistrationServiceController(
clientRegistrationService: ClientRegistrationService
): ClientRegistrationServiceController {
return ClientRegistrationServiceController(clientRegistrationService)
}
I want to only register a controller that exposes ClientDetailsService if we do not have a ClientRegistrationService. If it does exist, to additionally register a controller for the methods in that interface.
One of our modules that registers these controllers, also registers a JdbcClientDetailsService bean, which implements both interfaces. Yet, the #ConditionalOnBean(ClientRegistrationService::class) fails to match it, so only the first bean is created by not the second.
This is an example of how we declare the JdbcClientDetailsService:
#Bean
fun jdbcClientDetailsService(
passwordEncoder: PasswordEncoder,
dataSource: DataSource): JdbcClientDetailsService {
return JdbcClientDetailsService(dataSource).apply { setPasswordEncoder(passwordEncoder) }
}
The odd thing is that #Autowired ClientRegistrationService does successfully inject JdbcClientDetailsService.
What am I missing? How can I declare a bean that implements both interfaces, and match correctly against the conditionals? Is there a work around?
I succeed to get around this with the following:
#Bean
#Lazy
#Scope(proxyMode = ScopedProxyMode.INTERFACES)
public ClientRegistrationService registrationDetailsService(ClientDetailsServiceConfigurer configurer)
throws Exception {
ClientDetailsService built = configurer.and().build();
if (built instanceof ClientRegistrationService) {
return (ClientRegistrationService) built;
} else {
throw new IllegalStateException(built + " is not instanceof " + ClientRegistrationService.class);
}
}
It applies the same pattern as ClientDetailsServiceConfiguration, and rely on the same configurer.
We might get ride of '#Scope(proxyMode = ScopedProxyMode.INTERFACES)' if you want to retrieve an actual JdbcClientDetailsService

Can't override onAuthenticationSuccess method of AuthenticationSuccessHandler

Following some other posts, I tried to override the authentication success method of the spring-security handler, but it's never being called. My code looks like:
src/groovy/mypackage/MyAuthenticationSuccessHandler.groovy:
package mypackage
import org.springframework.security.core.Authentication
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
public MyAuthenticationSuccessHandler() {
println("constructed!")
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
println("override called")
super.onAuthenticationSuccess(request, response, authentication);
}
}
resources.groovy:
authenticationSuccessHandler(MyAuthenticationSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
}
There are no errors, the constructor is definitely called and MyAuthenticationSuccessHandler is injected into a test controller, but onAuthenticationSuccess is never called. I dropped a breakpoint into the superclass version and that worked. I also tried rewriting my custom class in java but that didn't work.
What am I doing wrong?
Turns out another login filter was already active and it was preventing the normal method from working. The filter in question is org.mitre.openid.connect.client.OIDCAuthenticationFilter and the workaround is to inject your success handler through that one e.g.:
authenticationSuccessHandler(apipulse.MyAuthenticationSuccessHandler) {
clientRegistrationTemplate = ref(clientRegistrationTemplate)
}
...
openIdConnectAuthenticationFilter(OIDCAuthenticationFilter) {
...
authenticationSuccessHandler = ref('authenticationSuccessHandler')
}
Just wasted a day looking at this - thanks a bunch, spring.

Spring Security Test : Testing the annotation #Secured (or #PreAuthorize)

I've got a problem (of course :)). I have a spring 4.2 application with Spring Security and Spring MVC (with Rest API) and I want to test the effectiveness of the annotation #Secured(ROLE_FOO) present on a REST method.
So I need to install spring-security-test library for this. OK.
Then I follow up some tutorials (or doc) like the official one : http://docs.spring.io/autorepo/docs/spring-security/4.1.0.RC1/reference/htmlsingle/
Here my test code (I'am trying to remove all "uneccessary" code.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest
public class UserResourceIntTest {
private MockMvc restUserMockMvc2;
#Autowired
private WebApplicationContext context;
....//Injection, Mocks declarations here
#Before
public void setup() {
this.restUserMockMvc2 = MockMvcBuilders.webAppContextSetup(context).apply(SecurityMockMvcConfigurers.springSecurity()).build();
}
#Test
#WithMockUser(roles="ROLE_VIEWER")
public void testGetUserListe() throws Exception {
//here getAuthentication() returns null !!! why ???
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// restUserMockMvc2.perform(get("/api/users/liste")
// .principal(SecurityContextHolder.getContext().getAuthentication()))
// .accept(MediaType.APPLICATION_JSON))
// .andExpect(status().isForbidden());
// .andExpect(content().contentType("application/json"));
}
Here the method I want to test :
#RestController
#RequestMapping("/api")
public class UserResource {
#RequestMapping(value = "/users/liste", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional(readOnly = true)
#Secured({ AuthoritiesConstants.TC_ADMIN })
public ResponseEntity<List<ManagedUserDTO>> getUserListe(Pageable pageable, Principal principal) throws URISyntaxException {
//doSomething...
}
Can you tell me WHY in my test,
SecurityContextHolder.getContext().getAuthentication()
returns null ??
#WithMockUser should authenticate a user automatically (the principal hence)
Thanks
EDIT1 : the setup part of the test (concerning only the security instruction) :
#Inject
private FilterChainProxy springSecurityFilterChain;
#Inject
private PageableHandlerMethodArgumentResolver pageableArgumentResolver;
#Before
public void setup() {
....
this.restUserMockMvc2 = MockMvcBuilders
.standaloneSetup(userResource2)
.alwaysDo(print()) .apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.setCustomArgumentResolvers(pageableArgumentResolver)
.build();
...
}
EDIT2 : just to be clear on the class definition :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#TestExecutionListeners(listeners={ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class})
public class UserResourceIntTest {
}
The problem is that Spring Security's WithSecurityContextTestExecutionListener is not being executed because #IntegrationTest is overriding the default TestExecutionListeners.
Most likely you don't need #IntegrationTest with MockMvc, so you should be able to remove it entirely and resolve your issue.
Alternatively you can resolve this by explicitly adding WithSecurityContextTestExecutionListener to your class like:
#TestExecutionListeners(listeners = { WithSecurityContextTestExecutionListener.class, IntegrationTestPropertiesListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class, SqlScriptsTestExecutionListener.class })
#IntegrationTest
public class UserResourceIntTest {

Resources