2-legged(客户端凭据)OAuth2服务器的Spring-security上下文设置 [英] Spring-security context setup for 2-legged (client credentials) OAuth2 server

查看:42
本文介绍了2-legged(客户端凭据)OAuth2服务器的Spring-security上下文设置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我想为一个客户端保护 REST 服务器,spring-security OAuth2 的最小设置是什么?我不想使用任何不必要的设置或实现任何不必要的 bean.也许已经有一个关于 spring-security + OAuth2 的简单"教程/示例?(虽然我尽量避免对此抱有太大希望)

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)

我当前的工作设置(使用 sparklr 上下文中的 copy+past+wtf)感觉太多了:

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>   

我已经实现了 authenticationManager (UserDetailsS​​ervice) 作为实现基本 spring-security 的一部分,以便帐户和角色在我们的数据库中持久化.

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.

我不太明白的豆子是:

userApprovalHandler:为什么我需要在 client_credentials 流/授权中进行任何用户批​​准?看来,sparklr 覆盖了默认的 TokenServicesUserApprovalHandler 以自动批准一个客户端.对于受信任的客户端和服务器之间的通信,我是否也需要这样做?

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:sparklr 所做的就是:

oauthAuthenticationEntryPoint: all sparklr does about this is:

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

那应该做什么?

clientCredentialsTokenEndpointFilter它说,只有当我想通过请求参数进行身份验证时,我才应该包含它.所以我想到的是:使用机密将 GET(?) 请求发送到我的服务器并获取令牌并使用该令牌访问资源?所以我在想,对令牌的请求应该包含秘密作为请求参数..?

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在我看来,这表示一个单独的资源服务器?如果我的资源与身份验证提供程序在同一台服务器上,这如何适用?

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我不记得在设置我的自定义 spring-security 实现时必须使用它,为什么我现在要这样做?

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..

我已将设置更新为当前工作状态.我现在可以使用客户端凭据请求访问令牌:

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"

感觉还是有很多设置,我的问题仍然存在.另外,我想知道这是否是保护可信客户端和 REST 服务器之间通信的正确方法.

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.

除非通过 https 完成,否则对令牌的初始请求仍然感觉不安全,但这就足够了吗?

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

还有令牌本身呢,我应该给它一个很长的生命周期并将它保存在客户端上吗?这在任何情况下都意味着捕获令牌过期异常,然后请求一个新的.还是我应该为每个请求都握手?刷新令牌怎么样?我想我在某处读到刷新令牌对于客户端凭据授予类型不安全..?是否有必要将令牌作为 HTTP 标头发送,或者我可以更改它吗?我不想为我们的客户端使用 spring-security 客户端堆栈,因为它有一个相当传统的设置 (jboss 5),到目前为止我们所做的只是将 REST 通信功能与请求参数集成..

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..

了解有关所有 spring-security 设置的更多信息也会有所帮助,但文档非常薄..

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

将 spring 安全配置更新为我们当前的状态.另外,这是我们的 web.xml:

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>

注意:上面的 spring-security-context.xml 将被 servlet-context 初始化.spring-context.xml 本身只初始化 bean.(另外:我们的服务器也有一些视图,所以所有的其余资源都在/rest 下运行,因此是 url-pattern.但是:总是需要有一个单独的 servlet 和 spring 上下文.)

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.)

推荐答案

userApprovalHandler:如果您的系统中只有一个客户端,我同意用户不必批准它访问他们的数据.

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

oauthAuthenticationEntryPoint:通常,如果身份验证失败,则响应类型为 JSON.文档说如果身份验证失败并且调用者要求提供特定的内容类型响应,则此入口点可以发送一个响应以及标准的 401 状态."

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:颁发访问令牌是一个两步过程.首先,您将用户发送到资源服务器进行身份验证.此重定向由客户端进行身份验证,最好使用 HTTP 标头(密钥 + 机密).作为回报,客户端会获得一个代码,该代码可以交换为令牌.您不会直接用密钥+秘密交换令牌,因为它不包含来自用户的批准.

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:我认为这样做的目的是表明如果您有许多不同的资源,哪些客户端可以访问哪些资源.

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

accessDecisionManager:对于 OAuth2,你需要一个 ScopeVoter,所以默认的 Manager 不够好.

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

一般:如果您只有一个客户端代表用户访问资源,那么可以考虑使用 Digest 而不是 OAuth2?如果您只想对客户端(而不是用户)进行身份验证,那么 OAuth2 就有点过分了.OAuth2 中的客户端身份验证与基于 https 的基本身份验证完全相同.

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.

这篇关于2-legged(客户端凭据)OAuth2服务器的Spring-security上下文设置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