Pet*_*ete 12 java spring jersey spring-security oauth-2.0
我们有一个基于Oauth2的REST服务器(资源+授权),由spring-security + spring web + jersey为我们的REST资源.其中大部分工作都很顺利,但是当在带有错误凭据的用户名密码流中命中/ oauth/token时,我们不仅会获得400(因为规范是正确的),而是在响应中将整个堆栈跟踪作为JSON .我已经搜索和调试并摸索了,但无法找到罪魁祸首.这可能是一个弹簧安全设置吗?还是弹簧网?或者使用泽西匹配资源的servlet?
响应示例(缩写):
$ 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"}
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?如果您需要更多信息,请告诉我(web.xml/security.xml/application.xml/servlet.xml)
谢谢!
编辑: 使用客户端凭据流与错误的凭据,它将给我401和没有堆栈跟踪.当用户名/密码不匹配时,抛出的BadCredentials/InvalidGrant异常只会导致堆栈跟踪.
编辑 - 我们的配置中的一些片段
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>
Run Code Online (Sandbox Code Playgroud)
servlet-context.xml只包含freemarker的东西并且无关紧要jersey-servlet也无关紧要,因为它只有mapps/rest/**资源,而请求的资源是/ oauth/token.这只留下了弹簧安全设置:
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" />
Run Code Online (Sandbox Code Playgroud)
好吧,对我来说似乎没有明显的地方可以在这里配置它.
堆栈跟踪表明,有一个未处理的InvalidGrantException抛出ResourceOwnerPasswordTokenGranter.所以我尝试在我的web.xml中的spring-security过滤器上方的过滤链中添加过滤器,捕获所有异常并处理它们.然而,无法工作,因为spring-security过滤器似乎自己处理InvalidGrantException,这意味着没有异常冒泡到我周围的过滤器.
将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);
}
Run Code Online (Sandbox Code Playgroud)
在那里引发了正确的异常:
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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
但是,即使它在端点回击时也从未处理过.然后,过滤链进行异常处理并添加堆栈跟踪.相反,端点应返回没有堆栈跟踪的干净400,即处理该死的异常!
现在,我能看到的唯一方法是覆盖TokenEndpoint并捕获异常.
有更好的想法吗?
您可以为该特定Exception编写ExceptionMapper,并根据请求执行任何操作.每当从端点方法抛出定义的异常时,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();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑 18.11.2013:
我想你总是可以覆盖EntryPoint类(如本StackOverflow问题所示).
自定义的OAuth2ExceptionRenderer(请参阅SO:自定义SpringSecurity OAuth2错误输出也可以.
| 归档时间: |
|
| 查看次数: |
5343 次 |
| 最近记录: |