Spring-security context setup for 2-legged (client credentials) OAuth2 server

18,793

Solution 1

userApprovalHandler: if you only have one client in your system, I agree the users should not have to approve it accessing their data.

oauthAuthenticationEntryPoint: Normally, if authentication fails, the response type is JSON. Documentation says "If authentication fails and the caller has asked for a specific content type response, this entry point can send one, along with a standard 401 status."

clientCredentialsTokenEndpointFilter: Issuing an access token is a two-step process. First, you send the user to the resource server to authenticate. This redirect is authenticated by the client, ideally with the HTTP Headers (key + secret). In return, the client gets a code, which can be exchanged for a token. You do not directly trade the key+secret for a token, as it contains no approval from the user.

resourceServerFilter: I think the purpose of this is indicating what clients have access to what resources, if you have many different resources.

accessDecisionManager: For OAuth2 you need a ScopeVoter, so the default Manager is not good enough.

Generally: If you will only have one client accessing the resources on behalf of the users, then maybe consider using Digest instead of OAuth2? And if you only want to authenticate the client (not the user), then OAuth2 is overkill. Client authentication in OAuth2 is really same as Basic Authentication over https.

Solution 2

A good example of oauth2 for REST clients and users with spring security ouath 2.0: http://www.e-zest.net/blog/rest-authentication-using-oauth-2-0-resource-owner-password-flow-protocol/

Share:
18,793
Pete
Author by

Pete

Oh, so they have internet on computers now!

Updated on June 02, 2022

