Trying to use Feign Client with Eureka and Ribbon-
Have a service registered using Eureka with name-test-service.
Was able to use Ribbon and Eureka successfully as follows-
#Autowired
private LoadBalancerClient loadBalancer;
public void getEmployee() throws RestClientException, IOException {
ServiceInstance serviceInstance=loadBalancer.choose("test-service");
String baseUrl=serviceInstance.getUri().toString();
baseUrl=baseUrl+"/test";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response=null;
try{
response=restTemplate.exchange(baseUrl,
HttpMethod.GET, getHeaders(),String.class);
}
This works correctly including load balancing.
Now tried to replace RestTemplate with Feign as follows-
#FeignClient(name="test-service")
public interface RemoteCallService {
#RequestMapping(method=RequestMethod.GET, value="/test")
public String resp();
}
And making the call with feign as follows-
#Autowired
private RemoteCallService loadBalancer;
public void getEmployee() throws RestClientException, IOException {
String data=loadBalancer.resp();
}
But this is not working. The Feign URL is not getting formed correctly.
On debugging the FeignClient has the following values-
HardCodedTarget(type=RemoteCallService, name=test-service, url=http://test-service)
Can anyone tell what am i missing.
Thanks
Related
I'm newbie to Spring Cloud. Following the example of Start Guide. I'm stuck at Zuul example.
With a request to zuul, my client app not receive any request from zuul and get 405(Method not allowed) to my browser.
But test without zuul, the client app can response success.
Simple code as this:
Zuul Server:
#EnableZuulProxy
#SpringBootApplication
public class MyZuulApp {
public static void main(String[] args) {
SpringApplication.run(MyZuulApp.class, args);
}
#Bean
public SimpleFilter simpleFilter() {
return new SimpleFilter();
}
}
Configuration:
##Zuul routes. Here for /student path, we are routing to localhost:8090 with extra path after that.
zuul.routes.book_app.url=http://localhost:8090
#zuul.routes.book_app=/book_app/**
##Ribbon is auto integrated with Zuul and for this exercise we are not using that.
ribbon.eureka.enabled=false
##Will start the gateway server #8080
server.port=8083
logging.level.org.springframework.web=DEBUG
Client App:
#RestController
#SpringBootApplication
public class BookApp {
#RequestMapping(value = "/available")
public String available() {
System.out.println("get abailable - ");
return "Spring in Action";
}
#RequestMapping(value = "/checked-out")
public String checkedOut() {
return "Spring Boot in Action";
}
public static void main(String[] args) {
SpringApplication.run(BookApp.class, args);
}
}
Configuration:
spring.application.name=book_app
server.port=8090
logging.level.org.springframework.web=DEBUG
Full code has organized at Github.
See my-zuul and book-app for that situation.
Thanks
I downloaded your code and run locally and it worked as expected.
Your Zuul application.properties
zuul.routes.book_app.url=http://localhost:8090
server.port=8083
Since your book application name is book_app.Zuul will proxy request to /book_app.So you have to send requests like this: http://localhost:8083/book_app/checked-out/.
I have an application with eureka, ribbon and feign. I have a feign RequestInterceptor, but the problem is that I need to know which is the host where I'm making the call. So far, with my current code I just can get the path with the RequestTemplate, but not the host.
Any idea?
I'm new to this as well, but I believe I've just learned something relevant to your question.
In the service you're creating, each instance can be given some unique identifier tied to a property included in its instance profile. Some .properties examples below:
application.properties (shared properties between instances)
spring.application.name=its-a-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
application-instance1.properties
server.port=5678
instance.uid=some-unique-property
application-instance2.properties
server.port=8765
instance.uid=other-unique-property
This service, as an extremely contrived example will show, can send out #Value annotated attributes to be consumed by the Ribbon app:
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class ItsAServiceApplication {
#Value("${server.port}")
private int port;
#Value("${instance.uid}")
private String instanceIdentifier;
public static void main(String[] args) {
SpringApplication.run(ItsAServiceApplication.class, args);
}
#RequestMapping
public String identifyMe() {
return "Instance: " + instanceIdentifier + ". Running on port: " + port + ".";
}
}
And just to complete the example, the Ribbon app that might consume these properties could look like this:
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class ServiceIdentifierAppApplication {
#Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(ServiceIdentifierAppApplication.class, args);
}
#GetMapping
public String identifyMe() {
return restTemplate.getForEntity("http://its-a-service", String.class).getBody();
}
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Results:
Reloading the rest template created by the services
As I said earlier, I'm pretty new to this and have just been learning myself. Hopefully this has given you an idea for how you might send these properties! I would imagine creating a dynamic property identifier through Spring Cloud Config would be ideal here.
I'm trying to make a sample OAuth2 Spring authorization and resource server. My intention is to implement two separate applications - one representing authorization server ant the other representing resource server. Since I'm quite a beginner in Spring Security, I guess I need some guidance to complete my task.
I already managed to implement a simple authorization server using in-memory token store (app named "OAuth").
AuthServerOAuth2Config.java
#Configuration
#EnableAuthorizationServer
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
private static final String RESOURCE_ID = "myResource";
#Autowired
private UserApprovalHandler handler;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authManager;
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.inMemory()
.withClient("test")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.resourceIds(RESOURCE_ID)
.secret("test")
.accessTokenValiditySeconds(300).//invalid after 5 minutes.
refreshTokenValiditySeconds(600);//refresh after 10 minutes.
// #formatter:on
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).userApprovalHandler(handler).authenticationManager(authManager);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
OAuth2SecurityConfig.java
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2SecurityConfig.class);
#Autowired
private ClientDetailsService clientService;
#Autowired
private DataSource dataSource;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
// #formatter:off
auth.inMemoryAuthentication()
.withUser("javabycode").password("123456").roles("USER")
.and()
.withUser("admin").password("admin123").roles("ADMIN");
// #formatter:on
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/oauth/token").permitAll();
// #formatter:on
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore) {
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientService));
handler.setClientDetailsService(clientService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
Accessing http://localhost:9081/OAuth/oauth/token?grant_type=password&username=admin&password=admin123 returns token as expected, so I'm guessing that authorization server is configured ok.
Now there's a resource server part (app named "RestTest"). I've managed to find some examples using RemoteTokenServices to access token service that resides in another app. So here's my resource server so far.
OAuth2ResourceConfig.java
#Configuration
#EnableResourceServer
#EnableWebSecurity
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "myResource";
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
anonymous().disable()
.requestMatchers().antMatchers("/v1/**")
.and().authorizeRequests()
.antMatchers("/v1/**").access("hasRole('ADMIN')")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
// #formatter:on
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws
Exception {
resources.tokenServices(tokenService()).resourceId(RESOURCE_ID).stateless(true);
}
#Primary
#Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://localhost:9081/OAuth/oauth/check_token/");
tokenService.setClientId("test");
tokenService.setClientSecret("test");
return tokenService;
}
}
I'm trying to secure my REST API (http://localhost:9081/RestTest/v1/foobar) so I believe that configuration above is correct, right? Problem is that when I access v1/foobar endpoint (via Postman) it's accessible without any authentication. So I think I'm simply missing some part of configuration, but I can't figure it out how to connect to authorization server correctly. One more thing to mention - I'm not using Spring Boot!
I'd really appreciate some guidance to make my sample work. Thanks!
EDIT1: I've added resourceId to both authentication and resource server - no luck. Is resourceId even mandatory?
You should add RESOURCE_ID both in ResourceServer and AuthorizationServer in a way that, (you updated your question though with that snippet)
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService()).resourceId(RESOURCE_ID).stateless(true);
}
And in your auth server
.scopes("read", "write", "trust").resourceIds(RESOURCE_ID)
Add a springSecurityFilterChain as you missing that in web.xml that you already said in comment
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
From spring docs:
It creates a Servlet Filter known as the springSecurityFilterChain which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, etc) within your application.
I have two services registered with eureka. Service C calls service A. Service C is feign client. I want implement feign client manually. But I catch an exception:
com.netflix.client.ClientException: Load balancer does not have
available server for client: service-test-a
Application class:
#EnableEurekaClient
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Feign interface:
#Component
public interface FeignService {
#RequestLine("GET /")
public String getServiceA();
}
Feign config:
#Configuration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
}
Controller:
#RestController
public class Controller {
private FeignService feignService;
#Autowired
public void Controller() {
feignService = Feign.builder()
.client(RibbonClient.create())
.target(FeignService.class, "http://service-test-a");
}
#RequestMapping(value = "/build", method = RequestMethod.GET)
public String getServiceC() {
return feignService.getServiceA();
}
}
What am I doing wrong?
AFAIK, there is no easy way of using OpenFeign with eureka. There is no guide or example for that. Also I guess that it may require some additional implementations and configuration.
Instead, please try to use Spring Cloud Feign. It provides full integration with eureka and ribbon without any additional implementation. You can use Spring Cloud Feign with just a few changes in your above code.
Please refer to Spring Cloud Feign
I am using
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.2.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
My main class:
#SpringBootApplication
//#Configuration
#ComponentScan(basePackages = "com.mypackage")
#EnableAutoConfiguration
#EnableEurekaClient
#EnableSwagger2
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
#LoadBalanced
#Bean(name="template")
RestTemplate restTemplate() {
return new RestTemplate();
}
}
My service calling:
#Autowired
private RestTemplate template;
ResponseEntity<String> avs = template.exchange("http://localhost:7075/xyz/json/authenticate",HttpMethod.POST ,request,String.class);
It is throwing the following exception
java.lang.IllegalStateException: No instances available for localhost
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:90)
at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:60)
at org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor$1.doWithRetry(RetryLoadBalancerInterceptor.java:48)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:276)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:157)
When you use a #LoadBalanced RestTemplate the hostname needs to be a serviceId not an actual hostname. In your case, it's trying to find a eureka record for localhost and can't find one. See the documentation for how to use multiple RestTemplate objects, one load balanced, one not.
#Configuration
public class MyConfiguration {
#LoadBalanced
#Bean
RestTemplate loadBalanced() {
return new RestTemplate();
}
#Primary
#Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
public class MyClass {
#Autowired
private RestTemplate restTemplate;
#Autowired
#LoadBalanced
private RestTemplate loadBalanced;
public String doOtherStuff() {
return loadBalanced.getForObject("http://stores/stores", String.class);
}
public String doStuff() {
return restTemplate.getForObject("http://example.com", String.class);
}
}
From what I read there is some kind of problem when you try to Autowire RestTemplate while using this Netflix cloud. However I found a workaround. First declare a new #Component class and in it create a method that returns RestTemplate:
#Component
public class RestTemplateComponentFix{
#Autowired
SomeConfigurationYouNeed someConfiguration;
#LoadBalanced
public RestTemplate getRestTemplate() {
// TODO set up your restTemplate
rt.setRequestFactory( new HttpComponentsClientHttpRequestFactory() );
return rt;
}
}
After that just Autowire the restTemplateComponentFix in your class and when when you need the rest template call the restTemplate() method. Something like this:
#Service
public class someClass{
#Autowired
RestTemplateComponentFix restTemplateComponentFix;
public void methodUsingRestTemplate(){
// Some code...
RestTemplate rt = restTemplateComponentFix.getRestTemplate();
// Some code...
}
}
The cool part is that you can easily unit test this code with something like:
RestTemplate rt = Mockito.mock(RestTemplate.class)
when(restTemplateComponentFix.getRestTemplate()).thenReturn(rt);
when(rt.someMethod()).thenReturn(something);