无法在 Spring Oauth2 密码授予中获取访问令牌

Posted

技术标签:

【中文标题】无法在 Spring Oauth2 密码授予中获取访问令牌【英文标题】:Unable to get the access token in Spring Oauth2 password grant 【发布时间】:2015-07-21 22:41:35 【问题描述】:

我正在尝试将 Oauth2 与 Spring 集成,以在我的应用程序中保护我的 Restful 服务。 我面临的问题是,一旦我通过有效的客户端 ID 来获取 resfresh/访问令牌,应用程序就会重定向到带有 404 响应的 url,而不是在不考虑用户名、密码的情况下返回访问令牌参数。正在使用的Url如下:

http://localhost:8080/oauthSample/oauth/token?grant_type=password&client_id=testabc&username=user&password=password

对于客户端 ID 无效的 URL(例如 http://localhost:8080/oauthSample/oauth/token?grant_type=password&client_id=test&username=user&password=password),返回的响应是

<oauth>
<error_description>No client with requested id: test</error_description>
<error>unauthorized</error>
</oauth>

安全applicationContext.xml如下:

<?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:oauth="http://www.springframework.org/schema/security/oauth2"
    xmlns:security="http://www.springframework.org/schema/security"  
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
                        http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config />

<!-- SPRING OAUTH IMPLICIT SECURTY CONFIG START -->

    <!-- Definition of the Authentication Service -->
    <http pattern="/oauth/token" create-session="stateless" 
        xmlns="http://www.springframework.org/schema/security"
        authentication-manager-ref="clientAuthenticationManager">
        <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" after="BASIC_AUTH_FILTER"/>
        <access-denied-handler ref="oauthAccessDeniedHandler"/>
    </http>

    <!-- Protected resources -->
    <http pattern="/services/**"
          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="/services/**" access="ROLE_USER"/>
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER"/>
        <access-denied-handler ref="oauthAccessDeniedHandler"/>
    </http>


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

    <bean id="clientAuthenticationEntryPoint"
          class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
        <property name="realmName" value="dstest/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 in config file -->
    <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="admin" password="password" 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>

    <!-- Token Store  -->
    <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"/>
        <!-- <property name="accessTokenValiditySeconds" value="100"/> -->
    </bean>

    <bean id="userApprovalHandler"
          class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler">
        <property name="tokenServices" ref="tokenServices"/>
    </bean>

    <!-- Token management -->
    <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="dstest"
                           token-services-ref="tokenServices"/>

    <!-- Client Definition -->
    <oauth:client-details-service id="clientDetails">

        <oauth:client client-id="testabc"
                      authorized-grant-types="password,authorization_code,refresh_token,implicit,redirect"
                      authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT"
                      redirect-uri="/web"
                      scope="read,write,trust"
                      access-token-validity="30"
                      refresh-token-validity="600"/>

    </oauth:client-details-service>

    <security:global-method-security pre-post-annotations="enabled" proxy-target-class="true">
        <security:expression-handler ref="oauthExpressionHandler"/>
    </security:global-method-security>
    <oauth:expression-handler id="oauthExpressionHandler"/>
    <oauth:web-expression-handler id="oauthWebExpressionHandler"/>

    <!-- SPRING OAUTH IMPLICIT SECURTY CONFIG END -->

</beans>

eclipse 控制台日志(如果需要)如下:

2015-05-11 17:47:40,315 [http-bio-8080-exec-6] DEBUG (AntPathRequestMatcher.java:103) Ð Checking match of request : '/oauth/token'; against '/oauth/token'
2015-05-11 17:47:40,315 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 1 of 6 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2015-05-11 17:47:40,315 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 2 of 6 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 3 of 6 in additional filter chain; firing Filter: 'ClientCredentialsTokenEndpointFilter'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AbstractAuthenticationProcessingFilter.java:188) Ð Request is to process authentication
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (ProviderManager.java:152) Ð Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AbstractAuthenticationProcessingFilter.java:311) Ð Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@a2dfd9fc: Principal: org.springframework.security.core.userdetails.User@ab371290: Username: testabc; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_CLIENT,ROLE_TRUSTED_CLIENT; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_CLIENT, ROLE_TRUSTED_CLIENT
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 4 of 6 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 5 of 6 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:318) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password at position 6 of 6 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AntPathRequestMatcher.java:103) Ð Checking match of request : '/oauth/token'; against '/oauth/token'
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AbstractSecurityInterceptor.java:193) Ð Secure object: FilterInvocation: URL: /oauth/token?grant_type=password&client_id=testabc&username=user&password=password; Attributes: [IS_AUTHENTICATED_FULLY]
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AbstractSecurityInterceptor.java:298) Ð Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@a2dfd9fc: Principal: org.springframework.security.core.userdetails.User@ab371290: Username: testabc; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_CLIENT,ROLE_TRUSTED_CLIENT; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_CLIENT, ROLE_TRUSTED_CLIENT
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AffirmativeBased.java:65) Ð Voter: org.springframework.security.access.vote.RoleVoter@1838b82, returned: 0
2015-05-11 17:47:40,316 [http-bio-8080-exec-6] DEBUG (AffirmativeBased.java:65) Ð Voter: org.springframework.security.access.vote.AuthenticatedVoter@13c2982, returned: 1
2015-05-11 17:47:40,317 [http-bio-8080-exec-6] DEBUG (AbstractSecurityInterceptor.java:214) Ð Authorization successful
2015-05-11 17:47:40,317 [http-bio-8080-exec-6] DEBUG (AbstractSecurityInterceptor.java:226) Ð RunAsManager did not change Authentication object
2015-05-11 17:47:40,317 [http-bio-8080-exec-6] DEBUG (FilterChainProxy.java:304) Ð /oauth/token?grant_type=password&client_id=testabc&username=user&password=password reached end of additional filter chain; proceeding with original chain
2015-05-11 17:47:40,318 [http-bio-8080-exec-6] DEBUG (ExceptionTranslationFilter.java:115) Ð Chain processed normally
2015-05-11 17:47:40,318 [http-bio-8080-exec-6] DEBUG (SecurityContextPersistenceFilter.java:97) Ð SecurityContextHolder now cleared, as request processing completed

我使用的环境如下:

1. Spring 3.1.0 Release
2. Oauth2 1.0.5 Release
3. Eclipse Luna
4. Maven
5. Apache tomcat 7.0.61

在客户端通过身份验证后,启用基于用户的身份验证是否需要进行任何更改?

【问题讨论】:

【参考方案1】:

搞定了...错误出现在指向 /services/* 的 web.xml servlet 映射 url 模式中。相反,它应该如下所示:

<servlet-mapping>
        <servlet-name>services</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

现在遇到另一个问题.. :( .. 调度程序 servlet 现在也在处理静态内容请求 (index.html)

Did not find handler method for [/index.html]

任何解决相同问题的指针将不胜感激。

【讨论】:

以上是关于无法在 Spring Oauth2 密码授予中获取访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何使用密码授予在 Spring Boot Oauth2 资源服务器中处理 CORS

Spring Boot Oauth2 验证资源所有者密码凭证授予的访问令牌

使用 Spring Security 为 Web 客户端授予针对 REST 服务器的 Oauth2 密码

Grails OAuth2 登录密码凭据授予返回 invalid_client

OAuth2 密码授予和基本身份验证

Spring OAuth2 - 客户端凭据授予类型中的用户信息