在 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 ReferenceSpring Security 使用
WithSecurityContextTestExecutionListener
挂钩 Spring Test 支持,这将确保我们的测试由正确的用户运行。它通过在运行我们的测试之前填充SecurityContextHolder
来实现这一点。如果您使用反应式方法安全性,您还需要填充ReactiveSecurityContextHolder
的ReactorContextTestExecutionListener
。测试完成后,将清除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