Creating custom ErrorWebExceptionHandler fails - spring-boot

I am trying to create my own ErrorWebExceptionHandler in Spring Boot 2 by extending the default one but my application fails to start with the following message:
Caused by: java.lang.IllegalArgumentException: Property 'messageWriters' is required
at org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler.afterPropertiesSet(AbstractErrorWebExceptionHandler.java:214) ~[spring-boot-autoconfigure-2.0.4.RELEASE.jar:2.0.4.RELEASE]
My handler (Kotlin code):
#Component
#Order(-2)
class SampleErrorWebExceptionHandler(
errorAttributes: ErrorAttributes?,
resourceProperties: ResourceProperties?,
errorProperties: ErrorProperties?,
applicationContext: ApplicationContext?
) : DefaultErrorWebExceptionHandler(errorAttributes, resourceProperties, errorProperties, applicationContext) {
override fun logError(request: ServerRequest, errorStatus: HttpStatus) {
// do something
}
}
What could be the cause?

Please try to add dependency of ServerCodecConfigurer in your constructor
GlobalErrorWebExceptionHandler(
ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext,
ServerCodecConfigurer configurer
) {
super(errorAttributes, resourceProperties, applicationContext);
this.setMessageWriters(configurer.getWriters());
}

You need to set the messageWriters on that instance, because they are required here. You should probably create that as a #Bean, just like Spring Boot is doing in the dedicated auto-configuration.

I just did this as well, and after looking at springs implementation I just added the components to the constructor.
#Component
#Order(-2)
class GlobalErrorWebExceptionHandler(
errorAttributes: ErrorAttributes,
resourceProperties: ResourceProperties,
applicationContext: ApplicationContext,
viewResolvers: ObjectProvider<ViewResolver>,
serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
applicationContext
) {
private val logger = LogFactory.getLog(GlobalErrorWebExceptionHandler::class.java)!!
init {
setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()))
setMessageWriters(serverCodecConfigurer.writers)
setMessageReaders(serverCodecConfigurer.readers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes) = RouterFunctions.route(RequestPredicates.all(), HandlerFunction { request ->
val ex = getError(request)
logger.error(ex.message)
ServerResponse.ok().build()
})
}

If the above solution is not very clear refer below:
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
private final ObjectProvider<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GlobalErrorWebExceptionHandler(ObjectProvider<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer,
ErrorAttributes errorAttributes, WebProperties.Resources resources, ApplicationContext applicationContext) {
super(errorAttributes, resources, applicationContext);
this.viewResolvers = viewResolvers;
this.serverCodecConfigurer = serverCodecConfigurer;
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
super.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest serverRequest) {
Map<String, Object> errorPropertiesMap = getErrorAttributes(serverRequest,
ErrorAttributeOptions.defaults());
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}

Related

How can I create beans from properties at runtime with autowiring capability

