在我的Spring Boot应用程序中,我在控制器方法中使用@PreAuthorize
注释来授权它们。表达式使用简单的boolean
- 返回方法,如下所示:
@ResponseStatus(OK)
@PreAuthorize("@auth.authentication.mayReadMe(principal)")
public UserDto readMe() {
...
mayReadMe(...)
方法只返回一个布尔值,但它使用三元逻辑并只将特殊枚举转换为boolean
:
boolean mayReadMe(@Nonnull UserDetails principal);
现在假设我想重写授权组件并让方法返回枚举:
@Nonnull
foo.bar.FooBarEnum mayReadMe(@Nonnull final UserDetails principal);
但是,我遇到以下异常:
java.lang.IllegalArgumentException: Failed to evaluate expression '@primaryAuth.authentication.mayReadMe(principal)'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:15)
at org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice.before(ExpressionBasedPreInvocationAdvice.java:44)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:57)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:25)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:62)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E:(pos 0): Type conversion problem, cannot convert from @javax.annotation.Nonnull foo.bar.FooBarEnum to java.lang.Boolean
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:78)
at org.springframework.expression.common.ExpressionUtils.convertTypedValue(ExpressionUtils.java:53)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:301)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:11)
... 113 common frames omitted
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [@javax.annotation.Nonnull foo.bar.FooBarEnum] to type [java.lang.Boolean]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:313)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:74)
... 116 common frames omitted
异常消息非常清楚,但我不能以任何方式注入我的自定义转换器。到目前为止我尝试过的:
WebMvcConfigurerAdapter.addFormatters(FormatterRegistry)
(Converter
和GenericConverter
)Bean
- 使用自定义ExpressionBasedPreInvocationAdvice
(但据我所知,它不应该有效)如何注入自定义类型转换器,以便@PreAuthorization
表达式可以将foo.bar.FooBarEnum
视为返回类型?
为什么我需要返回自定义类型,而不是布尔值。我还在编写一个简单的REST API自描述子系统,只是一个简单的GET /api
端点来返回端点列表等等。此列表由描述API端点,HTTP方法,传入和传出DTO的特定对象组成,我尝试添加到定义对象的最后一件事是端点授权策略表达式。请注意,返回@PreAuthorize
字符串表达式(我的意思是原始字符串)并不是一个好主意,但返回描述授权规则的自定义对象可能会更好。我最想要的是返回一个像这样的对象:
public final class AuthorizationExpression
implements BooleanSupplier {
...
public IExpression toExpression() {
...
}
我希望在我尝试注入的转换器中使用BooleanSupplier
以满足授权需求 - 只需返回true
或false
,以及IExpression
1}}应该是toString
- 使用Spring表达式求值程序在GET /api
处理程序中编辑。因此mayReadMe
签名可能如下:
AuthorizationExpression mayReadMe(...)
因此我可以将AuthorizationExpression
用于某个用例。 FooBarEnum
只是编辑前原始问题的简化。
答案 0 :(得分:0)
建议,让你的枚举实现转换方法:
public enum FooBarEnum {
// previous code
public boolean booleanValue() {
// TODO
}
}
并更改注释:
@PreAuthorize("@auth.authentication.mayReadMe(principal).booleanValue()")
答案 1 :(得分:0)
想出来。我只需要调整DefaultMethodSecurityExpressionHandler
实例。让我们假设净两个类作为库类:
public abstract class CustomTypesGlobalMethodSecurityConfiguration
extends GlobalMethodSecurityConfiguration {
protected abstract ApplicationContext applicationContext();
protected abstract ConversionService conversionService();
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
final ApplicationContext applicationContext = applicationContext();
final TypeConverter typeConverter = new StandardTypeConverter(conversionService());
final DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() {
@Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication authentication, final MethodInvocation methodInvocation) {
final StandardEvaluationContext decoratedStandardEvaluationContext = super.createEvaluationContextInternal(authentication, methodInvocation);
return new ForwardingStandardEvaluationContext() {
@Override
protected StandardEvaluationContext standardEvaluationContext() {
return decoratedStandardEvaluationContext;
}
@Override
public TypeConverter getTypeConverter() {
return typeConverter;
}
};
}
};
handler.setApplicationContext(applicationContext);
return handler;
}
}
其中ForwardingStandardEvaluationContext
是一个简单的转发装饰器来装饰StandardEvaluationContext
的实例,因为后者是ConversionService
- 意识到:
public abstract class ForwardingStandardEvaluationContext
extends StandardEvaluationContext {
protected abstract StandardEvaluationContext standardEvaluationContext();
@Override public void setRootObject(final Object rootObject, final TypeDescriptor typeDescriptor) { standardEvaluationContext().setRootObject(rootObject, typeDescriptor); }
@Override public void setRootObject(final Object rootObject) { standardEvaluationContext().setRootObject(rootObject); }
@Override public TypedValue getRootObject() { return standardEvaluationContext().getRootObject(); }
@Override public void addConstructorResolver(final ConstructorResolver resolver) { standardEvaluationContext().addConstructorResolver(resolver); }
@Override public boolean removeConstructorResolver(final ConstructorResolver resolver) { return standardEvaluationContext().removeConstructorResolver(resolver); }
@Override public void setConstructorResolvers(final List<ConstructorResolver> constructorResolvers) { standardEvaluationContext().setConstructorResolvers(constructorResolvers); }
@Override public List<ConstructorResolver> getConstructorResolvers() { return standardEvaluationContext().getConstructorResolvers(); }
@Override public void addMethodResolver(final MethodResolver resolver) { standardEvaluationContext().addMethodResolver(resolver); }
@Override public boolean removeMethodResolver(final MethodResolver methodResolver) { return standardEvaluationContext().removeMethodResolver(methodResolver); }
@Override public void setMethodResolvers(final List<MethodResolver> methodResolvers) { standardEvaluationContext().setMethodResolvers(methodResolvers); }
@Override public List<MethodResolver> getMethodResolvers() { return standardEvaluationContext().getMethodResolvers(); }
@Override public void setBeanResolver(final BeanResolver beanResolver) { standardEvaluationContext().setBeanResolver(beanResolver); }
@Override public BeanResolver getBeanResolver() { return standardEvaluationContext().getBeanResolver(); }
@Override public void addPropertyAccessor(final PropertyAccessor accessor) { standardEvaluationContext().addPropertyAccessor(accessor); }
@Override public boolean removePropertyAccessor(final PropertyAccessor accessor) { return standardEvaluationContext().removePropertyAccessor(accessor); }
@Override public void setPropertyAccessors(final List<PropertyAccessor> propertyAccessors) { standardEvaluationContext().setPropertyAccessors(propertyAccessors); }
@Override public List<PropertyAccessor> getPropertyAccessors() { return standardEvaluationContext().getPropertyAccessors(); }
@Override public void setTypeLocator(final TypeLocator typeLocator) { standardEvaluationContext().setTypeLocator(typeLocator); }
@Override public TypeLocator getTypeLocator() { return standardEvaluationContext().getTypeLocator(); }
@Override public void setTypeConverter(final TypeConverter typeConverter) { standardEvaluationContext().setTypeConverter(typeConverter); }
@Override public TypeConverter getTypeConverter() { return standardEvaluationContext().getTypeConverter(); }
@Override public void setTypeComparator(final TypeComparator typeComparator) { standardEvaluationContext().setTypeComparator(typeComparator); }
@Override public TypeComparator getTypeComparator() { return standardEvaluationContext().getTypeComparator(); }
@Override public void setOperatorOverloader(final OperatorOverloader operatorOverloader) { standardEvaluationContext().setOperatorOverloader(operatorOverloader); }
@Override public OperatorOverloader getOperatorOverloader() { return standardEvaluationContext().getOperatorOverloader(); }
@Override public void setVariable(final String name, final Object value) { standardEvaluationContext().setVariable(name, value); }
@Override public void setVariables(final Map<String, Object> variables) { standardEvaluationContext().setVariables(variables); }
@Override public void registerFunction(final String name, final Method method) { standardEvaluationContext().registerFunction(name, method); }
@Override public Object lookupVariable(final String name) { return standardEvaluationContext().lookupVariable(name); }
@Override public void registerMethodFilter(final Class<?> type, final MethodFilter filter) throws IllegalStateException { standardEvaluationContext().registerMethodFilter(type, filter); }
}
然后是几个应用程序类:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = false)
class SecurityConfiguration
extends CustomTypesGlobalMethodSecurityConfiguration {
private final ApplicationContext applicationContext;
private final ConversionService conversionService;
public SecurityConfiguration(
@Autowired final ApplicationContext applicationContext,
@Autowired final ConversionService conversionService
) {
this.applicationContext = applicationContext;
this.conversionService = conversionService;
}
@Override
protected ApplicationContext applicationContext() {
return applicationContext;
}
@Override
protected ConversionService conversionService() {
return conversionService;
}
}
最后转换服务配置:
@Configuration
class ConversionConfiguration {
@Bean
public ConversionService conversionService() {
final DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(FooBar.class, Boolean.class, FooBar::mayProceed);
return conversionService;
}
}
上面的代码使@PreAuthorize
能够理解FooBar
- 返回表达式。