1
votes

I migrated my spring boot rest service to use spring boot 2.0.2 and facing problems with actuator endpoints. I know spring boot removed the auto config security to the actuator framework in spring 2.0.2. So I think i had to specify the security in order to access those endpoints but i already had a security configured for my web service to use JWT. please suggest me how i can enable actuator endpoint in spring boot 2.0.2 and also have security configured for actuator enpoints with out impacting Jwt

pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.2.RELEASE</version>
</parent>
<properties>
    <jacoco.version>0.7.8</jacoco.version>
    <java.version>1.8</java.version>
    <mockito.version>2.7.22</mockito.version>
    <mybatis.version>3.4.4</mybatis.version>
    <mybatis.spring.version>1.3.1</mybatis.spring.version>
</properties>
<build>
    <finalName>${project.artifactId}</finalName>
    <resources>
        <resource>
            <filtering>true</filtering>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <!-- Uncomment this plugin after you have initialized the git repo. -->
        <!-- 
        <plugin>
            <groupId>pl.project13.maven</groupId>
            <artifactId>git-commit-id-plugin</artifactId>
        </plugin>
        -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>${jacoco.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                        <goal>check</goal>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <rules>
                    <rule>
                        <element>CLASS</element>

                        <limits>
                            <limit>
                                <counter>LINE</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>1.00</minimum>
                            </limit>
                            <limit>
                                <counter>BRANCH</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>1.00</minimum>
                            </limit>
                        </limits>
                    </rule>
                </rules>
            </configuration>
        </plugin>
    </plugins>
</build>
<reporting>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>${jacoco.version}</version>
            <reportSets>
                <reportSet>
                    <reports>
                        <report>report</report>
                    </reports>
                </reportSet>
            </reportSets>
        </plugin>
    </plugins>
</reporting>
<dependencies>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
        <groupId>com.ibm.db2.jcc</groupId>
        <artifactId>db2jcc_license_cisuz</artifactId>
        <version>DB2V11</version>
    </dependency>
    <dependency>
        <groupId>com.ibm.db2.jcc</groupId>
        <artifactId>db2jcc4</artifactId>
        <version>4.19.26</version>
    </dependency>
    <dependency>
        <groupId>com.jayway.jsonpath</groupId>
        <artifactId>json-path</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.skyscreamer</groupId>
        <artifactId>jsonassert</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-library</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>${mockito.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>${mybatis.spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-properties-migrator</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Integration test yaml

mybatis:
  configuration-properties:
               schema: abcd

spring:
  datasource:
     url: 
     username: 
     password: 

management:
  endpoints:
    web:
     exposure:
       include: "*"

The security config is below

 @Configuration
 @EnableResourceServer
 public class SecurityConfiguration implements 
      JwtAccessTokenConverterConfigurer {

@Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws 
  Exception {


 auth.inMemoryAuthentication().withUser("management").password("cfh5r64r 
  bvc54r").roles("ACTUATOR");
}


@Bean
public FilterRegistrationBean corsFilter() {
    // Set CORS configuration to allow cross-origin requests by default.
    // Addtionally add the HTTP OPTIONS method for pre-flight requests.
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.applyPermitDefaultValues();
    corsConfiguration.setAllowCredentials(true);
    corsConfiguration.addAllowedMethod(HttpMethod.GET);
    corsConfiguration.addAllowedMethod(HttpMethod.POST);
    corsConfiguration.addAllowedMethod(HttpMethod.PUT);
    corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);

    UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
    urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);

    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(
            new CorsFilter(urlBasedCorsConfigurationSource));
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

    return filterRegistrationBean;
}


@Override
public void configure(JwtAccessTokenConverter converter) {
    converter.setAccessTokenConverter(new DefaultAccessTokenConverter() {


        @Override
        public OAuth2Authentication extractAuthentication(Map<String, ?> 
           map) {
            Object i = map.get();
            Object e = map.get();

            if (issuerClaim == null || !issuer.equals(issuerClaim) || expirationClaim == null) {
                throw new InvalidTokenException("");
            }

            return super.extractAuthentication(map);
        }
    });
}

}

I'm trying to write integration tests as below

@RunWith(SpringRunner.class)
 @SpringBootTest(webEnvironment = WebEnvironment.MOCK)
 @AutoConfigureMockMvc
@ActiveProfiles("it")
@DirtiesContext
 public class SecurityConfigurationIT {

@Test
@WithMockUser(roles = VALID_ACTUATOR_ROLE)
public void should_be_authorized_for_actuator() throws Exception {
    mockMvc.perform(get(LOGGERS).header(HttpHeaders.ORIGIN, 
     ORIGIN)).andExpect(status().isOk())                 
       .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, 
      ORIGIN));
}

@Test
@WithMockUser(roles = INVALID_ACTUATOR_ROLE)
public void should_fail_as_forbidden_for_actuator() throws Exception {
    mockMvc.perform(get(LOGGERS).header(HttpHeaders.ORIGIN, 
   ORIGIN)).andExpect(status().isForbidden())

   .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, 
    ORIGIN));
}

}

When I'm trying to run it getting 401 but this is working in spring boot 1.5.x

MockHttpServletRequest:
  HTTP Method = GET
  Request URI = /actuator/info
   Parameters = {}
      Headers = {Origin=[test.com]}
         Body = null
Session Attrs = {}

Handler: Type = null

Async:
Async started = false
 Async result = null

 Resolved Exception:
         Type = null

     ModelAndView:
    View name = null
         View = null
        Model = null

  FlashMap:
   Attributes = null

  MockHttpServletResponse:
       Status = 401
Error message = null
      Headers = {Vary=[Origin, Access-Control-Request-Method, Access- 
        Control-Request-Headers], Access-Control-Allow-Origin=[test.com], 
     Access-Control-Allow-Credentials=[true], Cache-Control=[no-store], 
   Pragma=[no-cache], WWW-Authenticate=[Bearer realm=", 
 error="unauthorized", error_description="Full 
       authentication is required to access this resource"], Content-Type= 
   [application/json;charset=UTF-8], X-Content-Type-Options=[nosniff], X- 
      XSS-Protection=[1; mode=block], X-Frame-Options=[DENY]}
 Content type = application/json;charset=UTF-8
         Body = {"error":"**unauthorized","error_description":"Full 
     authentication is required to access this resource"**}
Forwarded URL = null

Redirected URL = null

1
Which test is failing? What is the value for LOGGERS? It seems strange that the error states the URI is /actuators/info but the variable is LOGGERS. Do your tests align with the exception output? - Rob Winch
Can you please show your http security config class? mainly http override method, what rules you have defined? - kj007
@Rob thanks for looking in to it. Both the test fails. Sorry I’m trying different endpoints and it fails for all of them. It gives 401 status for all of them. - Munny
@kj007 I don’t have http security config class. Is it legal to have http security config class even if I have two securityConfiguration classes one with extending websecurityAdapter and one with implementing jwtAccessTokenConverterConfiguration? - Munny

1 Answers

0
votes

If you have http security in your pom, you have to override configure otheriwse default it will required authentication and will throw 401 for all end points.

So you should have below config and handle according to your end points and roles, you might have to also call jwtAccessTokenConverterConfiguration for end points which required to check Authentication. (I don't think, this jwtAccessTokenConverterConfiguration is even called, did you debug?)

Below is sample which will pass all your end points from authentication, modify as per your need.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll();
    }

}