英文:
Strange Security Error in Spring Boot 3.0.2
问题
以下是您提供的内容的翻译:
我在Spring Boot 3.0.2中遇到了一个非常奇怪的安全问题。
当我尝试使用`@PreAuthorize`来保护REST服务,并且方法包含参数时,我会收到以下错误消息:
未指定类型为[int]的参数名称,并且在类文件中也找不到参数名称信息。
如果我移除这个注解,一切又恢复正常。
以下是我的源代码:
这个工作,方法列表上没有参数:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll() { // <<--
return currencyController.listAll(0, 100);
}
}
这个也有效,没有PreAuthorize,但有参数:
@RestController
@RequestMapping("/api/currency")
//@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0") int page, // <<--
@RequestParam(defaultValue = "100") int size) { // <<--
return currencyController.listAll(page, size);
}
}
这个不工作:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0") int page, // <<--
@RequestParam(defaultValue = "100") int size) { // <<--
return currencyController.listAll(page, size);
}
}
我不知道我该怎么办?有人有什么想法吗?
我的SecurityFilterChain:
http.securityMatcher("/api/**")
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(authenticationConverter);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.securityMatcher("/api/**")
.authorizeHttpRequests()
.requestMatchers("/api/**")
.authenticated();
http.build();
编辑:
这也不起作用:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0",
name = "page") int page, // <<--
@RequestParam(defaultValue = "100",
name = "size") int size) { // <<--
return currencyController.listAll(page, size);
}
}
还有,javac的参数-g:vars和/或-parameters也没有帮助:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<compilerArgs>
<arg>-g:vars</arg>
<arg>-parameters</arg>
</compilerArgs>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
编辑2:
如果我使用@EnableGlobalMethodSecurity,应用程序将无法启动。我收到以下错误消息:
上下文初始化期间遇到异常 - 取消刷新尝试:org.springframework.beans.factory.support.BeanDefinitionOverrideException:
名为'metaDataSourceAdvisor'的bean定义无效,定义在null中:无法注册bean定义
[根bean:类[org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor];作用域=;抽象=false;lazyInit=null;autowireMode=0;dependencyCheck=0;autowireCandidate=true;primary=false;factoryBeanName=null;factoryMethodName=null;initMethodNames=null;destroyMethodNames=null]
用于bean'metaDataSourceAdvisor',因为已经有一个
[根bean:类[org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor];作用域=;抽象=false;lazyInit=null;autowireMode=0;dependencyCheck=0;autowireCandidate=true;primary=false;factoryBeanName=null;factoryMethodName=null;initMethodNames=null;destroyMethodNames=null]绑定。
已经完成,我已经添加了注释。
编辑3:
如果我不使用@RequestParam,而是这样做,它可以工作:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(Map<String, String> reqParam) { // <<--
int page = 0;
int size = 100;
try {
page = Integer.parseInt(reqParam.get("page"));
size = Integer.parseInt(reqParam.get("size"));
} catch (Exception e) {
page = 0;
size = 100;
}
if (size <= 0) {
size = 100;
}
return currencyController.listAll(page, size);
}
}
编辑4:
问题更奇怪。如果我现在进行POST请求,那么只会在"add"方法中收到一个完全空的对象。当然,我会收到一个NotNull错误。如果我关闭安全性,对象会干净而完整地传递。
@PostMapping("/")
@PreAuthorize("hasAnyRole('api-write','frontend')")
public ResponseEntity add(@RequestBody Currency newObject) {
System.err.println(newObject.toString());
return new ResponseEntity(this.controller.add(newObject), HttpStatus.OK);
}
错误消息:
ConstraintViolationImpl{interpolatedMessage='darf nicht null sein', propertyPath=updatedBy, rootBeanClass=class de.bewidata.framework.entity.zrentity.Currency, messageTemplate='{jakarta.validation.constraints.NotNull.message}'}
发送对象:
{"objectType":"Currency","objectId":"2ea69820-1f8b-46c8-80c1-7e61e2f983fa","objectVersion":0,"createdAt":"2023-03-02T15:44:37.690+01:00","createdBy":"system","updatedAt":"2023-03-02T15:44:37.690+01:00","updatedBy":"system","deleted":false,"deletedAt":null,"deletedBy":null,"deactivated":false,"deactivatedAt":null,"id":0
<details>
<summary>英文:</summary>
I have a very strange security issue in Spring Boot 3.0.2
When I try to secure a REST service with `@PreAuthorize` and the method contains parameters I get the following error message:
Name for argument of type [int] not specified, and parameter name information not found in class file either.
If I remove the annotation, everything works again.
Here is my source code:
This work, no parameters on method listAll:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll() { // <<--
return currencyController.listAll(0, 100);
}
}
This work too, no PreAuthorize, but with parameters
@RestController
@RequestMapping("/api/currency")
//@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0") int page, // <<--
@RequestParam(defaultValue = "100") int size) { // <<--
return currencyController.listAll(page, size);
}
}
This not work:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0") int page, // <<--
@RequestParam(defaultValue = "100") int size) { // <<--
return currencyController.listAll(page, size);
}
}
I don't know what I can do? Did anyone had an idea?
My SecurityFilterChain:
http.securityMatcher("/api/**")
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(authenticationConverter);
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.securityMatcher("/api/**")
.authorizeHttpRequests()
.requestMatchers("/api/**")
.authenticated();
http.build();
EDIT:
This also doesn't work:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(@RequestParam(defaultValue = "0",
name = "page") int page, // <<--
@RequestParam(defaultValue = "100",
name = "size") int size) { // <<--
return currencyController.listAll(page, size);
}
}
Also javac Parameter -g:vars and / or -parameters not help
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<compilerArgs>
<arg>-g:vars</arg>
<arg>-parameters</arg>
</compilerArgs>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
EDIT 2:
If I use @EnableGlobalMethodSecurity the Application don't start. I get this error message:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.support.BeanDefinitionOverrideException:
Invalid bean definition with name 'metaDataSourceAdvisor' defined in null: Cannot register bean definition
[Root bean: class [org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null]
for bean 'metaDataSourceAdvisor' since there is already
[Root bean: class [org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] bound.
Is done, I already had the annotation.
EDIT 3:
If I don't use @RequestParam but do it this way it works:
@RestController
@RequestMapping("/api/currency")
@PreAuthorize("hasAnyRole('api','frontend')") // <<--
public class CurrencyRestService {
@GetMapping("/")
public Page<Currency> listAll(Map<String, String> reqParam) { // <<--
int page = 0;
int size = 100;
try {
page = Integer.parseInt(reqParam.get("page"));
size = Integer.parseInt(reqParam.get("size"));
} catch (Exception e) {
page = 0;
size = 100;
}
if (size <= 0) {
size = 100;
}
return currencyController.listAll(page, size);
}
}
EDIT 4:
The problem is even stranger. If I now make a POST, then only a completely empty object arrives in the method "add". Of course I get a NotNull Error. If I turn off the security, the object arrives clean and complete.
@PostMapping("/")
@PreAuthorize("hasAnyRole('api-write','frontend')")
public ResponseEntity add(@RequestBody Currency newObject) {
System.err.println(newObject.toString());
return new ResponseEntity(this.controller.add(newObject), HttpStatus.OK);
}
Error message:
ConstraintViolationImpl{interpolatedMessage='darf nicht null sein', propertyPath=updatedBy, rootBeanClass=class de.bewidata.framework.entity.zrentity.Currency, messageTemplate='{jakarta.validation.constraints.NotNull.message}'}
Send Object:
{"objectType":"Currency","objectId":"2ea69820-1f8b-46c8-80c1-7e61e2f983fa","objectVersion":0,"createdAt":"2023-03-02T15:44:37.690+01:00","createdBy":"system","updatedAt":"2023-03-02T15:44:37.690+01:00","updatedBy":"system","deleted":false,"deletedAt":null,"deletedBy":null,"deactivated":false,"deactivatedAt":null,"id":0,"name":"Dollar","iso2":null,"iso3":"USD","symbol":"$","alignment":"RIGHT","exchangeRatio":1.00}
Getted Object by security on:
{"objectType":"Currency","objectId":"2ea69820-1f8b-46c8-80c1-7e61e2f983fa","objectVersion":null,"createdAt":"2023-03-02T15:44:37.690+01:00","createdBy":null,"updatedAt":"2023-03-02T15:44:37.690+01:00","updatedBy":null,"deleted":false,"deletedAt":null,"deletedBy":null,"deactivated":false,"deactivatedAt":null,"id":null,"name":null,"iso2":null,"iso3":null,"symbol":null,"alignment":"RIGHT","exchangeRatio":null}
EDIT 5:
I debug the request and found a difference. I hope that help in any way. If the Rest Request work, without the Annotaion `@PreAuthorize("hasAnyRole('api','frontend')")`, the right abstract class come into the `MethodParameter`, so the parameter names could found. When I add the annotation, the wrong extendet class is in the `MethodParameter` also with final, and the names couldn't found.
package org.springframework.web.method.annotation;
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { // WORK without Security parameter.executable = public org.springframework.data.domain.Page com.example.framework.AbstractRestService.listAll(int,int)
// NOT WORK with Security parameter.executable = public final org.springframework.data.domain.Page com.example.framework.BusinessRoleRestService$$SpringCGLIB$$0.listAll(int,int)
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
}
EDIT 5:
I was able to isolate the problem. But do not know how to solve it. I noticed that this `$$SpringCGLIB$$` proxy class is only created for the rest of the service classes where another method is inserted.
This work without Problems:
@RestController
@RequestMapping("/api/businessrole")
@Tag(name = "businessrole")
@Extension
@PreAuthorize("hasAnyRole('api','frontend')")
public class BusinessRoleRestService extends AbstractRestService<BusinessRoleController, Long, BusinessRole> {
public BusinessRoleRestService(BusinessRoleController businessRoleController) {
super(businessRoleController);
}
}
Here the `$$SpringCGLIB$$` proxy class created, and I get the Error Message.
@RestController
@RequestMapping("/api/businessrole")
@Tag(name = "businessrole")
@Extension
@PreAuthorize("hasAnyRole('api','frontend')")
public class BusinessRoleRestService extends AbstractRestService<BusinessRoleController, Long, BusinessRole> {
public BusinessRoleRestService(BusinessRoleController businessRoleController) {
super(businessRoleController);
}
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Success")
})
@Operation(summary = "",
description = "")
@GetMapping("/types")
public List<String> listAllSubclasses() {
return this.controller.findAllSubclasses();
}
}
</details>
# 答案1
**得分**: 0
我找到了一个解决方案。使用 `ClassUtils.getUserClass(clazz);` 我得到了正确的类,并且可以手动注册方法到 `RequestMappingHandlerMapping`。现在一切都正常工作。我仍然认为这是一个 Bug。但我不知道在哪里。
<details>
<summary>英文:</summary>
I found a solution. With `ClassUtils.getUserClass(clazz);` I get the right class and could register manualy the methods with an `RequestMappingHandlerMapping`. Now all work. I still thing it is a Bug. But I don't know where.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论