简体   繁体   English

Spring-security oauth2 REST服务器的错误凭据(400)错误响应中的不需要的Stacktrace

[英]Unwanted Stacktrace in bad credentials (400) error response by spring-security oauth2 REST server

We have a REST server (resource + authorization) based on Oauth2 by spring-security + spring web + jersey for our REST resources. 我们有一个基于Oauth2的REST服务器(资源+授权),由spring-security + spring web + jersey为我们的REST资源。 Most of this is working out nicely, however when hitting /oauth/token in a username-password flow with bad credentials we don't just get a 400 (as would be correct by the spec) but an entire stacktrace as JSON in the response. 其中大部分工作都很顺利,但是当在带有错误凭据的用户名密码流中命中/ oauth / token时,我们不仅会获得400(因为规范是正确的),而是在响应中将整个堆栈跟踪作为JSON 。 I've searched and debugged and fumbled around but couldn't quite locate the culprit. 我已经搜索和调试并摸索了,但无法找到罪魁祸首。 Could this be a spring-security setting? 这可能是一个弹簧安全设置吗? or spring-web? 还是弹簧网? or the servlet that mapps the resources using jersey? 或者使用泽西匹配资源的servlet?

Example response (shortended): 响应示例(缩写):

$ curl -X POST -v --data "grant_type=password&username=admin&password=wrong_password&client_id=my_client" http://localhost:9090/oauth/token
* ...
* Connected to localhost (::1) port 9090 (#0)
* ...
> POST /oauth/token HTTP/1.1
> ...
> Accept: */*
> ...
> Content-Type: application/x-www-form-urlencoded
>
* ...
< HTTP/1.1 400 Bad Request
< ...
< Content-Type: application/json;charset=UTF-8
< ...
<
* ...
curl: (56) Recv failure: Connection reset by peer
{
    "cause": null,
    "stackTrace": [{
        "methodName": "getOAuth2Authentication",
        "fileName": "ResourceOwnerPasswordTokenGranter.java",
        "lineNumber": 62,
        "className": "org.springframework.security.oauth2.provider.passwo
    rd.ResourceOwnerPasswordTokenGranter",
        "nativeMethod": false
    },
    .... {"className": "java.lang.Thread",
    "nativeMethod": false
}],
"additionalInformation": null,
"oauth2ErrorCode": "invalid_grant",
"httpErrorCode": 400,
"summary": "error=\"invalid_grant\", error_description=\"Bad credentials\"","message":"Badcredentials","localizedMessage":"Badcredentials"}

Any ideas? 有任何想法吗? Please let me know if you need more infos (web.xml/security.xml/application.xml/servlet.xml) 如果您需要更多信息,请告诉我(web.xml / security.xml / application.xml / servlet.xml)

Thanks! 谢谢!

EDIT: Using client credentials flow with bad credentials it will give me a 401 and no stacktrace. 编辑:使用客户端凭据流与错误的凭据,它将给我401和没有堆栈跟踪。 It's just the BadCredentials / InvalidGrant exception thrown when username/password do not match that will result in a stacktrace. 当用户名/密码不匹配时,抛出的BadCredentials / InvalidGrant异常只会导致堆栈跟踪。

EDIT - Some snippets from our configuration 编辑 - 我们的配置中的一些片段

web.xml web.xml中

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/application-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>our.rest.package</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>

The servlet-context.xml just contains freemarker stuff and should not matter The jersey-servlet should not matter either, since it only mapps /rest/** resources and the requested resource is /oauth/token. servlet-context.xml只包含freemarker的东西并且无关紧要jersey-servlet也无关紧要,因为它只有mapps / rest / **资源,而请求的资源是/ oauth / token。 Which leaves only the spring-security setup: 这只留下了弹簧安全设置:

    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"
        after="BASIC_AUTH_FILTER" />
    <access-denied-handler ref="oauthAccessDeniedHandler" />
</http>

<http pattern="/rest/**" create-session="stateless"
    <!-- ... -->
</http>

<http disable-url-rewriting="true"
    xmlns="http://www.springframework.org/schema/security">
    <intercept-url pattern="/oauth/**" access="ROLE_USER" />
    <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />

    <form-login authentication-failure-url="/login.jsp?authentication_error=true"
        default-target-url="/index.jsp" login-page="/login.jsp"
        login-processing-url="/login.do" />
    <logout logout-success-url="/index.jsp" logout-url="/logout.do" />
    <anonymous />
</http>

<oauth:resource-server id="resourceServerFilter"
    resource-id="engine" token-services-ref="tokenServices" />

<oauth:authorization-server
    client-details-service-ref="clientDetails" token-services-ref="tokenServices">
    <oauth:client-credentials />
    <oauth:password />
</oauth:authorization-server>

<oauth:client-details-service id="clientDetails">
    <!-- several clients for client credentials flow... -->
    <oauth:client client-id="username-password-client"
        authorized-grant-types="password" authorities=""
        access-token-validity="3600" />
</oauth:client-details-service>

<authentication-manager id="clientAuthenticationManager"
    xmlns="http://www.springframework.org/schema/security">
    <authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>

<authentication-manager alias="theAuthenticationManager"
    xmlns="http://www.springframework.org/schema/security">
            <!-- authenticationManager is the bean name for our custom implementation 
                 of the UserDetailsService -->
    <authentication-provider user-service-ref="authenticationManager">
        <password-encoder ref="encoder" />
    </authentication-provider>
</authentication-manager>

<bean id="encoder"      class="org.springframework.security.crypto.password.StandardPasswordEncoder">
</bean>

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

<bean id="clientAuthenticationEntryPoint"
    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <property name="realmName" value="ourRealm/client" />
    <property name="typeName" value="Basic" />
</bean>

<bean id="oauthAccessDeniedHandler"     class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
</bean>

<sec:global-method-security
    secured-annotations="enabled" pre-post-annotations="enabled">
    <sec:expression-handler ref="expressionHandler" />
</sec:global-method-security>


<bean id="expressionHandler"
    class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
            <!-- our custom perission evaluator -->
    <property name="permissionEvaluator" ref="permissionEvaluatorJpa" />
</bean>

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

<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" />
            <bean
                class="org.springframework.security.web.access.expression.WebExpressionVoter" />

        </list>
    </constructor-arg>
</bean>

<bean id="tokenStore"
    class="org.springframework.security.oauth2.provider.token.JdbcTokenStore">
    <constructor-arg ref="ourDataSource" />
</bean>

<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" />
</bean>

<bean id="requestFactory"       class="org.springframework.security.oauth2.provider.DefaultAuthorizationRequestFactory">
    <constructor-arg name="clientDetailsService" ref="clientDetails" />
</bean>

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

Well, to me there seems to be no obvious place to configure this here. 好吧,对我来说似乎没有明显的地方可以在这​​里配置它。

The stacktrace suggest, that there is an unhandled InvalidGrantException thrown by the ResourceOwnerPasswordTokenGranter . 堆栈跟踪表明,有未处理的InvalidGrantException被抛出ResourceOwnerPasswordTokenGranter So I've tried adding filters to the filterchain above the spring-security filter in my web.xml, catching all exceptions and handling them. 所以我尝试在我的web.xml中的spring-security过滤器上方的过滤链中添加过滤器,捕获所有异常并处理它们。 Won't work however, as the spring-security filter seems to handle the InvalidGrantException on its own, meaning no exception bubbles up to my surrounding filter. 然而,无法工作,因为spring-security过滤器似乎自己处理InvalidGrantException,这意味着没有异常冒泡到我周围的过滤器。

The TokenEndpoint ( @RequestMapping(value = "/oauth/token") ) calls upon the ResourceOwnerPasswordTokenGranter to authenticate username/password: TokenEndpoint@RequestMapping(value = "/oauth/token") )调用ResourceOwnerPasswordTokenGranter来验证用户名/密码:

@FrameworkEndpoint
@RequestMapping(value = "/oauth/token")
public class TokenEndpoint extends AbstractEndpoint {
    @RequestMapping
    public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal,
            @RequestParam("grant_type") String grantType, @RequestParam Map<String, String> parameters) {
        // ...
        // undhandled:
        OAuth2AccessToken token = getTokenGranter().grant(grantType, authorizationRequest);
        // ...
        return getResponse(token);
    }

There the correct exception is raised: 在那里引发了正确的异常:

public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {   

    @Override
    protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) {
        // ...
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/bad grant
            throw new InvalidGrantException(e.getMessage());
        }
    }

}

but never handled not even when it hits back at the endpoint. 但是,即使它在端点回击时也从未处理过。 So then the filterchain does the exception handling and adds the stacktrace. 然后,过滤链进行异常处理并添加堆栈跟踪。 Instead the endpoint should return a clean 400 without the stacktrace, ie handle the damn exception! 相反,端点应返回没有堆栈跟踪的干净400,即处理该死的异常!

Now the only way I can see is to override the TokenEndpoint and catch the exception. 现在,我能看到的唯一方法是覆盖TokenEndpoint并捕获异常。

Any better ideas? 有更好的想法吗?

You could write a ExceptionMapper for that specific Exception and do whatever you want with the request. 您可以为该特定Exception编写ExceptionMapper,并根据请求执行任何操作。 The ExceptionMapper handles the Response whenever the defined exception is thrown from your endpoint methods. 每当从端点方法抛出定义的异常时,ExceptionMapper就会处理响应。

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MyExceptionMapper implements ExceptionMapper<InvalidGrantException>
{
    @Override
    public Response toResponse(InvalidGrantException exception)
    {
        return Response.status(Response.Status.BAD_REQUEST).build(); 
    }
}

EDIT 18.11.2013: 编辑 18.11.2013:

I guess you could always you override the EntryPoint class (as seen in this StackOverflow Question ). 我想你总是可以覆盖EntryPoint类(如本StackOverflow问题所示 )。

An customized OAuth2ExceptionRenderer (see SO: Customize SpringSecurity OAuth2 Error Output would work too. 自定义的OAuth2ExceptionRenderer(请参阅SO:自定义SpringSecurity OAuth2错误输出也可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 用于2脚(客户端凭据)OAuth2服务器的Spring-security上下文设置 - Spring-security context setup for 2-legged (client credentials) OAuth2 server Spring Security OAuth2数据库-错误的凭证错误 - Spring Security OAuth2 Database — Bad Credentials Error 具有spring-security的OAuth2 - 通过HTTP方法限制REST访问 - OAuth2 with spring-security - limit REST access by HTTP method 春季安全OAuth2在Google App Engine上导致服务器错误 - Spring-security OAuth2 gives server error on Google App Engine Spring Boot 2 Spring-Security 5 OAuth2 支持 client_credentials grant_type - Spring Boot 2 Spring-Security 5 OAuth2 support for client_credentials grant_type Rest Api调用使用Spring Oauth2给出错误400 - Rest Api call gives error 400 using Spring Oauth2 使用Java配置的Spring-Security OAuth2 StackOverflowError - Spring-Security OAuth2 StackOverflowError using Java config oauth2 spring-security 成功和失败处理程序 - oauth2 spring-security success and failure handler Spring-security甚至在提交表单之前就会显示“Bad Credentials” - Spring-security shows 'Bad Credentials' even before submitting the form 具有弹簧安全性的Oauth2:创建名称为&#39;oauth2TokenGranter&#39;的bean时出错:无法创建类型为ResourceOwnerPasswordTokenGranter的内部bean - Oauth2 with Spring-Security: Error creating bean with name 'oauth2TokenGranter': Cannot create inner bean of type ResourceOwnerPasswordTokenGranter
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM