No AuthenticationProvider found using spring secur

2020-07-29 15:34发布

问题:

I have been trying to authenticate a user via LDAP using their x509 certificate and cannot seem to get it working. I have an authentication provider declared, but I am still getting an error thrown saying there is no provider.

Here is my debugged output:

INFO: Initiating Jersey application, version 'Jersey: 1.12 02/15/2012 04:51 PM'
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 1 of 8 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG: o.s.s.w.c.HttpSessionSecurityContextRepository - No HttpSession currently exists
DEBUG: o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: null. A new one will be created.
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 2 of 8 in additional filter chain; firing Filter: 'customX509AuthenticationFilter'
DEBUG: m.d.CustomX509AuthenticationFilter - Checking secure context token: null
DEBUG: m.d.CustomX509AuthenticationFilter - X.509 client authentication certificate: [removed to save space]
DEBUG: m.d.CustomX509PrincipalExtractor - Subject DN is 'CN=Last.First.M.1234567890, OU=GROUP1, O=ORG, C=US'
DEBUG: m.d.CustomX509PrincipalExtractor - Extracted Principal name is '1234567890'
DEBUG: m.d.CustomX509AuthenticationFilter - X.509 client authentication certificate: [removed to save space]
DEBUG: m.d.CustomX509AuthenticationFilter - preAuthenticatedPrincipal = 1234567890, trying to authenticate
DEBUG: m.d.CustomX509AuthenticationFilter - Cleared security context due to exception
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:196) ~[spring-security-core-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doAuthenticate(AbstractPreAuthenticatedProcessingFilter.java:115) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.doFilter(AbstractPreAuthenticatedProcessingFilter.java:85) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:184) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:155) [spring-security-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) [spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259) [spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) [catalina.jar:6.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [catalina.jar:6.0.29]
    at com.thetransactioncompany.cors.CORSFilter.doFilter(CORSFilter.java:205) [cors-filter-1.3.2.jar:na]
    at com.thetransactioncompany.cors.CORSFilter.doFilter(CORSFilter.java:266) [cors-filter-1.3.2.jar:na]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) [catalina.jar:6.0.29]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) [catalina.jar:6.0.29]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) [catalina.jar:6.0.29]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) [catalina.jar:6.0.29]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [catalina.jar:6.0.29]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [catalina.jar:6.0.29]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [catalina.jar:6.0.29]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298) [catalina.jar:6.0.29]
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857) [tomcat-coyote.jar:6.0.29]
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588) [tomcat-coyote.jar:6.0.29]
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489) [tomcat-coyote.jar:6.0.29]
    at java.lang.Thread.run(Thread.java:679) [na:1.6.0_22]
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 3 of 8 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 4 of 8 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 5 of 8 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG: o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 6 of 8 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG: o.s.s.w.s.SessionManagementFilter - Requested session ID E34E30E0411B02EFA37B41BCBA282041 is invalid.
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 7 of 8 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG: o.s.security.web.FilterChainProxy - /x509 at position 8 of 8 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG: o.s.s.w.a.i.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /x509; Attributes: [hasRole('user')]
DEBUG: o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
DEBUG: o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6b207925, returned: -1
DEBUG: o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.1.RELEASE.jar:3.1.1.RELEASE]
    ...

