My SpringBoot app has Hystrix enabled with fallback defined for some of the Feign clients and undefined for the rest them.
Now, I wanted to disable Hystrix for the ones that did not have a fallback defined as yet. So I followed the steps listed in [paragraph 7.4] https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html which is to create a separate Feign configuration with a vanilla Feign.Builder. However adding the new #Bean Feign.Builder disables my Hystrix functionality across all Feign clients which I don't want. If I remove the #Bean Feign.Builder, Hystrix fallback kicks in like usual in myhystrixclient. A similar SO question here How to disable hystrix in one of multiple feign clients is still open. What am I doing wrong?
public class MyFeignClientConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
My Feign Client looks like below:
#FeignClient(name = "myregularclient", configuration = MyFeignClientConfiguration.class)
public interface MyRegularClient {
//my APIs here
}
My Hystrix Feign Configuration looks like the below:
public class MyFeignClientHystrixConfiguration {
#Bean
public FeignErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
}
And here is my Feign client where Hystrix fallback is implemented
#FeignClient(name = "myhystrixclient", configuration = MyFeignClientHystrixConfiguration.class, fallback = MyFallbackService.class)
public interface MyHystrixClient {
//my APIs here
}
UPDATE
Adding my Application.java for further review of the component scan aspects.
#ComponentScan(basePackages ="com.demo.xyz")
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
MetricFilterAutoConfiguration.class,
MetricRepositoryAutoConfiguration.class})
#EnableDiscoveryClient
#EnableFeignClients
#EnableCircuitBreaker
public class MyApplication {
/** Start the app **/
}
I managed to reproduce problem on Spring Cloud vDalston.SR5 and it seems I found a solution. It doesn't brake other feign clients which use hystrix. Tested it manually and with integration tests.
Create feign client without hystrix. Notice that configuration class is annotated with #Configuration annotation.
#FeignClient(name = "withoutHystrix",
url = "conrad.fake",
fallback = FeignClientWithoutHystrix.FallbackThatShouldNotOccur.class,
configuration = FeignClientWithoutHystrixConfig.class)
public interface FeignClientWithoutHystrix {
#RequestMapping(method = RequestMethod.GET, value = "/fake/url")
String getFromFakeUrl();
#Component
class FallbackThatShouldNotOccur implements FeignClientWithoutHystrix {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public String getFromFakeUrl() {
log.error("This fallback shouldn't occur");
return "Fallback";
}
}
}
#Configuration
public class FeignClientWithoutHystrixConfig {
#Bean
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
Exclude feign client configuration from #ComponentScan with excludeFilters:
#EnableFeignClients
#SpringBootApplication
#ComponentScan(basePackages = "konrad",
excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = FeignClientWithoutHystrixConfig.class)})
public class CloudClient {
public static void main(String[] args) {
SpringApplication.run(CloudClient.class, args);
}
}
Enable hystrix
feign:
hystrix:
enabled: true
If you want to check anything or run tests, this is my repository https://github.com/MrJavaNoHome/spring-cloud-client
please try this configuration:
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientConfiguration {
#Bean
#Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
And:
import feign.hystrix.HystrixFeign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
#Configuration
public class MyFeignClientHystrixConfiguration {
#Bean
#Scope("prototype")
public HystrixFeign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
feign-hystrix was a part of spring-cloud-starter-openfeign with version less than 3.0.0.
Starting from 3.0.0 , feign-hystrix no longer included
Related
I have a main app that exposes endpoint like this. I have a test like this. It simply uses TestRestTemplate, calls actuator endpoint and checks health:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#TestPropertySource(locations = "classpath:application-test.properties")
public class HealthDataImporterApplicationTest {
#Autowired
private TestRestTemplate testRestTemplate;
#LocalServerPort
private int serverPort;
#Test
public void health() {
assertThat("status", read(get("health"), "$.status").equals("UP"));
}
private String get(final String resource) {
return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource),
String.class);
}
}
I now added SecurityConfig class to add authentication to the app. Here is SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private static final String ACTUATOR_URL = "/actuator/**";
private static final String SCOPE_PREFIX = "SCOPE_";
private static final String PERMISSION_SCOPE = "scope:scope";
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, ACTUATOR_URL).permitAll()
.anyRequest().hasAuthority(SCOPE_PREFIX + PERMISSION_SCOPE)
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
}
Main app imports this SecurityConfig class so authentication is there. However, it fails the existing test because it complains it is missing auth0.audience and other value. It seems like it is trying to actually call auth0 url, which I don't want the test to. Otherwise, I have to put actual auth0 audience and domain which is sensitive data.
How can I disable spring security with TestRestTemplate? Is it worth to test this class at all?
As you are using spring-boot, do not override the JwtDecoder just to check audiences. Use spring.security.oauth2.resourceserver.jwt.audiences property instead (this is a comma separated list of acceptable audience).
Rather than deactivating security, I prefer to use mocked identities and include access-control in test coverage, using MockMvc (yes, even in #SpringBootTest with many components wired).
Refer to this other answer for details on how to do that in unit-test and integration-test.
Because I find request post-processors not that readable and I happen to unit-test secured components which are not controllers (like #Service or #Respository with method-security like #PreAuthorize, #PostFilter, etc.), I created a lib with test annotations for OAuth2:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.get("/actuator/health")
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_openid", "SCOPE_scope:scope")
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isForbidden());
}
}
As comparison, the same sample with just spring-security-test:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.perform(get("/actuator/health/liveness"))
.andExpect(status().isOk());
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"),
new SimpleGrantedAuthority("SCOPE_scope:scope"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
In JHipster, we generate a TestSecurityConfiguration class that mocks the JwtDecoder bean so no identity provider is needed. You can see the code here. I've copied it below for your convenience.
package com.auth0.flickr2.config;
import static org.mockito.Mockito.mock;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
/**
* This class allows you to run unit and integration tests without an IdP.
*/
#TestConfiguration
#Import(OAuth2Configuration.class)
public class TestSecurityConfiguration {
#Bean
ClientRegistration clientRegistration() {
return clientRegistrationBuilder().build();
}
#Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration.Builder clientRegistrationBuilder() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("end_session_endpoint", "https://jhipster.org/logout");
return ClientRegistration
.withRegistrationId("oidc")
.issuerUri("{baseUrl}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("read:user")
.authorizationUri("https://jhipster.org/login/oauth/authorize")
.tokenUri("https://jhipster.org/login/oauth/access_token")
.jwkSetUri("https://jhipster.org/oauth/jwk")
.userInfoUri("https://api.jhipster.org/user")
.providerConfigurationMetadata(metadata)
.userNameAttributeName("id")
.clientName("Client Name")
.clientId("client-id")
.clientSecret("client-secret");
}
#Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
#Bean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
}
This class is then referenced in the test and overrides the SecurityConfig.
#SpringBootTest(classes = { Flickr2App.class, TestSecurityConfiguration.class })
The full project repo is at https://github.com/oktadev/auth0-full-stack-java-example.
I am trying to set up a Spring MVC app but every time I call the http://localhost:9001/tasks API from postman I get the following error:
Here is my code:
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class TaskManagerApplication {
public static void main(String[] args) {
SpringApplication.run(TaskManagerApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:4200");
}
};
}
}
TaskRepository:
#Path("tasks")
#ApiIgnore
#Component
#AllArgsConstructor
public class TaskResource {
private final TaskService taskService;
#GET
#Produces(APPLICATION_JSON)
public List<Task> getAllTasks() {
return taskService.getTasks();
}
TaskService:
#Service
#RequiredArgsConstructor
public class TaskService {
private final TaskRepository taskRepository;
public List<Task> getTasks() {
return taskRepository.findAll();
}
Project Structure:
You are using JAX-RS in spring boot. Spring handles rest in its own way, if you want to use JAX-RS instead of Springs Rest Annotations, you need to do some extra configurations.
First, you need to add a JAX-RS dependency in your build.gradle or pom.xml file. I guess you have already done that. Jersey is one of the JAX-RS implementation, if you want to add this, you need to do the following.
build.gradle
implementation "org.springframework.boot:spring-boot-starter-jersey"
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
After that, you need to register the JAX-RS endpoints with Spring. I guess you missed this step.
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.context.annotation.Configuration;
#Configuration
public class JaxrsConfig extends ResourceConfig {
public JaxrsConfig() {
register(TaskResource.class);
}
}
After this, your JAX-RS endpoints will be registered with spring.
But I will suggest you to follow spring annotations if you are using spring. If you use spring annotations your code will look like this.
#RestController
#RequestMapping(path = "tasks")
public class TaskResource {
#GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<String> getAllTasks() {
return Arrays.asList("a","b");
}
}
Also you will need to remove JAX-RS from spring to use this Spring MVC annoatations to work.
I am using Spring Boot, and I would like to use AspectJ with it.
The following works (of course):
#Aspect
#Component
public class RequestMappingAspect {
#Before("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void advice(JoinPoint joinPoint) {
...
}
}
However, if #Component is removed and #EnableAspectJAutoProxy is added, the following does not work.
#SpringBootApplication
#EnableSwagger2
#EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
How to enable AspectJ auto proxy correctly?
Wondering about the same thing, we ended up doing something similar to this:
#EnableAspectJAutoProxy(proxyTargetClass = true)
#Configuration("Main applicationContext")
#ComponentScan(
basePackages = {"com.where.ever"},
excludeFilters = {#ComponentScan.Filter(Aspect.class)})
public class ApplicationConfiguration {
#Bean(autowire = Autowire.BY_TYPE)
public SomeAspect someAspect() {
return Aspects.aspectOf(SomeAspect.class);
}
...
...
}
This enabled us to just add the #Aspect-annotation on the aspects, which also wired them correctly.
Might be that this was a pointless reply, however, it explains how we solved the issue - and not the actual solution to the problem. Let me know if you want this to be deleted.
You need both #EnableAspectJAutoProxy for the spring configuration and combination of #Aspect / #Component annotations
#EnableAspectJAutoProxy does the same thing as xml based <aop:aspectj-autoproxy>
I have a application that uses Spring cloud config (--spring.profiles.active=native) and also serves up some html pages within the same application. All is fine until I introduce static resources (src/main/resources/css/bootstrap-switch.css). The URL calls to http://localhost:8080/css/bootstrap-switch.css fails with this Exception:
{"timestamp":1438114326940,"status":406,"error":"Not Acceptable","exception":"org.springframework.web.HttpMediaTypeNotAcceptableException","message":"Could not find acceptable representation","path":"/css/bootstrap-switch.css"}
When I disable the #EnableConfigServer, the URL returns the CSS content. I am on Spring Cloud Config version 1.0.2.
Here's my minimalist code that can reproduce this issue:
#SpringBootApplication
#EnableConfigServer
public class Application {
public static void main(String args[]) {
SpringApplication.run(ApplicationConfiguration.class, args);
}
}
#Configuration
#SpringBootApplication
class ApplicationConfiguration {
#Bean
public TestController testController() {
return new TestController();
}
#Bean
public MvcController mvcController() {
return new MvcController();
}
}
#RestController
class TestController {
#RequestMapping("/test")
#ResponseBody
public String test() {
return "hello world";
}
}
#Controller
class MvcController {
#RequestMapping("/landing")
public String landingPage() {
return "landing";
}
}
Config server by default has an api that matches /*/*. You can move the root of the api by changing spring.cloud.config.server.prefix=myroot.
I am attempting to test my #Service and #Repository classes in my project with spring-boot-starter-test and #Autowired is not working for the classes I'm testing.
Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = HelloWorldConfiguration.class
//#SpringApplicationConfiguration(classes = HelloWorldRs.class)
//#ComponentScan(basePackages = {"com.me.sbworkshop", "com.me.sbworkshop.service"})
//#ConfigurationProperties("helloworld")
//#EnableAutoConfiguration
//#ActiveProfiles("test")
// THIS CLASS IS IN src/test/java/ AND BUILDS INTO target/test-classes
public class HelloWorldTest {
#Autowired
HelloWorldMessageService helloWorldMessageService;
public static final String EXPECTED = "je pense donc je suis-TESTING123";
#Test
public void testGetMessage() {
String result = helloWorldMessageService.getMessage();
Assert.assertEquals(EXPECTED, result);
}
}
Service:
#Service
#ConfigurationProperties("helloworld")
// THIS CLASS IS IN /src/main/java AND BUILDS INTO target/classes
public class HelloWorldMessageService {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message=message;
}
}
The commented class annotations on the unit test represent the various things I've tried to get this working. The test and the project packages are in the same package paths and the #ComponentScan works fine from my entry point (#RestController class with main method). The service #ComponentScan's and #Autowire's fine in my #RestController class in the src/main/java side, but does not in the test. I am required to add it again as a #Bean in my #Configuration class in order for #Autowired to work. The class is otherwise in scope just fine and I can reference and instantiate it just fine from the test. The problem appears to be that #ComponentScan does not appear to correctly traverse multiple entries in my test runner classpath, in this case /target/test-classes and /target/classes.
The IDE I am using is IntelliJ IDEA 13.
UPDATE - here are HelloWorldRs and its config:
#RestController
#EnableAutoConfiguration
#ComponentScan
public class HelloWorldRs {
// SPRING BOOT ENTRY POINT - main() method
public static void main(String[] args) {
SpringApplication.run(HelloWorldRs.class);
}
#Autowired
HelloWorldMessageService helloWorldMessageService;
#RequestMapping("/helloWorld")
public String helloWorld() {
return helloWorldMessageService.getMessage();
}
}
...
#Configuration
public class HelloWorldConfiguration {
#Bean
public Map<String, String> map() {
return new HashMap<>();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldMessageService helloWorldMessageService() {
return new HelloWorldMessageService();
}
// This bean was manually added as a workaround to the #ComponentScan problem
#Bean
public HelloWorldRs helloWorldRs() {
return new HelloWorldRs();
}
}
First, I'd recommend to use a newer #RunWith(SpringRunner.class) but that makes no difference, it is just shorter (and recommended).
Second, from the #EnableAutoConfiguration I see that you are using spring boot - which is certainly a good thing. There are some good reasons why not to use #ComponentScan directly. Can you try the following?
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes=YourApplication_or_other_Configuration.class)
public class HelloWorldTest {
... etc.
I don't know if this will turn out to be the solution, but don't use the default package (i.e. don't put *.java in "src/main/java" directly), and definitely don't use a #ComponentScan or #EnableAutoConfiguration in the default package. You will end up killing your application on startup as it tries to scan everything on the classpath (including all the Spring libraries).
SpringBoot 2.7.3, JUnit 5.8.2
If you want to have full control about the spring's configuration (and not rely on the hidden magic of auto configuration) I suggest to create an explicit configuration class:
#ComponentScan(basePackages = { "my.package.to.scan" })
public class MySpringTestConfig
{
// just for spring configuration annotations
}
and reference it in your test class:
#ContextConfiguration(classes = { MySpringTestConfig.class })
#ExtendWith({ SpringExtension.class })
class MySpringTest
{
...
}