Spring oauth2 ver 2.0.7,端点 /oauth/token 上出现 404 错误

Posted

技术标签:

【中文标题】Spring oauth2 ver 2.0.7,端点 /oauth/token 上出现 404 错误【英文标题】:Spring oauth2 ver 2.0.7, 404 error on endpoint /oauth/token 【发布时间】:2015-06-11 21:19:03 【问题描述】:

尝试将我们的 Web 应用程序与 Spring oauth2 集成时遇到问题 端点 /oauth/token 映射为 GET 和 POST 方法

o.s.s.o.p.e.FrameworkEndpointHandlerMapping- Mapped "[/oauth/token],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]" onto public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.getAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException
o.s.s.o.p.e.FrameworkEndpointHandlerMapping- Mapped "[/oauth/token],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]" onto public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException

访问时:https://192.168.70.19:8072/oauth/token?grant_type=password&client_id=7CA42EA39288EC73212716FC6A51B8A2&username=admin&password=switch

**服务器返回:**错误的客户端凭据

没关系,但是当我添加 client_secret https://192.168.70.19:8072/oauth/token?grant_type=password&client_id=7CA42EA39288EC73212716FC6A51B8A2&client_secret=client_secret&username=admin&password=switch

服务器返回 404 错误(据我所知,这应该发生在 API 未仅映射时)

我的一些配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.2.xsd
    http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetClass"
                  value="org.springframework.security.core.context.SecurityContextHolder" />
        <property name="targetMethod" value="setStrategyName" />
        <property name="arguments">
            <list>
                <value>MODE_INHERITABLETHREADLOCAL</value>
            </list>
        </property>
    </bean>

    <bean id="authenticationEntryPoint"
          class="com.alu.ov.ngnms.appserver.login.AuthenticationEntryPoint">
        <constructor-arg name="loginUrl" value="/login.html" />
    </bean>
    <bean name="customUserDetailsAuthenticationProvider" class="com.alu.ov.ngnms.appserver.login.CustomUserDetailsAuthenticationProvider">
        <property name="aaaServerRepository" ref="AAAServerRepository"></property>
    </bean>
    <security:authentication-manager alias="authenticationManager"
                                     erase-credentials="false">
             <security:authentication-provider ref="customUserDetailsAuthenticationProvider" />
    </security:authentication-manager>
    <bean id="checkTokenEndPoint"
          class="org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint">
        <constructor-arg name="resourceServerTokenServices" ref="tokenServices"/>      
    </bean>
    <bean id="customAccessDeniedHandler"
          class="com.alu.ov.ngnms.appserver.login.CustomAccessDeniedHandler"></bean>

    <security:http pattern="/oauth/token" create-session="stateless" entry-point-ref="authenticationEntryPoint" authentication-manager-ref="clientAuthenticationManager">
        <security:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
        <security:anonymous enabled="false" />
        <security:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER" />
        <security:access-denied-handler ref="customAccessDeniedHandler" />
    </security:http>

    <security:http use-expressions="true" entry-point-ref="authenticationEntryPoint" create-session="never">
        <!-- for all users (login & non-login) -->
        <security:intercept-url pattern="/favicon.ico" access="permitAll" />
        <security:intercept-url pattern="/bower_components/**" access="permitAll"/>
        <security:intercept-url pattern="/locales/**" access="permitAll"/>
        <security:intercept-url pattern="/ov_components/**" access="permitAll"/>
        <security:intercept-url pattern="/scripts/**" access="permitAll"/>
        <security:intercept-url pattern="/styles/**" access="permitAll"/>
        <security:intercept-url pattern="/template/**" access="permitAll" />
        <security:intercept-url pattern="/webstart/classes/**" access="permitAll" />
        <security:intercept-url pattern="/assets/**" access="permitAll" />

        <!-- only for non-login users -->
        <security:intercept-url pattern="/login.html" access="!isAuthenticated()" />
        <security:intercept-url pattern="/upgrade.html" access="!isAuthenticated()" />
        <security:intercept-url pattern="/api/login" access="!isAuthenticated()" />

        <!-- for all login users -->

        <!-- only for admin user & no-license OV -->
        <security:intercept-url pattern="/noLicense.html" access="hasAnyRole('ROLE_ADMIN_NO_LICENSE')" />

        <security:intercept-url pattern="/**" access="isAuthenticated() and !hasRole('ROLE_ADMIN_NO_LICENSE')" />
        <security:access-denied-handler ref="customAccessDeniedHandler"/>
        <!-- Add filter to extract access token from request and perform authentication -->
        <security:custom-filter ref="customOAuth2AuthenProcessingFilter" before="PRE_AUTH_FILTER" />
        <security:expression-handler ref="oauthWebExpressionHandler" />
    </security:http>

    <bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
        <property name="authenticationManager" ref="clientAuthenticationManager" />
    </bean>

       <security:global-method-security authentication-manager-ref="authenticationManager" pre-post-annotations="enabled"
        secured-annotations="enabled">
    </security:global-method-security>

        <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices">
        <oauth:authorization-code />
        <oauth:implicit />
        <oauth:refresh-token />
        <oauth:client-credentials />
        <oauth:password authentication-manager-ref="authenticationManager"/>

    </oauth:authorization-server>
    <security:authentication-manager id="clientAuthenticationManager">
        <security:authentication-provider user-service-ref="clientDetailsUserService" />
    </security:authentication-manager>

    <bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
        <constructor-arg ref="clientDetails" />
    </bean>

    <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices" >
        <property name="tokenStore" ref="mongoDBTokenStore" />
        <property name="supportRefreshToken" value="true" />
        <property name="clientDetailsService" ref="clientDetails" />
        <!--  Access token & Refresh token will be expired in 1 year 1 second after being granted -->
        <property name="accessTokenValiditySeconds" value="31536001"></property>
        <property name="refreshTokenValiditySeconds" value="31536001"></property>
    </bean>

    <bean id="ovTokenExtractor" class="com.alu.ov.ngnms.appserver.login.OVTokenExtractor"></bean>
    <bean id="oauth2AuthenticationManager" class="org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager">
        <property name="tokenServices" ref="tokenServices"></property>
    </bean>
    <bean id="customOAuth2AuthenProcessingFilter" class="org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter">
        <property name="authenticationEntryPoint" ref="authenticationEntryPoint"></property>
        <property name="authenticationManager" ref="oauth2AuthenticationManager"></property>
        <property name="tokenExtractor" ref="ovTokenExtractor"></property>
    </bean>

    <bean id="clientDetails" class="com.alu.ov.ngnms.appserver.token.CustomClientDetailsService"/>

    <security:global-method-security pre-post-annotations="enabled" proxy-target-class="true">
        <!--you could also wire in the expression handler up at the layer of the http filters. See https://jira.springsource.org/browse/SEC-1452 -->
        <security:expression-handler ref="oauthExpressionHandler" />
    </security:global-method-security>

    <oauth:expression-handler id="oauthExpressionHandler" />

    <mvc:annotation-driven />

    <mvc:default-servlet-handler />

    <oauth:web-expression-handler id="oauthWebExpressionHandler" />
