Dean
Dean

Reputation: 887

Spring security fails JdbcClientDetailsService error

I am currently getting this error:

 org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

Which is handled by the DaoAuthenticationProvider however, it throws an InternalAuthenticationServiceException exception. This bypasses the exception handling of the ProviderManager, which does not call my customDaoProvider which would authenticate the user.

There are 2 DaoAuthenticationProvider registred with the ProviderManager. My observation is that on the first iteration of the ProviderManager, it selects DefaultAuthenticationProvider. I figure that is how it works, which would fail then the ProviderManager, on it's second Iteration would select my custom DaoAuthenticationProvider (customDaoProvider ) to verify the user credential. Thing is on the first iteration, when it fails it throws that specific error that the ProviderManager would rethrow.

This is inside the ProviderManager that would capture the specific error:

  try {
            result = provider.authenticate(authentication);

            if (result != null) {
                copyDetails(authentication, result);
                break;
            }
        } catch (AccountStatusException e) {
            prepareException(e, authentication);
            // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
            throw e;
        } catch (InternalAuthenticationServiceException e) {
            prepareException(e, authentication);
            throw e;
        } catch (AuthenticationException e) {
            lastException = e;
        }

Note the catch for InternalAuthenticationServiceException. Here it rethrow the exception and stop the authentication process.

My entire security.xml below (except the element):

<http pattern="/oauth/token" create-session="stateless"   authentication-manager-ref="authenticationManager" xmlns="http://www.springframework.org/schema/security">
    <intercept-url pattern="/oauth/token" access="ROLE_FRONTEND"  />
    <anonymous enabled="false" />
    <http-basic entry-point-ref="clientAuthenticationEntryPoint"  />

    <custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />

    <access-denied-handler ref="oauthAccessDeniedHandler" />
</http>


 <authentication-manager alias="authenticationManager"  xmlns="http://www.springframework.org/schema/security">
    <sec:authentication-provider user-service-ref="clientDetailsUserService" />
    <sec:authentication-provider ref="customDaoProvider">
    </sec:authentication-provider>
</authentication-manager>


<beans:bean id="customUserDetailService" class="com.xxxx.api.services.UserDetailsServiceImpl">
    <beans:property name="accountService" ref="userAccountServiceImpl"></beans:property>
</beans:bean>

<!-- DAO Providers -->
<beans:bean id="customDaoProvider" class="com.xxxx.api.daoauthproviders.CustomDaoAuthenticationProvider">
    <beans:property name="userDetailsService" ref="customUserDetailService"/>
    <beans:property name="passwordEncoder" ref="passwordEncoder" />
    <beans:property name="accountService" ref="userAccountServiceImpl"></beans:property>
    <beans:property name="clientService" ref="clientServiceImpl"></beans:property>
</beans:bean>
<!-- End of DAO Providers  -->

<beans:bean id="clientDetails" class="org.springframework.security.oauth2.provider.JdbcClientDetailsService">
    <beans:constructor-arg ref="dataSource" />
</beans:bean>

<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased" xmlns="http://www.springframework.org/schema/beans">
    <beans:constructor-arg>
        <beans:list>
            <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
            <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
            <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
        </beans:list>
    </beans:constructor-arg>
</beans:bean>   

<oauth:authorization-server  client-details-service-ref="clientDetails" token-services-ref="tokenServices">
    <oauth:refresh-token />
    <oauth:client-credentials/>
    <oauth:custom-grant token-granter-ref="customPasswordGrant"/>
    <oauth:custom-grant token-granter-ref="randomTokenGrant" />
</oauth:authorization-server>

<beans:bean id="restServicesSuccessHandler" class="com.xxxx.api.handlers.RestAuthenticationSuccessHandler" />   



<oauth:resource-server id="resourceServerFilter" resource-id="api" token-services-ref="tokenServices" />

<sec:global-method-security pre-post-annotations="enabled" proxy-target-class="true">
    <sec:expression-handler ref="oauthExpressionHandler" />
</sec:global-method-security>

<oauth:expression-handler id="oauthExpressionHandler" />

<oauth:web-expression-handler id="oauthWebExpressionHandler" />

<beans:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <beans:property name="realmName" value="api/" />
</beans:bean>

<!-- Custom Grant Types -->
<beans:bean id="randomTokenGrant" class="com.xxxx.api.grants.RandomTokenGrant">
    <beans:constructor-arg index="0"><beans:value>${api.common.login.oauth.token}</beans:value></beans:constructor-arg>
    <beans:constructor-arg index="1"><beans:value>${user.login.username.param}</beans:value></beans:constructor-arg>
    <beans:constructor-arg index="2"><beans:value>${user.login.password.param}</beans:value></beans:constructor-arg>
    <beans:constructor-arg index="3" ref="authenticationManager"></beans:constructor-arg>
    <beans:constructor-arg index="4" ref="tokenServices"></beans:constructor-arg>
    <beans:constructor-arg index="5" ref="clientDetails"></beans:constructor-arg>
    <beans:constructor-arg index="6"><beans:value>${api.common.login.oauth.grant_type.two_factor}</beans:value></beans:constructor-arg>
    <beans:constructor-arg index="7" ref="userAccountServiceImpl"></beans:constructor-arg>
</beans:bean>

<beans:bean id="customPasswordGrant" class="com.xxxx.api.grants.CustomPasswordGrant">
    <beans:constructor-arg index="0" ref="authenticationManager"></beans:constructor-arg>
    <beans:constructor-arg index="1" ref="tokenServices"></beans:constructor-arg>
    <beans:constructor-arg index="2" ref="clientDetails"></beans:constructor-arg>
    <beans:constructor-arg index="3" ref="userAccountServiceImpl"></beans:constructor-arg>
    <beans:constructor-arg index="4"><beans:value>${api.common.login.oauth.grant_type.custom_password}</beans:value></beans:constructor-arg>
</beans:bean>

<!-- End Of Custom Grant Types -->

<beans:bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
    <beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>


<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.JdbcTokenStore">
    <beans:constructor-arg ref="dataSource" />
</beans:bean>
<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
    <beans:property name="tokenStore" ref="tokenStore" />
    <beans:property name="supportRefreshToken" value="true" />
    <beans:property name="clientDetailsService" ref="clientDetails" />
</beans:bean>

<beans:bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
    <beans:constructor-arg ref="clientDetails" />
</beans:bean>
<beans:bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />

<beans:bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <beans:property name="realmName" value="api" />
</beans:bean>

<beans:bean id="clientServiceImpl" class="com.xxxx.api.services.ClientServiceImpl">
    <beans:property name="clientDetailsService" ref="clientDetails" />
</beans:bean>

Upvotes: 1

Views: 2201

Answers (1)

Dave Syer
Dave Syer

Reputation: 58094

You could use the same AuthenticationManager for clients and users, but that only makes life difficult, especially if you use a ProviderManager (like you are), because the client and user credentials are presented in the same form (UsernamePasswordAuthenticationToken). It's far easier if you stick to the provided default behaviour which is a separate AuthenticationManager for clients that is created for you by the framework. All the samples and integration tests use this dual authentication manager pattern, so there is plenty to crib from.

Upvotes: 1

Related Questions