Spring - настройка контекста безопасности для двухногих (учетные данные клиента) сервер OAuth2

Какая минимальная настройка для spring -security OAuth2, если я хочу защитить сервер REST для одного клиента? Я не хочу использовать ненужную настройку или внедрять ненужные beans. Может быть, есть "легкий" учебник/пример, который уже существует для spring -security + OAuth2? (Хотя я стараюсь не слишком надеяться на это)

Моя текущая рабочая настройка (работа с копией + прошлое + wtf из контекста sparklr) выглядит слишком много:

<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, чтобы учетные записи и роли сохранялись в нашей базе данных.


beans Я действительно не понимаю:

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 (?) На мой сервер с этим секретом и получить токен и с этим доступом к токену ресурсы? Поэтому я думаю, что запрос на токен должен содержать секрет как параметр запроса.?

resourceServerFilter Мне кажется, что это указывает на отдельный сервер ресурсов? Как это применимо, если мои ресурсы находятся на том же сервере, что и поставщик проверки подлинности?

AccessDecisionManager Я не помню, чтобы использовать это при настройке моей пользовательской реализации spring -security, почему я хочу сделать это сейчас?

Спасибо, что прочитали! Надеюсь, кто-то может ответить на несколько моих вопросов.

Update

Я обновил настройку до текущего рабочего состояния. Теперь я могу запросить токен доступа с учетными данными клиента:

$ 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 с параметрами запроса.

Это также поможет узнать больше обо всех настройках spring -security, но документация довольно тонкая.

ИЗМЕНИТЬ

Обновлена ​​конфигурация безопасности spring для нашего текущего состояния. Кроме того, здесь наш 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 сверху будет инициализирован сервлет-контекстом. Сам spring -context.xml инициализирует beans. (Также: у нашего сервера также есть несколько представлений, поэтому все ресурсы ресурсов работают под /rest, следовательно, с шаблоном url. Но: всегда необходимо иметь отдельный сервлет и контекст spring.)

Ответ 1

userApprovalHandler: если у вас только один клиент в вашей системе, я согласен, что пользователям не нужно будет одобрять его доступ к их данным.

oauthAuthenticationEntryPoint. Обычно, если аутентификация завершается с ошибкой, тип ответа - JSON. Документация гласит: "Если аутентификация завершается с ошибкой, и вызывающий абонент запросил ответ определенного типа контента, эта точка входа может отправить ее вместе со стандартным статусом 401".

clientCredentialsTokenEndpointFilter. Выдача токена доступа - это двухэтапный процесс. Во-первых, вы отправляете пользователя на сервер ресурсов для аутентификации. Это перенаправление аутентифицируется клиентом, в идеале с заголовками HTTP (ключ + секрет). В свою очередь, клиент получает код, который можно обменять на токен. Вы не напрямую торгуете ключ + секрет для токена, так как он не содержит одобрения пользователя.

resourceServerFilter. Я думаю, что цель этого - показать, какие клиенты имеют доступ к каким ресурсам, если у вас много разных ресурсов.

accessDecisionManager. Для OAuth2 вам нужен ScopeVoter, поэтому менеджер по умолчанию недостаточно хорош.

Вообще: Если у вас будет только один клиент, который будет обращаться к ресурсам от имени пользователей, то, возможно, подумайте над использованием Digest вместо OAuth2? И если вы хотите только аутентифицировать клиента (а не пользователя), то OAuth2 является излишним. Аутентификация клиента в OAuth2 действительно такая же, как и обычная проверка подлинности по https.