Question

I'm trying to implement a simple client credentials flow spring-security-oauth2 api. I tried to adapt sparklr and tonr examples, but with no success. I also tried follow the code of this thread: Spring-security context setup for 2-legged (client credentials) OAuth2 server but it don't seems to work with me. Could someone show me an example or help me with what I have to do to get this working.

spring-servlet.xml in sparklr application

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:sec="http://www.springframework.org/schema/security"
        xmlns:mvc="http://www.springframework.org/schema/mvc"
        xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <!-- Just for testing... -->
    <http pattern="/oauth/cache_approvals" security="none" xmlns="http://www.springframework.org/schema/security" />
    <http pattern="/oauth/uncache_approvals" security="none" xmlns="http://www.springframework.org/schema/security" />

    <http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"
            xmlns="http://www.springframework.org/schema/security">
        <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
        <anonymous enabled="false" />
        <http-basic entry-point-ref="clientAuthenticationEntryPoint" />
        <!-- include this only if you need to authenticate clients via request parameters -->
        <custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>

    <!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling 
        separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
    <http pattern="/oauth/(users|clients)/.*" request-matcher="regex" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
            use-expressions="true" xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/oauth/users/([^/].*?)/tokens/.*"
                access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('write')"
                method="DELETE" />
        <intercept-url pattern="/oauth/users/.*"
                access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('read')"
                method="GET" />
        <intercept-url pattern="/oauth/clients/.*"
                access="#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')" method="GET" />
        <intercept-url pattern="/**" access="denyAll()"/>
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
        <expression-handler ref="oauthWebExpressionHandler" />
    </http>

    <!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling 
        separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
    <http pattern="/photos/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
            access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/photos" access="ROLE_USER,SCOPE_READ" />
        <intercept-url pattern="/photos/trusted/**" access="ROLE_CLIENT,SCOPE_TRUST" />
        <intercept-url pattern="/photos/user/**" access="ROLE_USER,SCOPE_TRUST" />
        <intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>

    <!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling 
        separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
    <http pattern="/me/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
            access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/me" access="ROLE_USER,SCOPE_READ" />
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>

    <http access-denied-page="/login.jsp?authorization_error=true" disable-url-rewriting="true"
            xmlns="http://www.springframework.org/schema/security">
        <intercept-url pattern="/oauth/**" access="ROLE_USER" />
        <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

        <form-login authentication-failure-url="/login.jsp?authentication_error=true" default-target-url="/index.jsp"
                login-page="/login.jsp" login-processing-url="/login.do" />
        <logout logout-success-url="/index.jsp" logout-url="/logout.do" />
        <anonymous />
    </http>

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

    <bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
        <property name="realmName" value="sparklr2/client" />
        <property name="typeName" value="Basic" />
    </bean>

    <bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />

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

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

    <authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
        <authentication-provider user-service-ref="clientDetailsUserService" />
    </authentication-manager>

    <authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">
        <authentication-provider>
            <user-service id="userDetailsService">
                <user name="marissa" password="koala" authorities="ROLE_USER" />
                <user name="paul" password="emu" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

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

    <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />

    <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
        <property name="tokenStore" ref="tokenStore" />
        <property name="supportRefreshToken" value="true" />
        <property name="clientDetailsService" ref="clientDetails" />
    </bean>

    <bean id="userApprovalHandler" class="org.springframework.security.oauth.examples.sparklr.oauth.SparklrUserApprovalHandler">
        <property name="autoApproveClients">
            <set>
                <value>my-less-trusted-autoapprove-client</value>
            </set>
        </property>
        <property name="tokenServices" ref="tokenServices" />
    </bean>

    <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"
            user-approval-handler-ref="userApprovalHandler">
        <oauth:authorization-code />
        <oauth:implicit />
        <oauth:refresh-token />
        <oauth:client-credentials />
        <oauth:password />
    </oauth:authorization-server>

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

    <oauth:client-details-service id="clientDetails">
        <oauth:client client-id="my-trusted-client" authorized-grant-types="password,authorization_code,refresh_token,implicit"
                authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT" scope="read,write,trust" access-token-validity="60" />
        <oauth:client client-id="my-trusted-client-with-secret" authorized-grant-types="password,authorization_code,refresh_token,implicit"
                secret="somesecret" authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT" />
        <oauth:client client-id="my-client-with-secret" authorized-grant-types="client_credentials" authorities="ROLE_CLIENT"
                scope="read" secret="secret" />
        <oauth:client client-id="my-less-trusted-client" authorized-grant-types="authorization_code,implicit"
                authorities="ROLE_CLIENT" />
        <oauth:client client-id="my-less-trusted-autoapprove-client" authorized-grant-types="implicit"
                authorities="ROLE_CLIENT" />
        <oauth:client client-id="my-client-with-registered-redirect" authorized-grant-types="authorization_code,client_credentials"
                authorities="ROLE_CLIENT" redirect-uri="http://anywhere?key=value" scope="read,trust" />
        <oauth:client client-id="my-untrusted-client-with-registered-redirect" authorized-grant-types="authorization_code"
                authorities="ROLE_CLIENT" redirect-uri="http://anywhere" scope="read" />
        <oauth:client client-id="tonr" resource-ids="sparklr" authorized-grant-types="client_credentials"
                authorities="ROLE_CLIENT" scope="read,write" secret="secret" />
    </oauth:client-details-service>

    <mvc:annotation-driven />

    <mvc:default-servlet-handler />

    <sec:global-method-security pre-post-annotations="enabled" proxy-target-class="true">
        <!--you could also wire in the expression handler up at the layer of the http filters. See https://jira.springsource.org/browse/SEC-1452 -->
        <sec:expression-handler ref="oauthExpressionHandler" />
    </sec:global-method-security>

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

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

    <!--Basic application beans. -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
            </map>
        </property>
        <property name="viewResolvers">
            <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="prefix" value="/WEB-INF/jsp/"></property>
                <property name="suffix" value=".jsp"></property>
            </bean>
        </property>
        <property name="defaultViews">
            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
                <property name="extractValueFromSingleKeyModel" value="true" />
            </bean>
        </property>
    </bean>

    <bean id="photoController" class="org.springframework.security.oauth.examples.sparklr.mvc.PhotoController">
        <property name="photoService" ref="photoServices" />
    </bean>

    <bean id="photoServiceUserController" class="org.springframework.security.oauth.examples.sparklr.mvc.PhotoServiceUserController">
        <property name="userDetailsService" ref="userDetailsService" />
    </bean>

    <bean id="adminController" class="org.springframework.security.oauth.examples.sparklr.mvc.AdminController">
        <property name="tokenServices" ref="tokenServices" />
        <property name="userApprovalHandler" ref="userApprovalHandler" />
    </bean>

    <!-- Override the default mappings for approval and error pages -->
    <bean id="accessConfirmationController" class="org.springframework.security.oauth.examples.sparklr.mvc.AccessConfirmationController">
        <property name="clientDetailsService" ref="clientDetails" />
    </bean>

    <bean id="photoServices" class="org.springframework.security.oauth.examples.sparklr.impl.PhotoServiceImpl">
        <property name="photos">
            <list>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="1" />
                    <property name="name" value="photo1.jpg" />
                    <property name="userId" value="marissa" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo1.jpg" />
                </bean>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="2" />
                    <property name="name" value="photo2.jpg" />
                    <property name="userId" value="paul" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo2.jpg" />
                </bean>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="3" />
                    <property name="name" value="photo3.jpg" />
                    <property name="userId" value="marissa" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo3.jpg" />
                </bean>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="4" />
                    <property name="name" value="photo4.jpg" />
                    <property name="userId" value="paul" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo4.jpg" />
                </bean>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="5" />
                    <property name="name" value="photo5.jpg" />
                    <property name="userId" value="marissa" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo5.jpg" />
                </bean>
                <bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
                    <property name="id" value="6" />
                    <property name="name" value="photo6.jpg" />
                    <property name="userId" value="paul" />
                    <property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo6.jpg" />
                </bean>
            </list>
        </property>
    </bean>