</beans>

--------编辑 1-------- 添加 web.xml:

<servlet>
        <servlet-name>atmoSpring</servlet-name>
        <servlet-class>org.atmosphere.cpr.MeteorServlet</servlet-class>


        <init-param>
            <!-- When MeteorServlet is used, this is the parameter that will be looked 
                and all requests will be delegated to this servlet, Of course, since we are 
                using, Spring MVC, we delegate to DispatcherServlet -->
            <param-name>org.atmosphere.servlet</param-name>
            <param-value>org.springframework.web.servlet.DispatcherServlet</param-value>
        </init-param>

        <!-- Bunch of Atmosphere specific properties -->
        <init-param>
            <param-name>org.atmosphere.cpr.broadcasterClass</param-name>
            <param-value>org.atmosphere.cpr.DefaultBroadcaster</param-value>
        </init-param>

        <!-- Set Atmosphere to use the container native Comet support. -->
        <init-param>
            <param-name>org.atmosphere.useNative</param-name>
            <param-value>true</param-value>
        </init-param>

        <!-- Force Atmosphere to use stream when writing bytes. -->
        <init-param>
            <param-name>org.atmosphere.useStream</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.cpr.AtmosphereInterceptor</param-name>
            <param-value>org.atmosphere.interceptor.SSEAtmosphereInterceptor</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.interceptor.SSEAtmosphereInterceptor.contentType</param-name>
            <param-value>text/event-stream</param-value>
        </init-param>

        <!-- Use this interceptor to prevent firewall/proxies from canceling the 
            connection after a specific idle time -->
        <init-param>
            <param-name>org.atmosphere.cpr.AtmosphereInterceptor</param-name>
            <param-value>org.atmosphere.interceptor.HeartbeatInterceptor</param-value>
        </init-param>
        <init-param>
            <param-name>org.atmosphere.interceptor.HeartbeatInterceptor.heartbeatFrequencyInSeconds</param-name>
            <param-value>30</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.useWebSocketAndServlet3</param-name>
            <param-value>false</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.cpr.AtmosphereInterceptor.disableDefaults</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.cpr.broadcasterCacheClass</param-name>
            <param-value>org.atmosphere.cache.UUIDBroadcasterCache</param-value>
        </init-param>

        <init-param>
            <param-name>org.atmosphere.cpr.broadcaster.shareableThreadPool</param-name>
            <param-value>true</param-value>
        </init-param>


        <init-param>
            <param-name>org.atmosphere.cpr.sessionSupport</param-name>
            <param-value>true</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <servlet-mapping>
        <servlet-name>atmoSpring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/webContext.xml</param-value>
    </context-param>

    <!-- Spring Security -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
            <param-name>contextAttribute</param-name>
            <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher> 
        <dispatcher>ASYNC</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>

    <filter>
        <filter-name>cacheControlFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
    </filter>

    <filter-mapping>
        <filter-name>cacheControlFilterChain</filter-name>
        <url-pattern>/api/*</url-pattern>
    </filter-mapping>

    <!-- Session Listener for Webstart -->
    <listener>
        <listener-class>com.alu.ov.ngnms.appserver.controller.SessionListener</listener-class>
    </listener>

【问题讨论】:

可能您没有定义端点并将其映射到 servlet。您没有展示任何配置,也没有描述您如何创建应用程序上下文。 @DaveSyer 感谢您的回复,配置文件比较长,所以我只是复制了一些相关的bean,我添加了web.xml 已编辑 - 添加完整的安全上下文和 web.xml;实际上,我们的 web 应用程序之前使用过 spring oauth2 1.0.0,现在我尝试将其迁移到最新版本以获得新支持的功能,但是 github 中的 oauth2 示例只包含注释基础配置,所以我不确定我的新版本配置。你知道 oauth2 从 1.x 到 2.x 的迁移教程吗? 尝试在调试模式下运行您的 oauth2 服务器并提供日志。 @prtk_shah 感谢您的支持,但问题已通过按照 Dave 的建议重新配置 web.xml 得到解决 【参考方案1】:

您不能在ContextLoaderListener 中定义授权服务器端点。我真的不知道MeteorServlet 是如何工作的,但是您必须将配置放入DispatcherServlet 以便它可以处理“/oauth/token”请求(处理程序由&lt;authorization-server/&gt; 声明创建)。

【讨论】:

感谢 Dave,问题确实是 web.xml 中的配置不明确造成的,只是不知道为什么它在 1.0.0 中运行良好。顺便说一句,非常感谢。【参考方案2】:

我在/servlet/oauth/token 请求上也得到了 404。在我的例子中,我将 DispatcherServlet 映射到 /servlet/*,而不是 web.xml 文件中的 /*。我必须更新 AuthorizationServerConfigurerAdapter 以使用前缀,以便授权过滤器和请求映射处理程序适配器都可以处理 oauth 请求(即:/servlet/oauth/token 对我来说) @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception endpoints.prefix("/servlet").authenticationManager(authenticationManager);

在 AbstractSecurityWebApplicationInitializer 中设置 DispatcherWebApplicationContextSuffix 以从 web.xml 文件中引用 Dispatcher servlet 名称也很重要。

【讨论】:

我使用 /oauth/token ,我应该配置什么吗? 我猜,如果适合你,你可以使用默认配置。

以上是关于Spring oauth2 ver 2.0.7,端点 /oauth/token 上出现 404 错误的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 入门(1-3)Spring Security oauth2.0 指南

spring security oauth2 认证端异常处理(基于前后分离统一返回json)

Eurynome Cloud Athena 基于Spring Security OAuth2 的前后端分离脚手架

利用OAuth2.0来构建一个基于Spring Boot的安全的端到端通信应用

J2EE企业分布式微服务云快速开发架构 Spring Cloud+Spring Boot2+Mybatis+Oauth2+ElementUI 前后端分离

springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)