I try to create beans at runtime from application.ymal like this
config:
properties:
service1:
propertyService1: valueService1
service2:
propertyService2: valueService2
The class in which the bean was created...
#Configuration
public class DynamicBeanConfiguration implements ApplicationContextAware {
private TestProperties testProperties;
public DynamicBeanConfiguration(TestProperties testProperties) {
this.testProperties = testProperties;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
for (Map.Entry<String, Properties> entry: testProperties.getProperties().entrySet()) {
String beanName = entry.getKey();
ConfigurableListableBeanFactory beanFactory = configurableApplicationContext.getBeanFactory();
TestDynamicBean dynamicBean = (TestDynamicBean) beanFactory.createBean(TestDynamicBean.class, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
for (Map.Entry<Object, Object> properties: entry.getValue().entrySet()) {
dynamicBean.setServiceName(beanName);
dynamicBean.setValue(properties.getValue().toString());
beanFactory.autowireBean(dynamicBean);
beanFactory.registerSingleton(beanName, dynamicBean);
}
}
}
}
The class in which autoconfiguration was created...
#Configuration
#EnableConfigurationProperties(TestProperties.class)
public class TestAutoConfiguration {
#Bean
public DynamicBeanConfiguration dynamicBeanConfiguration(TestProperties properties) {
return new DynamicBeanConfiguration(properties);
}
}
And in this way I successfully get the created beans...
TestDynamicBean service2 = applicationContext.getBean("service2", TestDynamicBean.class);
TestDynamicBean service1 = applicationContext.getBean("service1", TestDynamicBean.class);
But I can't understand how can I use these beans via #Autowired with #Qualifier?
If I create beans this way, no beandefinition is created...
if I created beans through BeanDefinitionRegistryPostProcessor, then it is not possible to read the properties in the map...
Of course I can still return the map this way...
#Bean
public Map<String, TestDynamicBean> dynamicBeanConfiguration(TestProperties properties) {
return new DynamicBeanConfiguration(properties);
But I would like to call services like this...
#Autowired(required = false)
#Qualifier("service1")
private TestDynamicBean service1;
Thanks for any ideas.
Got almost what I wanted...
#Autowired
private DynamicBeanConfiguration dynamicBeanMap;
#Autowired(required = false)
#Qualifier("service1")
private TestDynamicBean service1;
#Autowired(required = false)
#Qualifier("service2")
private TestDynamicBean service2;

Global error handling using Spring boot + WebFlux

How can we handle exceptions globally when using reactive programming in Spring boot rest controller?
I would assume that #ControllerAdvice will not work because I have tried this and it was unsuccessful.
My other try is currently this option, using custom attributes:
#Component
public class OsvcErrorAttributes extends DefaultErrorAttributes {
public OsvcErrorAttributes() {
super(true);
}
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return assembleError(request);
}
private Map<String, Object> assembleError(ServerRequest request) {
ServerException serverException = (ServerException)getError(request);
Map<String, Object> errorAttributes = new HashMap<>();
errorAttributes.put("message", serverException.getMessage());
errorAttributes.put("errors", serverException.getErrorMap());
return errorAttributes;
}
}
and WebExceptionHandler like this:
#Component
#Order(-2)
public class OsvcErrorHandler extends AbstractErrorWebExceptionHandler {
public OsvcErrorHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
// TODO: 25.06.2019 temporary workaround
ServerCodecConfigurer serverCodecConfigurer = new DefaultServerCodecConfigurer();
setMessageWriters(serverCodecConfigurer.getWriters());
setMessageReaders(serverCodecConfigurer.getReaders());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest serverRequest) {
final Map<String, Object> errorAttributes = getErrorAttributes(serverRequest, true);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorAttributes));
}
}
Code that generates an error:
#Data
#Service
public class ContactService {
private final ContactRepository contactRepository;
public Mono<Business> saveNewContact(Business business) {
return contactRepository.save(business)
.onErrorMap(throwable ->
ServerException.create(throwable.getMessage())
.persistError("ico", business.getIco(), "ICO is probably duplicate"));
}
}
Problem is that this does not work either. I did follow this tutorial and I cannot see if I am wrong with something or not.
You just use ServerCodecConfigurer injection in you global error handler constructor like this.
public OsvcErrorHandler(GlobalErrorAttributes errorAttributes, ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, new ResourceProperties(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
Please find the code example in the git repository.
Try injecting the ServerCodecConfigurer instead of instantiating it. I also inject a ViewResolversProvider when doing this, although it might not be necessary.
public OsvcErrorHandler(
final CustomErrorAttributes customAttributes,
final ResourceProperties resourceProperties,
final ObjectProvider<List<ViewResolver>> viewResolversProvider,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext
) {
super(customAttributes, resourceProperties, applicationContext);
this.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
this.setMessageWriters(serverCodecConfigurer.getWriters());
this.setMessageReaders(serverCodecConfigurer.getReaders());
}
You need to define and implement ErrorWebExceptionHandler as a bean and set an #Order annotation with value less than -1, because that is the default of the Spring DefaultErrorWebExceptionHandler
Here is a sample implementation:
public class GlobalErrorHandler extends DefaultErrorWebExceptionHandler {
public GlobalErrorHandler(
final ErrorAttributes errorAttributes,
final WebProperties.Resources resources,
final ErrorProperties errorProperties,
final ApplicationContext applicationContext) {
super(errorAttributes, resources, errorProperties, applicationContext);
}
#Override
public Mono<Void> handle(final ServerWebExchange exchange, final Throwable ex) {
final ServerHttpResponse response = exchange.getResponse();
if (ex instanceof IllegalStateException
&& StringUtils.equals("Session was invalidated", ex.getMessage())
&& response.getStatusCode().is3xxRedirection()) {
final DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
return response.writeWith(Mono.just(bufferFactory.wrap("Redirecting...".getBytes())));
}
return super.handle(exchange, ex);
}
}
And here is a sample configuration based on org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration class:
#Configuration
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
public ErrorWebFluxAutoConfiguration(final ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
#Bean
#Order(-2)
public ErrorWebExceptionHandler errorWebExceptionHandler(
final ErrorAttributes errorAttributes,
final org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
final WebProperties webProperties,
final ObjectProvider<ViewResolver> viewResolvers,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext) {
final GlobalErrorHandler exceptionHandler =
new GlobalErrorHandler(
errorAttributes,
resourceProperties.hasBeenCustomized()
? resourceProperties
: webProperties.getResources(),
serverProperties.getError(),
applicationContext);
exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return exceptionHandler;
}
#Bean
#ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
}
Thanks to this article which points me to use ErrorWebExceptionHandler.

Spring Boot - Auto wiring service having String constructor

How do i #autowire bean class TransactionManagerImpl which is having 1(String) argument constructor without using new in spring-boot application?
Even after searching through many post i couldn't get any clue to autowire without using new
I need to autowire TransactionManager in three different classes and the parameters are different in all three classes.
This looks like very basic scenario.
#Service
public class TransactionManagerImpl implements TransactionManager {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
String txnLogFile;
#ConstructorProperties({"txnLogFile"})
public TransactionManagerImpl(String txnLogFile) {
this.txnLogFile= txnLogFile;
}
}
is there any specific requirement where you want to use #Service annotation?
if not then you can use #Bean to create a bean for TransactionManagerImpl like below.
#Configuration
public class Config {
#Value("${txnLogFile}")
private String txnLogFile;
#Bean
public TransactionManager transactionManager() {
return new TransactionManagerImpl(txnLogFile);
}
}
and remove #Service annotation from TransactionManagerImpl.
Putting aside other complications, it can be done like this
public TransactionManagerImpl(#Value("${txnLogFile}") String txnLogFile) {
this.txnLogFile= txnLogFile;
}
Finally, i did it as below, now sure if this is the best way to do. I did not want to have three implementation just because of one variable.
application.yaml
app:
type-a:
txn-log-file: data/type-a-txn-info.csv
type-b:
txn-log-file: data/type-b-txn-info.csv
default:
txn-log-file: data/default/txn-info.csv
MainApplication.java
#SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(MainApplication.class).web(WebApplicationType.NONE).run(args);
}
#Bean
public TransactionManager transactionManager(#Value("${app.default.txn-log-file}") String txnLogFile) {
return new TransactionManagerImpl(txnLogFile);
}
#Bean
public CsvService csvService(String txnLogFile) {
return new CsvServiceImpl(txnLogFile);
}
}
TypeOneRoute.java
#Configuration
public class TypeOneRoute extends RouteBuilder {
#Value("${app.type-a.txn-log-file}")
private String txnLogFile;
#Autowired
private ApplicationContext applicationContext;
private TransactionManager transactionManager;
#Override
public void configure() throws Exception {
transactionManager = applicationContext.getBean(TransactionManager.class, txnLogFile);
transactionManager.someOperation();
}
}
TypeTwoRoute.java
#Configuration
public class TypeTwoRoute extends RouteBuilder {
#Value("${app.type-b.txn-log-file}")
private String txnLogFile;
#Autowired
private ApplicationContext applicationContext;
private TransactionManager transactionManager;
#Override
public void configure() throws Exception {
transactionManager = applicationContext.getBean(TransactionManager.class, txnLogFile);
transactionManager.create();
}
}
TransactionManager.java
#Service
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public interface TransactionManager {
public ZonedDateTime create() throws IOException, ParseException;
}
TransactionManagerImpl.java
public class TransactionManagerImpl implements TransactionManager {
#Autowired
private ApplicationContext applicationContext;
private String txnLogFile;
public TransactionManagerImpl(String txnLogFile) {
this.txnLogFile = txnLogFile;
}
private CsvService csvService;
#PostConstruct
public void init() {
csvService = applicationContext.getBean(CsvService.class, txnLogFile);
}
public ZonedDateTime create() throws IOException, ParseException {
try {
csvService.createTxnInfoFile();
return csvService.getLastSuccessfulTxnTimestamp();
} catch (IOException e) {
throw new IOException("Exception occured in getTxnStartDate()", e);
}
}
}
Initially TransactionManager Bean will be registered with the app.default.txn-info.csv and when i actually get it from ApplicationContext i am replacing the value with the parameter passed to get the bean from ApplicationContext