DEBUG: o.s.s.w.s.HttpSessionRequestCache - DefaultSavedRequest added to Session: DefaultSavedRequest[https://192.168.56.67/cert/x509]
DEBUG: o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
DEBUG: o.s.s.w.a.Http403ForbiddenEntryPoint - Pre-authenticated entry point called. Rejecting access
DEBUG: o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
DEBUG: o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed

Here is my spring-security.xml:

<http entry-point-ref="http403" use-expressions="true">
    <intercept-url pattern="/**" access="hasRole('user')" />
    <custom-filter position="PRE_AUTH_FILTER" ref="x509Filter" />
</http>

<global-method-security secured-annotations="enabled" />

<authentication-manager alias="authManager">
    <authentication-provider ref="daoAuthenticationProvider" />
</authentication-manager>

<beans:bean id="http403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />

<beans:bean id="x509Filter" class="CustomX509AuthenticationFilter">
    <beans:property name="authenticationManager" ref="authManager" />
    <beans:property name="principalExtractor">
        <beans:bean class="CustomX509PrincipalExtractor" />
    </beans:property>
</beans:bean>

<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <beans:constructor-arg>
        <beans:ref bean="authenticator" />
    </beans:constructor-arg>
    <beans:constructor-arg>
        <beans:ref local="populator" />
    </beans:constructor-arg>
    <beans:property name="hideUserNotFoundExceptions" value="false" />
</beans:bean>

<beans:bean id="authenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">
    <beans:constructor-arg>
      <beans:ref local="contextSource" />
    </beans:constructor-arg>
    <beans:property name="userSearch">
      <beans:ref local="userSearch" />
    </beans:property>
 </beans:bean>

<beans:bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <beans:constructor-arg value="ldap://localhost:389/dc=my,dc=domain" />
    <beans:property name="userDn" value="cn=manager,dc=my,dc=domain" />
    <beans:property name="password" value="password" />
</beans:bean>

<beans:bean id="populator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
    <beans:constructor-arg index="0">
        <beans:ref local="contextSource" />
    </beans:constructor-arg>
    <beans:constructor-arg index="1" value="ou=roles" />
    <beans:property name="groupRoleAttribute" value="cn" />
    <beans:property name="groupSearchFilter" value="(member={0})" />
    <beans:property name="rolePrefix" value="none" />
    <beans:property name="convertToUpperCase" value="false" />
    <beans:property name="searchSubtree" value="false" />
</beans:bean>

<beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
    <beans:constructor-arg index="0" value="ou=people" />
    <beans:constructor-arg index="1" value="(uid={0})" />
    <beans:constructor-arg index="2">
        <beans:ref local="contextSource" />
    </beans:constructor-arg>
</beans:bean>

<beans:bean id="userDetailsService" class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
    <beans:constructor-arg>
        <beans:ref local="userSearch" />
    </beans:constructor-arg>
    <beans:constructor-arg>
        <beans:ref local="populator" />
    </beans:constructor-arg>
</beans:bean>

I cannot figure out why the error is being thrown and have done a lot of research trying to get to the root of the problem. Any help would be greatly appreciated.

Thanks, James

回答1:

At a techical level the problem is that your authentication filter is incompatible with your authentication provider: your CustomX509AuthenticationFilter creates a PreAuthenticatedAuthenticationToken, and sends it to the configured LdapAuthenticationProvider to be processed, but that provider only supports UsernamePasswordAuthenticationTokens.

You might be able to solve the problem by making your CustomX509AuthenticationFilter create a UsernamePasswordAuthenticationToken instead, but there seems to be some conceptual problem here, I guess.

If a client provides a valid x509 certificate shouldn't you just trust it without further LDAP authentication? Even if you wanted this additional step, where do you get the password for the LDAP bind operation? Do you extract that from the certificate? If so, this won't give you any additional security, because if someone has the certificate, they will get authenticated anyway with or without LDAP.

Update after the comment from James:

It seems that all you need from LDAP is role information for the user identified by the certificate, in which case you don't need to authenticate it against LDAP. You only need to load user details using the LDAP manager account already configured in your contextSource. Try the following:

  1. Wrap your LdapUserDetailsService in a UserDetailsByNameServiceWrapper
  2. Instead of the LdapAuthenticationProvider configure a PreAuthenticatedAuthenticationProvider that will be able to process the PreAuthenticatedAuthenticationToken issued by your CustomX509AuthenticationFilter.
  3. Inject the wrapped LdapUserDetailsService into the PreAuthenticatedAuthenticationProvider.

This will make sure that the Authentication object gets populated with all information available from LDAP. Then subsequent filters can authorize the request based on the user's granted authorities (group memberships defined in LDAP).