Comments

  • Pete
    Pete almost 2 years

    What's the minimal setup for spring-security OAuth2 if I want to secure a REST server for one client? I don't want to use any unnecessary setup or implement any unnecessary beans. Maybe there's an "easy" tutorial / example out there already for spring-security + OAuth2? (Though I'm trying to avoid being too hopeful about that)

    My current working setup (working with the copy+past+wtf from the sparklr context) feels like too much:

    <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"
           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/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">
    
        <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices">
            <oauth:client-credentials />
        </oauth:authorization-server>
    
        <sec:authentication-manager alias="clientAuthenticationManager">
            <sec:authentication-provider user-service-ref="clientDetailsUserService" />
        </sec:authentication-manager>
    
        <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>
    
        <oauth:resource-server id="resourceServerFilter"
                resource-id="rest_server" token-services-ref="tokenServices" />
    
        <oauth:client-details-service id="clientDetails">
            <oauth:client client-id="the_client" authorized-grant-types="client_credentials" 
                    authorities="ROLE_RESTREAD" secret="1234567890" />
        </oauth:client-details-service>
    
    
        <http pattern="/**" 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="/rest/**" access="ROLE_RESTREAD" method="GET" />
            <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
            <access-denied-handler ref="oauthAccessDeniedHandler" />
        </http>
    
        <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="false" />
            <property name="clientDetailsService" ref="clientDetails" />
            <property name="accessTokenValiditySeconds" value="400000" />
            <property name="refreshTokenValiditySeconds" value="0" />
        </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>
    
    
        <bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
            <property name="realmName" value="theRealm" />
        </bean>
    
        <bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
            <property name="realmName" value="theRealm/client" />
            <property name="typeName" value="Basic" />
        </bean>
    
        <bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
            <property name="authenticationManager" ref="clientAuthenticationManager" />
        </bean>
    
    
        <bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
            <constructor-arg ref="clientDetails" />
        </bean>
    
        <bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
    
    
        <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>   
    

    I already have implemented the authenticationManager (UserDetailsService) as a part of implementing basic spring-security so that accounts and roles are persisted against our database.


    The beans I don't really get are:

    userApprovalHandler: Why would I need any user approval in a client_credentials flow / grant? It seems, sparklr overrides the default TokenServicesUserApprovalHandler to auto-approve one client. Do I need to do that as well for the communication between my trusted client(s) and the server?

    oauthAuthenticationEntryPoint: all sparklr does about this is:

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

    What's that supposed to do?

    clientCredentialsTokenEndpointFilter It says, I should include this only if I want to authenticate via request parameters.. So what I have in mind is exactly that: Send a GET(?) request to my server with the secret and get a token and with that token access the resources? So I'm thinking, the request for the token should contain the secret as request parameter..?

    resourceServerFilter It seems to me that this indicates a separate resource server? How does that apply if my resources are on the same server as the authentication provider?

    accessDecisionManager I don't remember having to use this when setting up my custom spring-security implementation, why would I want to do so now?

    Thanks for reading through! Hope someone can answer a few of my questions..

    Update

    I've updated the setup to the current working state. I can now request an access token with the client credentials:

    $ curl -X -v -d 'client_id=the_client&client_secret=secret&grant_type=client_credentials' -X POST "http://localhost:9090/our-server/oauth/token"
    

    and use that token to access protected resources:

    $ curl -H "Authorization: Bearer fdashuds-5432fsd5-sdt5s5d-sd5" "http://localhost:9090/our-server/rest/social/content/posts"
    

    It still feels like a lot of setup and my questions remain. Also I'm wondering if this is the right way to go for securing the communication between trusted client and REST server in general.

    It also still feels like the initial request for the token is not secure except if done via https, but will that suffice?

    Also what about the token itself, should I give it a long lifetime and persist it on the client? that would in any case mean catching a token expiration exception and then requesting a new one. Or should I do the handshake for every request? What about refreshing the token? I think I read somewhere that refresh token is not secure for the client credentials grant type..? Is it necessary to send the token as HTTP header or can I change that? I don't want to use the spring-security client stack for our client as it has a rather legacy setup (jboss 5) and all we did so far was integrate REST communication capabilities with request parameters..

    It would also help to know more about all the spring-security setup but the documentation is quite thin..

    EDIT

    Updated the spring security configuration to our current state. Also, here's our web.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
            id="WebApp_ID" version="2.5">
    
        <display-name>the-display-name</display-name>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-context.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <listener>
            <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
        </listener>
    
        <servlet>
            <servlet-name>jersey-serlvet</servlet-name>     
            <servlet-class>
                com.sun.jersey.spi.spring.container.servlet.SpringServlet
            </servlet-class>        
            <init-param>
                <param-name>com.sun.jersey.config.property.packages</param-name>
                <param-value>base.package.rest</param-value>
            </init-param>               
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>jersey-serlvet</servlet-name>
            <url-pattern>/rest/*</url-pattern>
        </servlet-mapping>
    
        <servlet>
            <servlet-name>appServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>
                /WEB-INF/servlet-context.xml            
                </param-value>
            </init-param>
            <load-on-startup>2</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>appServlet</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
    
        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
            <init-param>
                <param-name>contextAttribute</param-name>
                <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.appServlet</param-value>
            </init-param>
        </filter>
    
        <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    

    Note: The spring-security-context.xml from above will get initialized by the servlet-context. The spring-context.xml itself only initializes the beans. (Also: Our Server also has a few views so all rest resources run under /rest hence the url-pattern. But: It is always necessary to have a separate servlet and spring context.)

  • Pete
    Pete over 11 years
    Thanks for the detailed answer, helps to shed some light on the whole process. Concerning the "overkill". We need to make part of our resources available to a thrid party app vendor as well as to several of our own apps. This is just the first step to secure communication between "main" client application, which will need most resources and the resource server. also this communication is supposedly the simplest one and I prefer adding complexity to simple solutions as the need arises so I am trying to get the minimal setup right even if in that scenarion it is overkill.
  • baraber
    baraber about 9 years
    "You do not directly trade the key+secret for a token" : Is this really the case for "client credentials" grant type ?
  • metadaddy
    metadaddy over 7 years
    Blog entry is no longer there.