【问题标题】:An Authentication object was not found in the SecurityContext - Spring 5在 SecurityContext 中找不到 Authentication 对象 - 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 SecuritySpring Oauth2 : Authentication Object was not found in the SecurityContextAn 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 - 我不知道为什么版本不一致;这些是我继承的版本号。我会尽力清理它们。

标签: spring spring-boot spring-mvc spring-security


【解决方案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 的基本示例。重点是:

      1. @RunWith 指示 spring-test 模块它应该创建一个 ApplicationContext。这与使用现有的 Spring Test 支持没有什么不同。如需更多信息,请参阅Spring Reference
      2. @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。
      猜你喜欢
      • 2013-03-08
      • 2014-04-04
      • 2013-05-09
      • 2019-10-28
      • 2013-05-18
      • 2016-03-11
      • 2016-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多