Spring Security Oauth - 发送令牌请求时所需的基本访问身份验证

时间:2014-02-20 12:26:25

标签: java spring spring-mvc oauth spring-security

我遇到了Spring Security OAuth 2.0的一个问题。

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

<http pattern="/path/**" 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="/path/*" access="ROLE_USER" />
    <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
    <access-denied-handler ref="oauthAccessDeniedHandler" />
</http>

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

<bean id="clientAuthenticationEntryPoint"
    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <property name="realmName" value="path/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>

<bean id="oauthAuthenticationProvider" class="my.package.OAuthAuthenticationProvider" xmlns="http://www.springframework.org/schema/beans" />

<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 ref="oauthAuthenticationProvider" />
</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.oauth2.provider.approval.TokenServicesUserApprovalHandler">
    <property name="tokenServices" ref="tokenServices" />
</bean>

<!-- authorization-server aka AuthorizationServerTokenServices is an interface 
    that defines everything necessary for token management -->
<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="test" token-services-ref="tokenServices" />
<!-- ClientsDeailsService: Entry Point to clients database (given is in 
    memory implementation) -->
<oauth:client-details-service id="clientDetails">
    <!-- client -->
    <oauth:client client-id="the_client"
        authorized-grant-types="authorization_code,client_credentials"
        authorities="ROLE_USER" scope="read,write,trust" secret="secret" />

    <oauth:client client-id="my-trusted-client-with-secret"
        authorized-grant-types="password,authorization_code,refresh_token,implicit"
        secret="somesecret" authorities="ROLE_USER" />

</oauth:client-details-service>

<sec:global-method-security
    pre-post-annotations="enabled" proxy-target-class="true">
    <sec:expression-handler ref="oauthExpressionHandler" />
</sec:global-method-security>

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

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

在我的web.xml文件中看起来像这样:

...
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring-security.xml</param-value>
</context-param>
...
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
...
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
...
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
...
<servlet>
    <servlet-name>Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
...
<servlet-mapping>
    <servlet-name>Dispatcher Servlet</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping> 

但在这种情况下,系统的“旧部分”不起作用。 所以我必须更改Dispatcher Servlet映射:

<servlet-mapping>
    <servlet-name>Dispatcher Servlet</servlet-name>
    <url-pattern>/somepath/*</url-pattern>
</servlet-mapping> 

现在旧的部分有效,但不是Oauth Spring的安全性。我试过两个网址模式:

<servlet-mapping>
    <servlet-name>Dispatcher Servlet</servlet-name>
    <url-pattern>/somepath/*</url-pattern>
    <url-pattern>/*</url-pattern>
</servlet-mapping> 

然后oauth工作,旧部分没有。

我又做了一次尝试并改变了:

<http pattern="/somepath/oauth/token" create-session="stateless"
    authentication-manager-ref="clientAuthenticationManager"
    xmlns="http://www.springframework.org/schema/security">
    <intercept-url pattern="/somepath/oauth/token" access="IS_AUTHENTICATED_FULLY" />
    <anonymous enabled="false" />
    <http-basic entry-point-ref="clientAuthenticationEntryPoint" />
    <custom-filter ref="clientCredentialsTokenEndpointFilter"
        after="BASIC_AUTH_FILTER" />
    <access-denied-handler ref="oauthAccessDeniedHandler" />
</http>

现在它的工作原理如下:

  1. 我正在访问地址(之前有效):

    https://localhost.server:8443/system/somepath/oauth/token?grant_type=password&client_id=my-trusted-client-with-secret&client_secret=somesecret&username=user&password=pass

  2. Web浏览器要求登录和密码(基本访问身份验证)。

  3. 我需要在登录字段输入client-id,在密码字段输入secret。

  4. 生成对oauth的请求,然后调用my.package.OAuthAuthenticationProvider中的身份验证方法。

  5. 我应该更改哪些内容以避免此基本身份验证提示?

2 个答案:

答案 0 :(得分:2)

您对servlet和过滤器映射的问题源于Spring OAuth需要一个DispatcherServlet用于其OAuth相关端点。我建议你把它们放在默认的servlet映射(/)中,但这取决于你。在任何情况下,您的一站式安全配置都是servlet配置文件,而不是根上下文(因此您需要使用DispatcherServlet而不是ContextLoaderListener加载它。这与示例中的相同(您需要在web.xml中使用servlet init参数),但没有什么可以阻止您添加自己的附加servlet(只要映射不会发生冲突)。

身份验证问题不同。 OAuth2规范建议使用基本身份验证来保护/ token端点,因此提示原则上是一件好事,尽管我可以看到您试图避免它。在你正在做的表单字段中放置秘密并不足以在生产系统中使用,但如果由于某种原因你需要它,你可以使用ClientCredentialsTokenEndpointFilter(就像你做的那样)。过滤器无法正常工作的原因可能是因为您正在发送GET(而不是POST),出于安全目的,这比首先使用过滤器更糟糕,但我没有看到标志被设置在过滤器中打开仅POST验证的任何地方(虽然我建议你在任何地方愤怒地使用这个东西时设置它。)

你没有显示2个Spring XML文件,但是你的web.xml引用了2.我认为这个问题最终可以追溯到你将它们混淆在一起或者豆类定义在两者之间重复的事实。也许你可以澄清一下(或者按照上面的建议切换到DispatcherServlet加载一个文件)?

答案 1 :(得分:0)

您可以使用以下配置来避免登录页面:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests()
                .antMatchers("/").permitAll()
            .and()
                .authorizeRequests().anyRequest().hasRole("USER")
            .and()
                .csrf()
                .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/**")).disable();
        // @formatter:on
    }
    ...
}

默认情况下,此方法包含登录页面。使用这个新实现,如果您尝试在没有令牌的情况下执行请求,将显示403错误页面(您还可以管理此错误以重定向到自定义页面)