首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >错误凭据(400)中不需要的堆栈跟踪( oauth2 REST服务器)对春季安全的错误响应

错误凭据(400)中不需要的堆栈跟踪( oauth2 REST服务器)对春季安全的错误响应
EN

Stack Overflow用户
提问于 2013-09-02 08:00:44
回答 1查看 6.1K关注 0票数 12

我们有一个基于Oauth2的REST服务器(资源+授权)--安全+ spring +运动衫作为REST资源。但是,当在用户名密码流中访问/oauth/token时,我们不仅得到了一个400 (规范可能是正确的),而且在响应中得到了作为JSON的整个堆栈跟踪。我搜索,调试,摸索,但找不到罪魁祸首。这会是一个春季安全设置吗?还是春网?还是使用jersey的servlet来处理资源呢?

示例响应(短消息):

代码语言:javascript
运行
复制
$ 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"}

有什么想法吗?如果您需要更多的信息,请告诉我(web.xml/security.xml/application.xml/servlet.xml)。

谢谢!

编辑:使用客户端凭据流与糟糕的凭证,它将给我一个401和没有堆栈跟踪。只是当用户名/密码不匹配时抛出的BadCredentials / InvalidGrant异常将导致堆栈跟踪。

编辑-来自配置的一些片段

web.xml

代码语言:javascript
运行
复制
<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>

servlet-context.xml只包含共济会成员的内容,不应该重要的是jersey-servlet也不重要,因为它只有mapp /rest/**资源,所请求的资源是/oauth/token。这只剩下春季安全设置:

代码语言:javascript
运行
复制
    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" />

嗯,在我看来,这里似乎没有明显的位置来配置这个。

堆栈跟踪表明,有一个未处理的InvalidGrantExceptionResourceOwnerPasswordTokenGranter抛出。因此,我尝试在我的web.xml中的spring安全过滤器之上的过滤器链中添加过滤器,捕捉所有异常并处理它们。但是不能工作,因为弹簧安全过滤器似乎自己处理InvalidGrantException,这意味着我周围的过滤器上也没有例外的气泡。

TokenEndpoint (@RequestMapping(value = "/oauth/token"))调用ResourceOwnerPasswordTokenGranter来验证用户名/密码:

代码语言:javascript
运行
复制
@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);
    }

在此引发正确的例外情况:

代码语言:javascript
运行
复制
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());
        }
    }

}

但从未处理过,即使当它返回端点时也是如此。然后,过滤器链执行异常处理,并添加堆栈跟踪。相反,端点应该返回一个干净的400没有堆栈跟踪,即处理该死的异常!

现在,我看到的唯一方法是重写TokenEndpoint并捕获异常。

有更好的主意吗?

EN

回答 1

Stack Overflow用户

发布于 2013-10-15 08:10:37

您可以为这个特定的异常编写一个ExceptionMapper,并对请求做任何您想做的事情。每当从端点方法抛出已定义的异常时,ExceptionMapper将处理响应。

代码语言:javascript
运行
复制
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(); 
    }
}

编辑 18.11.2013:

我想您可以始终重写EntryPoint类(如this StackOverflow Question中所示)。

定制的OAuth2ExceptionRenderer (参见SO: Customize SpringSecurity OAuth2 Error Output也能工作)。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18568277

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档