Search code examples
javaspring-bootspring-el

Spring Boot 2.x.x - @ConditionalOnExpression: Expression parsing failed


I'm using Spring Boot 2.2.5.RELEASE. In my application.properties file I have the following properties:

security.basic.authorize-mode=authenticated
security.basic.enabled=true

Since they're no longer customizable because of simplification of Spring Boot default security configuration,

enter image description here

I can't use it via application.properties.

I've researched and tried solution using annotation @ConditionalOnExpression for two properties like:

@ConditionalOnExpression("${security.basic.enabled:true} && ${security.basic.authorize-mode:authenticated}")

And by the same logic was this one. But it doesn't work for me, because I have authenticated instead of values true/false:

enter image description here

So, I've tried to write like:

@ConditionalOnExpression("${security.basic.enabled:true} && ${security.basic.authorize.mode}.equals('authenticated')")

it compiles good, but after running the program, I'm getting the error:

Caused by: org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
    at org.springframework.context.expression.StandardBeanExpressionResolver.evaluate(StandardBeanExpressionResolver.java:164) ~[spring-context-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnExpressionCondition.evaluateExpression(OnExpressionCondition.java:60) ~[spring-boot-autoconfigure-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.autoconfigure.condition.OnExpressionCondition.getMatchOutcome(OnExpressionCondition.java:48) ~[spring-boot-autoconfigure-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) ~[spring-boot-autoconfigure-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    ... 26 common frames omitted
Caused by: org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
    at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:135) ~[spring-expression-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:61) ~[spring-expression-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:33) ~[spring-expression-5.2.4.RELEASE.jar:5.2.4.RELEASE]

Can I use @ConditionalOnExpression or @ConditionalOnProperty for multiple properties, but not only for boolean true/false values?

UPD: I've added single quotes:

@ConditionalOnExpression("${security.basic.authorize.mode:'authenticated'} && ${security.basic.enabled:true}")

enter image description here

In this case I'm getting:

Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Boolean] for value 'authenticated'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value 'authenticated'
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:70) ~[spring-expression-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    ... 36 common frames omitted
Caused by: java.lang.IllegalArgumentException: Invalid boolean value 'authenticated'
    at org.springframework.core.convert.support.StringToBooleanConverter.convert(StringToBooleanConverter.java:63) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.core.convert.support.StringToBooleanConverter.convert(StringToBooleanConverter.java:31) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:385) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    ... 38 common frames omitted

And also I tried using environment.getProperty() for @ConditionalOnExpression annotation:

@ConditionalOnExpression("#{environment.getProperty('security.basic.authorize.mode') eq('authenticated') && environment.getProperty('security.basic.enabled') eq('true')}")

Solution

  • As mentioned by @M. Deinum, I can use WebSecurityConfigurerAdapter with rules for authenticated access:

    public class SpringSecurityConfiguration
            extends WebSecurityConfigurerAdapter {
        private final UserInfoDetailsService userInfoDetailsService;
    
        public SpringSecurityConfiguration_Database(UserInfoDetailsService userInfoDetailsService) {
            this.userInfoDetailsService = userInfoDetailsService;
        }
    
        @Override
        protected void configure(
                AuthenticationManagerBuilder authenticationManagerBuilder)
                throws Exception {
            authenticationManagerBuilder
                    .userDetailsService(userInfoDetailsService);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/api/user/**", "/h2-console")
                    .authenticated()
                    .and()
                    .httpBasic()
                    .realmName("...")
                    .and()
                    .csrf()
                    .disable();
        }
    }