Spring security oauth 2 和客户端凭据流
Posted
技术标签:
【中文标题】Spring security oauth 2 和客户端凭据流【英文标题】:Spring security oauth 2 and client credentials flow 【发布时间】:2014-05-04 20:17:00 【问题描述】:我正在尝试实现一个简单的客户端凭据流 spring-security-oauth2 api。我试图改编 sparklr 和 tonr 的例子,但没有成功。我也尝试遵循此线程的代码:Spring-security context setup for 2-legged (client credentials) OAuth2 server 但它似乎不适用于我。 有人可以给我看一个例子或帮助我完成这项工作。
spring-servlet.xml 在 sparklr 应用程序中
<?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:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:sec="http://www.springframework.org/schema/security"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="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
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- Just for testing... -->
<http pattern="/oauth/cache_approvals" security="none" xmlns="http://www.springframework.org/schema/security" />
<http pattern="/oauth/uncache_approvals" security="none" xmlns="http://www.springframework.org/schema/security" />
<http pattern="/oauth/token" create-session="stateless" 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" before="BASIC_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling
separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
<http pattern="/oauth/(users|clients)/.*" request-matcher="regex" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
use-expressions="true" xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/oauth/users/([^/].*?)/tokens/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('write')"
method="DELETE" />
<intercept-url pattern="/oauth/users/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('read')"
method="GET" />
<intercept-url pattern="/oauth/clients/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')" method="GET" />
<intercept-url pattern="/**" access="denyAll()"/>
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="oauthWebExpressionHandler" />
</http>
<!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling
separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
<http pattern="/photos/**" 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="/photos" access="ROLE_USER,SCOPE_READ" />
<intercept-url pattern="/photos/trusted/**" access="ROLE_CLIENT,SCOPE_TRUST" />
<intercept-url pattern="/photos/user/**" access="ROLE_USER,SCOPE_TRUST" />
<intercept-url pattern="/photos/**" access="ROLE_USER,SCOPE_READ" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling
separately. This isn't mandatory, but it makes it easier to control the behaviour. -->
<http pattern="/me/**" 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="/me" access="ROLE_USER,SCOPE_READ" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<http access-denied-page="/login.jsp?authorization_error=true" 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>
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="sparklr2" />
</bean>
<bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="sparklr2/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-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="marissa" password="koala" authorities="ROLE_USER" />
<user name="paul" password="emu" 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>
<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" />
</bean>
<bean id="userApprovalHandler" class="org.springframework.security.oauth.examples.sparklr.oauth.SparklrUserApprovalHandler">
<property name="autoApproveClients">
<set>
<value>my-less-trusted-autoapprove-client</value>
</set>
</property>
<property name="tokenServices" ref="tokenServices" />
</bean>
<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="sparklr" token-services-ref="tokenServices" />
<oauth:client-details-service id="clientDetails">
<oauth:client client-id="my-trusted-client" authorized-grant-types="password,authorization_code,refresh_token,implicit"
authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT" scope="read,write,trust" access-token-validity="60" />
<oauth:client client-id="my-trusted-client-with-secret" authorized-grant-types="password,authorization_code,refresh_token,implicit"
secret="somesecret" authorities="ROLE_CLIENT, ROLE_TRUSTED_CLIENT" />
<oauth:client client-id="my-client-with-secret" authorized-grant-types="client_credentials" authorities="ROLE_CLIENT"
scope="read" secret="secret" />
<oauth:client client-id="my-less-trusted-client" authorized-grant-types="authorization_code,implicit"
authorities="ROLE_CLIENT" />
<oauth:client client-id="my-less-trusted-autoapprove-client" authorized-grant-types="implicit"
authorities="ROLE_CLIENT" />
<oauth:client client-id="my-client-with-registered-redirect" authorized-grant-types="authorization_code,client_credentials"
authorities="ROLE_CLIENT" redirect-uri="http://anywhere?key=value" scope="read,trust" />
<oauth:client client-id="my-untrusted-client-with-registered-redirect" authorized-grant-types="authorization_code"
authorities="ROLE_CLIENT" redirect-uri="http://anywhere" scope="read" />
<oauth:client client-id="tonr" resource-ids="sparklr" authorized-grant-types="client_credentials"
authorities="ROLE_CLIENT" scope="read,write" secret="secret" />
</oauth:client-details-service>
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<sec: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 -->
<sec:expression-handler ref="oauthExpressionHandler" />
</sec:global-method-security>
<oauth:expression-handler id="oauthExpressionHandler" />
<oauth:web-expression-handler id="oauthWebExpressionHandler" />
<!--Basic application beans. -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
</map>
</property>
<property name="viewResolvers">
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</property>
<property name="defaultViews">
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="extractValueFromSingleKeyModel" value="true" />
</bean>
</property>
</bean>
<bean id="photoController" class="org.springframework.security.oauth.examples.sparklr.mvc.PhotoController">
<property name="photoService" ref="photoServices" />
</bean>
<bean id="photoServiceUserController" class="org.springframework.security.oauth.examples.sparklr.mvc.PhotoServiceUserController">
<property name="userDetailsService" ref="userDetailsService" />
</bean>
<bean id="adminController" class="org.springframework.security.oauth.examples.sparklr.mvc.AdminController">
<property name="tokenServices" ref="tokenServices" />
<property name="userApprovalHandler" ref="userApprovalHandler" />
</bean>
<!-- Override the default mappings for approval and error pages -->
<bean id="accessConfirmationController" class="org.springframework.security.oauth.examples.sparklr.mvc.AccessConfirmationController">
<property name="clientDetailsService" ref="clientDetails" />
</bean>
<bean id="photoServices" class="org.springframework.security.oauth.examples.sparklr.impl.PhotoServiceImpl">
<property name="photos">
<list>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="1" />
<property name="name" value="photo1.jpg" />
<property name="userId" value="marissa" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo1.jpg" />
</bean>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="2" />
<property name="name" value="photo2.jpg" />
<property name="userId" value="paul" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo2.jpg" />
</bean>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="3" />
<property name="name" value="photo3.jpg" />
<property name="userId" value="marissa" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo3.jpg" />
</bean>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="4" />
<property name="name" value="photo4.jpg" />
<property name="userId" value="paul" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo4.jpg" />
</bean>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="5" />
<property name="name" value="photo5.jpg" />
<property name="userId" value="marissa" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo5.jpg" />
</bean>
<bean class="org.springframework.security.oauth.examples.sparklr.PhotoInfo">
<property name="id" value="6" />
<property name="name" value="photo6.jpg" />
<property name="userId" value="paul" />
<property name="resourceURL" value="/org/springframework/security/oauth/examples/sparklr/impl/resources/photo6.jpg" />
</bean>
</list>
</property>
</bean>
</beans>
spring-servlet.xml 在 tonr 应用程序中
<?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:sec="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.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
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<http access-denied-page="/login.jsp?authorization_error=true" xmlns="http://www.springframework.org/schema/security">
<intercept-url pattern="/sparklr/**" access="ROLE_USER" />
<intercept-url pattern="/facebook/**" 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 />
<custom-filter ref="oauth2ClientFilter" after="EXCEPTION_TRANSLATION_FILTER" />
</http>
<authentication-manager xmlns="http://www.springframework.org/schema/security">
<authentication-provider>
<user-service>
<user name="marissa" password="wombat" authorities="ROLE_USER" />
<user name="sam" password="kangaroo" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<!--apply the oauth client context -->
<oauth:client id="oauth2ClientFilter" />
<!-- define an oauth 2 resource for sparklr -->
<oauth:resource id="sparklr" type="client_credentials" client-id="tonr" client-secret="secret"
access-token-uri="$accessTokenUri" scope="read,write" />
<!--define an oauth 2 resource for trusted client on sparklr -->
<oauth:resource id="trusted" type="client_credentials" client-id="my-client-with-registered-redirect"
access-token-uri="$accessTokenUri" scope="trust" />
<!--define an oauth 2 resource for facebook. according to the facebook docs, the 'client-id' is the App ID, and the 'client-secret'
is the App Secret -->
<oauth:resource id="facebook" type="authorization_code" client-id="233668646673605" client-secret="33b17e044ee6a4fa383f46ec6e28ea1d"
authentication-scheme="query" access-token-uri="https://graph.facebook.com/oauth/access_token" user-authorization-uri="https://www.facebook.com/dialog/oauth"
token-name="oauth_token" client-authentication-scheme="form" />
<context:property-placeholder location="classpath:/sparklr.properties" />
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.springframework.security.oauth.examples.tonr.converter.AccessTokenRequestConverter" />
</set>
</property>
</bean>
<mvc:default-servlet-handler />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.BufferedImageHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="contentViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultViews">
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</property>
</bean>
<!--Basic application beans. -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="sparklrController" class="org.springframework.security.oauth.examples.tonr.mvc.SparklrController">
<property name="sparklrService" ref="sparklrService" />
</bean>
<bean id="facebookController" class="org.springframework.security.oauth.examples.tonr.mvc.FacebookController">
<property name="facebookRestTemplate">
<oauth:rest-template resource="facebook">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<!--facebook sends its json as text/javascript for some reason -->
<constructor-arg value="text" />
<constructor-arg value="javascript" />
</bean>
<bean class="org.springframework.http.MediaType">
<constructor-arg value="application" />
<constructor-arg value="json" />
</bean>
</list>
</property>
</bean>
</list>
</property>
</oauth:rest-template>
</property>
</bean>
<bean id="sparklrService" class="org.springframework.security.oauth.examples.tonr.impl.SparklrServiceImpl">
<property name="sparklrPhotoListURL" value="$sparklrPhotoListURL" />
<property name="sparklrTrustedMessageURL" value="$sparklrTrustedMessageURL" />
<property name="sparklrPhotoURLPattern" value="$sparklrPhotoURLPattern" />
<property name="sparklrRestTemplate">
<oauth:rest-template resource="sparklr" />
</property>
<property name="trustedClientRestTemplate">
<oauth:rest-template resource="trusted" />
</property>
</bean>
</beans>
尝试在 sparklr 应用程序上验证 tonr 应用程序时出现以下错误:
HTTP 状态 500 - 请求处理失败;嵌套异常是 error="access_denied", error_description="请求访问时出错 令牌。
我希望有人可以帮助我。谢谢
【问题讨论】:
您收到 500 错误,那么服务器上是否有任何堆栈跟踪?无论如何,您能提供您所做的令牌请求信息吗? @luizcarlosfx 我需要你的帮助,我正在尝试使用基于 java 的配置将 oauth2 与 rest 和 web 应用程序一起使用。是否可以将 oauth 用于 WEB ? @HarmeetSingh 这里有一个示例:github.com/spring-projects/spring-security-oauth/tree/master/… 使用 spring security oauth2。所有配置都是使用java代码完成的,比xml配置简单多了。是的,当然可以将 oauth 用于 WEB。此示例演示了这一点 @luizcarlosfx 感谢您的帮助。我对此有一些疑问,如果你不介意,我想和你讨论一下? 好吧,我认为这不是更好的地方。你可以问一个问题。不知道***有没有私信功能 【参考方案1】:您可以尝试允许匿名访问 /oauth/token
端点。这是我可以让我的 client_credentials 流工作的唯一方法。
改变这一行
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
到这里
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_ANONYMOUSLY" />
并删除这一行:
<anonymous enabled="false" />
希望对您有所帮助。
【讨论】:
我已经解决了这个问题。我升级了所有用于 java 配置的 spring 配置,只编写了需要的部分,并且效果很好。这个例子有很多我不需要的代码。不过谢谢你的回答【参考方案2】:替换这一行
<oauth:resource id="sparklr" type="client_credentials" client-id="tonr" client-secret="secret"
access-token-uri="$accessTokenUri" scope="read,write" />
通过
<oauth:resource id="sparklr" type="authorization_code" client-id="tonr" client-secret="secret"
access-token-uri="$accessTokenUri" scope="read,write" />
错误是 el 类型 client_credentials 并替换为 authorization_code 并且可以正常工作。
【讨论】:
正如我在问题中所说,我想将 oauth2 与客户端凭据流一起使用,而不是授权码流。但我不是反对者以上是关于Spring security oauth 2 和客户端凭据流的主要内容,如果未能解决你的问题,请参考以下文章
社交登录,spring-security-oauth2 和 spring-security-jwt?
spring-security-oauth2中的HttpSecurity配置问题
Spring security oauth 2 和客户端凭据流