双腿(客户端凭据)OAuth2 服务器的 Spring 安全上下文设置

2022-09-01 11:05:49

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

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

<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(UserDetailsService)作为实现基本 spring-security 的一部分,以便帐户和角色在我们的数据库中持久化。


我真正没有得到的豆子是:

userApprovalHandler:为什么我需要在client_credentials流/授权中进行任何用户批准?看起来,sparklr会覆盖默认设置以自动批准一个客户端。我是否也需要为受信任的客户端和服务器之间的通信执行此操作?TokenServicesUserApprovalHandler

oauthAuthenticationEntryPoint:Sparklr对此所做的一切是:

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

这该怎么办?

clientCredentialsTokenEndpointFilter它说,只有当我想通过请求参数进行身份验证时,我才应该包含它。因此,我所想到的正是:向我的服务器发送一个GET(?)请求,其中包含密钥并获取令牌,然后使用该令牌访问资源?所以我在想,令牌的请求应该包含秘密作为请求参数..?

资源服务器过滤器在我看来,这表明一个单独的资源服务器?如果我的资源与身份验证提供程序位于同一服务器上,这如何适用?

accessDecisionManager我不记得在设置自定义spring-security实现时必须使用它,为什么我现在要这样做?

感谢您的通读!希望有人能回答我的一些问题。

更新

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

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

并使用该令牌访问受保护的资源:

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

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

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

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

这也将有助于更多地了解所有弹簧安全设置,但文档非常薄。

编辑

将弹簧安全配置更新到当前状态。另外,这是我们的网站.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 资源都在 /rest 下运行,因此是 url 模式。但是:总是需要有一个单独的 servlet 和 spring 上下文。


答案 1

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

oauthauthenticationEntryPoint:通常,如果身份验证失败,响应类型为 JSON。文档显示“如果身份验证失败,并且调用方已要求特定内容类型响应,则此入口点可以发送一个响应以及标准 401 状态。

clientCredentialsTokenEndpointFilter:颁发访问令牌的过程分为两个步骤。首先,将用户发送到资源服务器进行身份验证。此重定向由客户端进行身份验证,理想情况下使用 HTTP 标头(密钥 + 机密)。作为回报,客户端将获得一个代码,该代码可以交换为令牌。您不会直接将密钥+机密换成令牌,因为它不包含用户的批准。

resourceServerFilter我认为这样做的目的是指示哪些客户端可以访问哪些资源,如果你有许多不同的资源。

accessDecisionManager:对于OAuth2,你需要一个ScopeVoter,所以默认的管理器不够好。

通常:如果您只有一个客户端代表用户访问资源,那么也许可以考虑使用 Digest 而不是 OAuth2?如果您只想对客户端(而不是用户)进行身份验证,那么OAuth2就大材小用了。OAuth2 中的客户端身份验证实际上与通过 https 进行的基本身份验证相同。


答案 2

适用于 REST 客户端和具有 spring security ouath 2.0 的用户的 oauth2 的一个很好的例子:http://www.e-zest.net/blog/rest-authentication-using-oauth-2-0-resource-owner-password-flow-protocol/


推荐