</beans>

spring-servlet.xml in tonr application

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:sec="http://www.springframework.org/schema/security"
        xmlns:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
    http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
    http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <http access-denied-page="/login.jsp?authorization_error=true" xmlns="http://www.springframework.org/schema/security">
        <intercept-url pattern="/sparklr/**" access="ROLE_USER" />
        <intercept-url pattern="/facebook/**" access="ROLE_USER" />
        <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

        <form-login authentication-failure-url="/login.jsp?authentication_error=true" default-target-url="/index.jsp"
                login-page="/login.jsp" login-processing-url="/login.do" />
        <logout logout-success-url="/index.jsp" logout-url="/logout.do" />
        <anonymous />
        <custom-filter ref="oauth2ClientFilter" after="EXCEPTION_TRANSLATION_FILTER" />
    </http>

    <authentication-manager xmlns="http://www.springframework.org/schema/security">
        <authentication-provider>
            <user-service>
                <user name="marissa" password="wombat" authorities="ROLE_USER" />
                <user name="sam" password="kangaroo" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

    <!--apply the oauth client context -->
    <oauth:client id="oauth2ClientFilter" />

    <!--  define an oauth 2 resource for sparklr --> 
    <oauth:resource id="sparklr" type="client_credentials" client-id="tonr" client-secret="secret"
            access-token-uri="${accessTokenUri}" scope="read,write" />

    <!--define an oauth 2 resource for trusted client on sparklr -->
    <oauth:resource id="trusted" type="client_credentials" client-id="my-client-with-registered-redirect"
            access-token-uri="${accessTokenUri}" scope="trust" />

    <!--define an oauth 2 resource for facebook. according to the facebook docs, the 'client-id' is the App ID, and the 'client-secret' 
        is the App Secret -->
    <oauth:resource id="facebook" type="authorization_code" client-id="233668646673605" client-secret="33b17e044ee6a4fa383f46ec6e28ea1d"
            authentication-scheme="query" access-token-uri="https://graph.facebook.com/oauth/access_token" user-authorization-uri="https://www.facebook.com/dialog/oauth"
            token-name="oauth_token" client-authentication-scheme="form" />

    <context:property-placeholder location="classpath:/sparklr.properties" />

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.springframework.security.oauth.examples.tonr.converter.AccessTokenRequestConverter" />
            </set>
        </property>
    </bean>

    <mvc:default-servlet-handler />

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter" />
        </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="contentViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
            </map>
        </property>
        <property name="defaultViews">
            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
        </property>
    </bean>

    <!--Basic application beans. -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="sparklrController" class="org.springframework.security.oauth.examples.tonr.mvc.SparklrController">
        <property name="sparklrService" ref="sparklrService" />
    </bean>

    <bean id="facebookController" class="org.springframework.security.oauth.examples.tonr.mvc.FacebookController">
        <property name="facebookRestTemplate">
            <oauth:rest-template resource="facebook">
                <property name="messageConverters">
                    <list>
                        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                            <property name="supportedMediaTypes">
                                <list>
                                    <bean class="org.springframework.http.MediaType">
                                        <!--facebook sends its json as text/javascript for some reason -->
                                        <constructor-arg value="text" />
                                        <constructor-arg value="javascript" />
                                    </bean>
                                    <bean class="org.springframework.http.MediaType">
                                        <constructor-arg value="application" />
                                        <constructor-arg value="json" />
                                    </bean>
                                </list>
                            </property>
                        </bean>
                    </list>
                </property>
            </oauth:rest-template>
        </property>
    </bean>

    <bean id="sparklrService" class="org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl">
        <property name="sparklrPhotoListURL" value="${sparklrPhotoListURL}" />
        <property name="sparklrTrustedMessageURL" value="${sparklrTrustedMessageURL}" />
        <property name="sparklrPhotoURLPattern" value="${sparklrPhotoURLPattern}" />
        <property name="sparklrRestTemplate">
            <oauth:rest-template resource="sparklr" />
        </property>
        <property name="trustedClientRestTemplate">
            <oauth:rest-template resource="trusted" />
        </property>
    </bean>

</beans>

I am getting the following error when attempting to authenticate tonr application on sparklr application:

HTTP Status 500 - Request processing failed; nested exception is error="access_denied", error_description="Error requesting access token.

I hope someone can help me. Thanks

Was it helpful?

Solution

You can try allowing anonymous access to the /oauth/token endpoint. That's the only way I could get my client_credentials flow to work.

Change this line

<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />

to this

<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_ANONYMOUSLY" />

And remove this line:

<anonymous enabled="false" />

Hope that helps.

OTHER TIPS

replace this line

  <oauth:resource id="sparklr" type="client_credentials" client-id="tonr" client-secret="secret"
        access-token-uri="${accessTokenUri}" scope="read,write" />

by

 <oauth:resource id="sparklr" type="authorization_code" client-id="tonr" client-secret="secret"
        access-token-uri="${accessTokenUri}" scope="read,write" />

the error is el type client_credentials and replaced by authorization_code and will work fine.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top