在 SecurityContext 中找不到 Authentication 对象 - Spring 5

Posted

技术标签:

【中文标题】在 SecurityContext 中找不到 Authentication 对象 - Spring 5【英文标题】:An Authentication object was not found in the SecurityContext - Spring 5 【发布时间】:2022-01-22 21:31:15 【问题描述】:

我是 Spring Boot 和 Spring Security 的新手,并且继承了一个使用它们的 webapp 项目。我们将把 webapp 迁移到一个新的部署环境。我们将要更改的一件事是身份验证机制,以便它可以在新环境中运行。同时,我想使用一些现有的 PostMan 测试来运行 REST 端点,绕过安全性。基本上,我想暂时禁用安全性。

我有一个提供全局方法级别安全性的类:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration 
    // ...

我有多个控制器类,例如,提供用户信息的类:

@RestController
public class UserController 

    @ApiOperation(value = "Gets the list of users for an admin.")
    @PreAuthorize("#oauth2.hasScope('dashboard') and #oauth2.hasScope('read') and hasRole('ROLE_SYSADMIN')")
    @GetMapping(value = "/user/list", produces = MediaType.APPLICATION_JSON_VALUE)
    @AuditAccess(message = "Accessing /user/list")
    public ResponseEntity<List<UserResponse>> getUserList() 
        // ...
    

    // ...

如果我尝试运行我的 PostMan 测试,它们会失败,因为在我的本地测试环境中没有设置身份验证机制。我第一次尝试绕过安全性是注释掉@EnableGlobalMethodSecurity 行,但这导致了运行时错误,可能是由于方法上的@PreAuthorize 注释。如果我也注释掉这些,我可以让测试运行。但是,有许多这样的注释分布在许多文件中。我宁愿不评论这一切。我宁愿暂时替换一个“什么都不做”的方法安全版本。这是我的尝试:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration 

    /**
     * This class is designed to let everything pass the method filter, i.e.,
     * effectively removing method level security.
     */
    class DoNothingMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler 

        public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) 
            return filterTarget;
        

        @Override
        public void setReturnObject(Object returnObject, EvaluationContext ctx) 
            // do nothing
        
    

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() 
        MethodSecurityExpressionHandler doNothingHandler =
                new DoNothingMethodSecurityExpressionHandler();
        return doNothingHandler;
// TODO restore below
//        return new OAuth2MethodSecurityExpressionHandler();
    

如果我尝试运行我的 PostMan 测试

GET http://localhost:8080/myapp/user/list

我收到一个错误:

2021-12-21 06:28:14,252 INFO  [stdout] (default task-1) Request: http://localhost:8080/myapp/user/list raised
2021-12-21 06:28:14,253 INFO  [stdout] (default task-1) org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
2021-12-21 06:28:14,254 INFO  [stdout] (default task-1)     at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:379) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
2021-12-21 06:28:14,254 INFO  [stdout] (default task-1)     at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:223) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
2021-12-21 06:28:14,254 INFO  [stdout] (default task-1)     at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:65) ~[spring-security-core-5.3.1.RELEASE.jar:5.3.1.RELEASE]
2021-12-21 06:28:14,255 INFO  [stdout] (default task-1)     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,255 INFO  [stdout] (default task-1)     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,256 INFO  [stdout] (default task-1)     at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,257 INFO  [stdout] (default task-1)     at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,257 INFO  [stdout] (default task-1)     at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,257 INFO  [stdout] (default task-1)     at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,258 INFO  [stdout] (default task-1)     at mycompany.rest.controller.UserController$$EnhancerBySpringCGLIB$$f2b6b566.getUserList(<generated>) ~[classes:?]
2021-12-21 06:28:14,259 INFO  [stdout] (default task-1)     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_271]
2021-12-21 06:28:14,259 INFO  [stdout] (default task-1)     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_271]
2021-12-21 06:28:14,260 INFO  [stdout] (default task-1)     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_271]
2021-12-21 06:28:14,260 INFO  [stdout] (default task-1)     at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_271]
2021-12-21 06:28:14,260 INFO  [stdout] (default task-1)     at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,261 INFO  [stdout] (default task-1)     at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,261 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,263 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,264 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,265 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,265 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]
2021-12-21 06:28:14,266 INFO  [stdout] (default task-1)     at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.5.RELEASE.jar:5.2.5.RELEASE]

出于某种原因,我们仍在尝试进行身份验证。

这个问题与其他一些问题(Spring Boot 2.0 Disable Default Security、Spring Oauth2 : Authentication Object was not found in the SecurityContext 和 An Authentication object was not found in the SecurityContext - Spring 3.2.2)类似,但涉及不同的版本和不同的用例。我无法弄清楚如何将这些答案应用于我的情况。我的 Spring 环境包括:

<spring.version>5.2.5.RELEASE</spring.version>
<spring.oath2.version>2.4.1.RELEASE</spring.oath2.version>
<spring.boot.version>2.2.6.RELEASE</spring.boot.version>
<spring.jwt.version>1.1.0.RELEASE</spring.jwt.version>
<spring.security.version>5.3.1.RELEASE</spring.security.version>

