Spring Oauth2:在 SecurityContext 中找不到身份验证对象

Posted

技术标签:

【中文标题】Spring Oauth2:在 SecurityContext 中找不到身份验证对象【英文标题】:Spring Oauth2 : Authentication Object was not found in the SecurityContext 【发布时间】:2017-09-01 01:21:42 【问题描述】:

我有一个项目,我在其中实现 Spring 安全性和 Spring OAuth2 安全性。当我请求访问令牌时,它运行良好,但是当我使用访问令牌请求资源时,我得到“在 SecurityContext 中找不到身份验证对象”。

我的项目的SecurityContext是:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" 
xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2
       http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
       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">

<global-method-security jsr250-annotations="enabled" />
<http pattern="/**/*.css" security="none" />
<http pattern="/**/*.css.map" security="none" />


<http pattern="/**/*.gif" security="none" />
<http pattern="/**/*.html" security="none" />
<http pattern="/**/*.ttf" security="none" />
<http pattern="/**/*.eot" security="none" />
<http pattern="/**/*.svg" security="none" />
<http pattern="/**/*.woff" security="none" />
<http pattern="/**/*.woff2" security="none" />
<http pattern="/**/*.xls" security="none" />
<http pattern="/**/*.ico" security="none" />
<http pattern="/**/*.jpg" security="none" />
<http pattern="/**/*.js" security="none" />
<http pattern="/**/*.png" security="none" />
<http pattern="/**/*.xml" security="none" />
<http pattern="/**/*.mp4" security="none" />
<http pattern="editCustomerTrnx" security="none"/>
<!--<http pattern="/embed/*" security="none"/> -->

