Spring Security 4 + OAuth2 = 坏凭证

Posted

技术标签:

【中文标题】Spring Security 4 + OAuth2 = 坏凭证【英文标题】:Spring Security 4 + OAuth2 = Bad Credentials 【发布时间】:2016-09-12 13:34:47 【问题描述】:

我正在使用 Spring Security + Oauth2 进行多个提议。场景是:有一个公共上下文、一个私有上下文和一个 REST 上下文(最后两个都经过身份验证)。

对于私有上下文 (/private/),未通过身份验证的用户应重定向到 /login,在进行身份验证之前,将重定向到 /private/index。对于 REST 上下文 (/rest/),用户应该拥有一个令牌,然后才能访问该区域。

记住这是一个 POC,代码非常基础。

spring-security.xml

<beans:beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
                    http://www.springframework.org/schema/beans/spring-beans.xsd
                    http://www.springframework.org/schema/security 
                    http://www.springframework.org/schema/security/spring-security.xsd
                    http://www.springframework.org/schema/security/oauth2 
                    http://www.springframework.org/schema/security/spring-security-oauth2.xsd">

<sec:http pattern="/oauth/token" create-session="stateless"
    authentication-manager-ref="clientAuthenticationManager"
    use-expressions="true" xmlns="http://www.springframework.org/schema/security">
    <sec:intercept-url pattern="/oauth/token" access="isAuthenticated()" />
    <sec:anonymous enabled="false" />
    <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
    <!-- include this only if you need to authenticate clients via request 
        parameters -->
    <sec:custom-filter ref="clientCredentialsTokenEndpointFilter"
        after="BASIC_AUTH_FILTER" />
    <sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>

<!-- Spring Security Context -->
<sec:http auto-config="true" use-expressions="true"
    xmlns="http://www.springframework.org/schema/security">
    <sec:intercept-url pattern="/" access="permitAll" />
    <sec:intercept-url pattern="/private/**" access="hasRole('ROLE_USER')" />
    <sec:form-login authentication-failure-url="/login?error" />
    <sec:logout logout-success-url="/login?logout" />
</sec:http>

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

<bean id="clientAuthenticationEntryPoint"
    class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <property name="realmName" value="test/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">
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
            <bean
                class="org.springframework.security.web.access.expression.WebExpressionVoter" />
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
        </list>
    </constructor-arg>
</bean>

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

<sec:authentication-manager alias="authenticationManager"
    xmlns="http://www.springframework.org/schema/security">
    <sec:authentication-provider>
        <sec:user-service id="userDetailsService">
            <sec:user name="admin" password="admin" authorities="ROLE_ADMIN, ROLE_USER" />
            <sec:user name="user" password="user" authorities="ROLE_USER" />
        </sec:user-service>
    </sec:authentication-provider>
</sec:authentication-manager>

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

<!-- Used for the persistenceof tokens (currently an in memory implementation) -->
<bean id="tokenStore"
    class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />

<!-- Used to create token and and every thing about them except for their 
    persistence that is reposibility of TokenStore (Given here is a default implementation) -->
<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.DefaultUserApprovalHandler">
</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">
    <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="test"
        authorized-grant-types="authorization_code,implicit" authorities="ROLE_CLIENT"
        scope="read,write" secret="secret" />
    <!--Self defined 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-details-service>

<sec:global-method-security
    pre-post-annotations="enabled" proxy-target-class="true"
    xmlns="http://www.springframework.org/schema/security">
    <!--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" />

【问题讨论】:

【参考方案1】:

问题解决了。

如需进一步咨询,只需将“alias”一词更改为“id”,如下所示

<sec:authentication-manager alias="authenticationManager"
xmlns="http://www.springframework.org/schema/security">

正确:

<sec:authentication-manager id="authenticationManager"
xmlns="http://www.springframework.org/schema/security">

并且有效!

【讨论】:

以上是关于Spring Security 4 + OAuth2 = 坏凭证的主要内容,如果未能解决你的问题,请参考以下文章

Spring security 4.2.3,OAUTH 2,/oauth/token 端点,CORS 不工作

Spring Security 4 + OAuth2 = 坏凭证

针对授权标头的Spring Security OAuth2 CORS问题

Spring Security OAuth2分布式系统认证解决方案

Oauth2 资源服务器重叠 Spring Security 配置

授权标头的 Spring Security OAuth2 CORS 问题