unable to use #AspectJ with Spring-Apache CXF services

I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}

Getting No bean resolver registered

After upgrading today from Spring boot 1.2.5 to 1.3.0 BUILD-SNAPSHOT Calling
#PreAuthorize fails:
example:
#PreAuthorize("#defaultSecurityService.canDoSomething(authentication.principal.id, #objId)")
Result doSomething(#P("objId")String objId);
where defaultSecurityService is defined as:
#Service
public class DefaultSecurityService implements SecurityService {
...
public boolean canDoSomething(String userId, String objId){
return true; //
}
}
Stack trace
Caused by: java.lang.IllegalArgumentException: Failed to evaluate expression '#oauth2.throwOnError(defaultSecurityService.canDoSomething(authentication.principal.id, #objId))'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:14)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 8): No bean resolver registered in the context to resolve access to bean 'defaultSecurityService'
what i've tried:
make SecurityService extend [PermissionEvaluator][1] and register a bean
atApplication.java`
#Bean
#Lazy
public PermissionEvaluator permissionEvaluator(){
return securityService;
}`
But i'm still getting the same error
Reading the spring security 4.0.2 documentation didn't reveal any relevant material about breaking changes
This appears to be a bug in the newly added OAuth2AutoConfiguration. Specifically it brings in OAuth2MethodSecurityConfiguration which overrides the DefaultMethodSecurityExpressionHandler with a OAuth2MethodSecurityExpressionHandler that does not have a BeanResolver set.
If you are not using OAuth2, then the easiest solution is to remove Spring Security OAuth from your classpath.
Alternatively, you can exclude the OAuth2AutoConfiguration using the following if you use #SpringBootApplication:
#SpringBootApplication(exclude=OAuth2AutoConfiguration.class)
alternatively you can use the following if you leverage #AutoConfiguration directly:
#AutoConfiguration(exclude=OAuth2AutoConfiguration.class)
UPDATE
You can also use something like this:
public class DelegatingMethodSecurityExpressionHandler implements
MethodSecurityExpressionHandler {
private final MethodSecurityExpressionHandler delegate;
public DelegatingMethodSecurityExpressionHandler(
MethodSecurityExpressionHandler delegate) {
super();
this.delegate = delegate;
}
public Object filter(Object filterTarget, Expression filterExpression,
EvaluationContext ctx) {
return delegate.filter(filterTarget, filterExpression, ctx);
}
public ExpressionParser getExpressionParser() {
return delegate.getExpressionParser();
}
public EvaluationContext createEvaluationContext(
Authentication authentication, MethodInvocation invocation) {
return delegate.createEvaluationContext(authentication, invocation);
}
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
delegate.setReturnObject(returnObject, ctx);
}
}
Then in your configuration use:
#Autowired(required = false)
List<AuthenticationTrustResolver> trustResolvers = new ArrayList<>();
#Autowired(required = false)
List<PermissionEvaluator> permissionEvaluators = new ArrayList<>();
#Bean
public MethodSecurityExpressionHandler securityExpressionHandler(ApplicationContext context) {
OAuth2MethodSecurityExpressionHandler delegate = new OAuth2MethodSecurityExpressionHandler();
delegate.setApplicationContext(context);
if(trustResolvers.size() == 1) {
delegate.setTrustResolver(trustResolvers.get(0));
}
if(permissionEvaluators.size() == 1) {
delegate.setPermissionEvaluator(permissionEvaluators.get(0));
}
return new DelegatingMethodSecurityExpressionHandler(delegate);
}
We have to wrap it in the DelegatingMethodSecurityExpressionHandler because Spring Boot's auto config will replace any subclass of DefaultMethodSecurityExpressionHandler with the broken configuration.
I had the same problem than you, my bean in charge of managing security on a REST controller wasn't found:
org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 8): No bean resolver registered in the context to resolve access to bean 'communitySecurityAuthorizer
Rob's reply pointed me in the right direction (I thought I was doing it wrong, not that it was a bug in the standard Spring OAuth2).
I don't use springboot as I'm making a webapp and I found the answer that solved my problem here:
https://github.com/spring-projects/spring-security-oauth/issues/730#issuecomment-219480394
The problem comes in fact from the bean resolver which is null so here is the solution (retranscription of the link above):
Add a #Bean with OAuth2WebSecurityExpressionHandler that explicitly
sets the application context
#Bean
public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
In the ResourceServerConfigurerAdapter, configure the resources and
pass in the Bean above.
#Autowired
private OAuth2WebSecurityExpressionHandler expressionHandler;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(expressionHandler);
}
Hope this'll others !
As Almiriad has said, generate the OAuth2MethodSecurityExpressionHandler instance as a bean.
Instead do that:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
....
}
do this:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return getOAuth2MethodSecurityExpressionHandler();
}
#Bean
public OAuth2MethodSecurityExpressionHandler getOAuth2MethodSecurityExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
....
}
Hope this'll others !

Resources