<!-- Default URL provided by spring to get the token(access and refresh) from oauth -->
<http pattern="/oauth/token" create-session="never"
      authentication-manager-ref="clientAuthenticationManager"
      xmlns="http://www.springframework.org/schema/security">
    <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>
    <http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
    <!-- Using this to authenticate client using request parameter -->
    <custom-filter ref="clientCredentialsTokenEndPointFilter" after="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="/Api/**" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
      access-decision-manager-ref="accessDecisionManager"
      xmlns="http://www.springframework.org/schema/security">
   <anonymous enabled="false"/>
   <intercept-url pattern="/Api/**" access="ROLE_ADMIN"/>
   <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
   <access-denied-handler ref="oauthAccessDeniedHandler"/>
</http>

<!-- 2 -->
<http auto-config="true">
    <intercept-url pattern="/Admin/**"
        access="ROLE_ADMINISTRATOR,ROLE_AUTHENTICATED" requires-channel="any" />
    <intercept-url pattern="/Seller/**" access="ROLE_AUTHENTICATED,ROLE_SELLER"
        requires-channel="any" />
    <intercept-url pattern="/login/**" access="IS_AUTHENTICATED_ANONYMOUSLY"
        requires-channel="any" />
    <intercept-url  pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY"
        requires-channel="any" />
    <!-- <remember-me key="remittancerm" /> -->
    <custom-filter position="CONCURRENT_SESSION_FILTER" ref="customSessionFilter" />
    <form-login login-page="/main"

        authentication-failure-handler-ref="failureHandler"
        always-use-default-target="false" default-target-url="/"
        authentication-success-handler-ref="ash" />
    <logout logout-url="/logout" logout-success-url="/" />
    <access-denied-handler ref="" error-page="/" />
    <!-- authentication-failure-url="/main?errormessage=authentication.login.failed" -->
    <session-management
        session-authentication-strategy-ref="sls" />
    <port-mappings>
        <port-mapping http="8080" https="8443" />
    </port-mappings>
</http>

<authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">
    <authentication-provider> <!-- user-service-ref="userDetailService" -->
        <user-service>
           <user name="subash" authorities="ROLE_ADMIN" password="123456"/>
        </user-service>
        <!-- <password-encoder ref="passwordEncoder">
        </password-encoder> -->
    </authentication-provider>
</authentication-manager>

<beans:bean id="ash"
    class="com.remittance.session.CustomSavedRequestAwareAuthenticationSuccessHandler">
</beans:bean>

<beans:bean id="failureHandler" class="com.remittance.session.CustomAuthenticationFailureHandler">
</beans:bean>
<beans:bean id="forbiddenEntryPoint"
    class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint" />


<beans:bean id="customSessionFilter" class="com.remittance.session.CustomSessionFilter">
    <beans:constructor-arg ref="sessionRegistry" />
</beans:bean>

<beans:bean id="sls"
    class="com.remittance.session.SessionLoggingStrategy">
    <beans:constructor-arg ref="sas" />
    <beans:constructor-arg ref="sessionLogApi" />
</beans:bean>

<beans:bean id="sas"
    class="com.remittance.session.PersistingConcurrentSessionControlStrategy">
    <beans:constructor-arg name="sessionRegistry"
        ref="sessionRegistry" />
    <beans:constructor-arg name="sessionApi" ref="sessionApi" />
    <beans:property name="maximumSessions" value="-1" />
</beans:bean>

<beans:bean id="sessionRegistry"
    class="com.remittance.session.PersistingSessionRegistry">
    <beans:constructor-arg ref="sessionApi" />
</beans:bean>

<beans:bean id="userDetailService"
    class="com.remittance.session.UserDetailsServiceImpl">
    <beans:constructor-arg ref="userRepository" />
</beans:bean>

<beans:bean id="passwordEncoder"
    class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />


<beans:bean id="userTest" class="com.remittance.session.UserTest">
    <beans:constructor-arg ref="userRepository" />
</beans:bean>



<!-- OAuth2 Security  -->


<!-- Resource protected by oauth2 security -->

<!-- OAuth Client Details -->
<oauth2:client-details-service id="clientDetails">
   <oauth2:client client-id="android5.5" secret="1234567890" authorized-grant-types="password,authorization_code,refresh_token,implicit,client_credentials"
                 authorities="ROLE_CLIENT,ROLE_TRUSTED_CLIENT" scope="read,write,trust"/>
   <oauth2:client client-id="nokia3320" secret="0987654321" authorized-grant-types="password,authorization_code,refresh_token,implicit,client_credentials"
                 authorities="ROLE_CLIENT,ROLE_TRUSTED_CLIENT" scope="read,write,trust"/>
</oauth2:client-details-service>

 <!-- This defined token store, we have used in memory token store for now but this can be changed to a user defined one -->
 <beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore"/>

 <!-- Load User By User name -->
 <beans:bean id="clientDetailsUserDetailsService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
    <beans:constructor-arg ref="clientDetails"/>
 </beans:bean>

 <!-- This is where we defined token based configurations, token validity and other things -->
 <beans:bean id="tokenService" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
   <beans:property name="tokenStore" ref="tokenStore"/>
   <beans:property name="accessTokenValiditySeconds" value="500"/>
   <beans:property name="clientDetailsService" ref="clientDetails"/>
   <beans:property name="supportRefreshToken" value="true"/>
 </beans:bean>

 <!-- It Determine whether a given client authentication request has been approved by user or not -->
 <!-- ToeknStoreUserApprovalHandler : A user approval handler that remembers approval decisions by consulting existing tokens -->
 <beans:bean id="userApprovalHandler" class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
    <beans:property name="tokenStore" ref="tokenStore"/>
    <beans:property name="requestFactory" ref="oauth2RequestFactory"/>
 </beans:bean>


 <!-- Server issuing access token to the client after successfully authenticating the resource owner and obtaining authorization -->
 <oauth2:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenService"
                             user-approval-handler-ref="userApprovalHandler">
     <!-- <oauth2:authorization-code/> -->
     <!-- <oauth2:client-credentials/> -->
     <!-- <oauth2:implicit/> -->
     <oauth2:password/>
     <!-- <oauth2:refresh-token/> -->
 </oauth2:authorization-server>

 <authentication-manager id="clientAuthenticationManager">
     <authentication-provider user-service-ref="clientDetailsUserDetailsService"/>
 </authentication-manager>

 <!-- Include this if you need to authenticate client via request parameter -->
 <beans:bean id="clientCredentialsTokenEndPointFilter"
    class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
    <beans:property name="authenticationManager" ref="clientAuthenticationManager" />
</beans:bean>

 <!-- Server hosting the protected resource ,capable of accepting and responding to protected resource request using access tokens -->
 <oauth2:resource-server id="resourceServerFilter" resource-id="test" token-services-ref="tokenService"/>

 <!-- Authentication Entry Point -->
 <beans:bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
   <beans:property name="realmName" value="test" />
 </beans:bean>

 <beans:bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
    <beans:property name="realmName" value="test/client" />
    <beans:property name="typeName" value="Basic" />
 </beans:bean>

<!-- Access Denied Handler -->
<beans:bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>

<!-- This beans prepares oauth2Request using incoming request parameter -->
<beans:bean id="oauth2RequestFactory" class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
  <beans:constructor-arg ref="clientDetails"/>
</beans:bean>

<!-- Access Decision Manager -->
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased" xmlns="http://www.springframework.org/schema/beans">
  <beans:constructor-arg>
    <beans:list>
        <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
        <beans:bean class="org.springframework.security.access.vote.RoleVoter" />
        <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
    </beans:list>
</beans:constructor-arg>
</beans:bean>

我使用http://localhost:8060/oauth/token?grant_type=password&client_id=nokia3320&client_secret=0987654321&username=subash&password=123456 请求令牌并得到以下响应


 "access_token": "9f5a89ce-a0d9-4d65-8e83-5d3b16d8c025",
 "token_type": "bearer",
 "refresh_token": "c2ac82ec-9f41-46dd-b7c2-4772c018505c",
 "expires_in": 499,
 "scope": "read trust write"

当我尝试使用 http://localhost:8060/Api/currencyList 访问资源时,授权错误中的访问令牌出现以下响应


 "error": "unauthorized",
 "error_description": "An Authentication object was not found in the 
                      SecurityContext"

我想使用 spring oauth2 保护下面的资源

@RequestMapping(value="/currencyList",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<CurrencyDTO> getCurrencyList()

    List<CurrencyDTO> currencyList=new ArrayList<CurrencyDTO>();

    CurrencyDTO currency1 = new CurrencyDTO();
    currency1.setCurrencyCode("NEP");
    currency1.setCurrencyName("Rupees");
    currency1.setId((long)1);
    currency1.setSymbol("Rs");

    CurrencyDTO currency2 = new CurrencyDTO();
    currency2.setCurrencyCode("AM");
    currency2.setCurrencyName("Dollar");
    currency2.setId((long)2);
    currency2.setSymbol("$");

    currencyList.add(currency1);
    currencyList.add(currency2);

    return currencyList;


我在这个问题上卡了大约 2 天。我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

认证成功后,你必须在安全上下文中添加认证对象

public void successfulAuthentication(HttpServletRequest request,HttpServletResponse response,
                FilterChain chain, Authentication authentication) throws IOException, ServletException 

    //your some custom code

    // Add the authentication to the Security context
    SecurityContextHolder.getContext().setAuthentication(authentication);

【讨论】:

我认为在认证成功后 spring 本身将认证置于安全上下文中。不是吗? 当然,Spring Security 会自动为您处理这些,因此您无需担心。但如果使用自定义实现进行身份验证,则必须手动添加。【参考方案2】:

这里的问题是资源和授权服务器不共享令牌的公共内存位置。当我将 InMemoryTokenStore 更改为 JdbcTokenStore 我的问题得到解决。

【讨论】:

【参考方案3】:

请更新您的 pom 文件 spring boot 父版本 1.4.2.RELEASE

【讨论】:

【参考方案4】:

就我而言,我将我的 api 网址添加为公共网址。我的受 oauth 保护的 REST-Service url 以 /resource/api/** 开头,但我在安全配置类中添加为

@Override
public void configure(WebSecurity web) throws Exception 
    web.ignoring().antMatchers("/css/**","/js/**","/resource/**");

所以,OAuth2AuthenticationProcessingFilter 没有调用,我的 api 服务器上出现 Authentication Object was not found in the SecurityContext 异常。

【讨论】:

以上是关于Spring Oauth2:在 SecurityContext 中找不到身份验证对象的主要内容,如果未能解决你的问题,请参考以下文章

Spring:为啥在端点中返回整个 OAuth2User 是不好的?

Spring OAuth2.0 - 动态注册 OAuth2.0 客户端

Spring Cloud Oauth2 初探

oauth2 spring-security 如果您在请求令牌或代码之前登录

Oauth2 Spring 中的更改响应

Spring Cloud Gateway OAuth2 with Spring Security OAuth2 Authorization Server = loop