什么是绕过安全的简单方法?

【问题讨论】:

我首先想知道你为什么使用多个不同版本的 spring,spring 是一个生态系统,版本可以协同工作,spring-boot 自动拉入 spring 上下文,并且 spring security 已经自带nimbus jwt 库,所以我首先想知道奇怪的依赖用法的原因 如果这是一个 spring boot 应用程序,你应该使用 spring boot parent 或 bom,然后使用 spring boot 启动器让 spring 为你处理版本。为什么我要指出这一点是因为我不想对由于混乱的依赖关系而“可能不起作用”的事情给出答案 @Toerktumlare - 我不知道为什么版本不一致;这些是我继承的版本号。我会尽力清理它们。 【参考方案1】:

您可以尝试设置 prePostEnabled = false,然后在 WebSecurityConfigurerAdapter 实现中删除任何身份验证过滤器,例如

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
        httpSecurity.csrf().disable().authorizeRequests()
                .anyRequest().permitAll();

    

【讨论】:

【参考方案2】:

您可以保留它而不是删除 @EnableGlobalMethodSecurity,但只需禁用它的 prePostEnabled

但它需要至少启用以下一项(详见this):

prePostEnabled(用于启用@PreAuthorize / @PreFilter / @PostAuthorize / @PostFilter) securedEnabled(用于启用@Secured) jsr250Enabled(用于启用 JSR-250 注释,例如 @DenyAll@PermitAll@RolesAllowed) 定义一个自定义MethodSecurityMetadataSource

由于您只在现有配置中启用prePostEnabled,我相信您现在不应该在任何方法上使用任何@Secured 和JSR-250 注解。

所以下面的技巧应该只禁用@PreAuthorize,同时保持其他东西保持不变:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = false, securedEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration 


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = false, jsr250Enabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration 


【讨论】:

【参考方案3】:

测试的“切换”代码(取消/注释)是不行的! (在“严格的团队”中,您甚至可能不会提交注释代码!-> sonarqube)

在复杂(分布式)环境中,拥有(spring)@Profile("test")(例如关闭安全性)听起来绝对合法!

(单元+集成)测试您的安全性(+配置)仍然是 [非常好 - 必不可少的]!

Spring Security 5.5.x Reference Documentation, Chapter 19 Testing

本节介绍 Spring Security 提供的测试支持。

19.1. Testing Method Security

在我们可以使用 Spring Security Test 支持之前,我们必须执行一些设置。下面是一个例子:

@RunWith(SpringJUnit4ClassRunner.class) // 1.
@ContextConfiguration // 2.
public class WithMockUserTests  ...

(我们可以两者合二为一:@SpringBootTest ;)

这是一个如何设置 Spring Security Test 的基本示例。重点是:

    @RunWith 指示 spring-test 模块它应该创建一个 ApplicationContext。这与使用现有的 Spring Test 支持没有什么不同。如需更多信息,请参阅Spring Reference @ContextConfiguration 指示 spring-test 配置用于创建 ApplicationContext。由于未指定配置,因此将尝试默认配置位置。这与使用现有的 Spring Test 支持没有什么不同。如需更多信息,请参阅Spring Reference

Spring Security 使用 WithSecurityContextTestExecutionListener 挂钩 Spring Test 支持,这将确保我们的测试由正确的用户运行。它通过在运行我们的测试之前填充SecurityContextHolder 来实现这一点。如果您使用反应式方法安全性,您还需要填充ReactiveSecurityContextHolderReactorContextTestExecutionListener。测试完成后,将清除SecurityContextHolder。如果只需要 Spring Security 相关支持,可以将@ContextConfiguration替换为@SecurityTestExecutionListeners

请记住,我们在HelloMessageService 中添加了@PreAuthorize 注释,因此它需要经过身份验证的用户才能调用它。如果我们运行以下测试,我们预计以下测试将通过:

@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() 
   messageService.getMessage();

(测试预期(身份验证)异常!),所以之一:

19.1.2. @WithMockUser

...

19.1.3. @WithAnonymousUser

...

19.1.4. @WithUserDetails

...

19.1.5. @WithSecurityContext

...

19.1.6. Test Meta Annotations

...以及本章/参考的其余部分,将很有用。

【讨论】:

我支持这个答案,测试应该在启用安全性的情况下运行。在这种有邮递员测试的情况下,den a profile 将是最合适的。但是应该尽快将测试从 postman 重写为 spring boot。

以上是关于在 SecurityContext 中找不到 Authentication 对象 - Spring 5的主要内容,如果未能解决你的问题,请参考以下文章

在 SecurityContext 中找不到 Authentication 对象 - Spring 3.2.2

Symfony:在 SecurityContext 中找不到用于防火墙后路由的令牌

Spring Boot junit测试安全应用程序

Firebase 规则自行重置

Audiokit 修剪音频

找不到Joomla文件错误