how use application name replace ip:port about spring cloud eureka? - spring-cloud

spring cloud eureka question: there are 3 microservices, 1 eureka server, 2 eureka client;
microservice A,B use annotation #EnableEurekaClient;
microservice A have a RESTful api "http://localhost:8080/hi". the api return "hello".
now, I call the api , use url "http://client/hi", but it doesn't work.
how use application name replace ip:port about spring cloud eureka?
the bootstrap.yml content:
spring:
application:
name: client
eureka:
client:
service-url:
defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/

There are many ways to do that and it depends on how you call REST API in your code.
If you are using RestTemplate to call the API, you can do that with #LoadBalanced RestTemplate
In your code that wants to invoke REST api, please define RestTemplate with #LoadBalanced like below.
#LoadBalanced
#Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
And when you call API, just use application name instead of host:port like below.
this.restTemplate.getForObject("http://client/hi", String.class)
If you are using SpringCloud Feign, you can define the interface to call your REST api like below (without URL)
#FeignClient(name="client")
public interface ProductResource {
:
}
And add annotation #EnableFeignClients in your spring boot application like below.
#SpringBootApplication
#EnableFeignClients
: // other annotations you need.
public class YourAPIInvokerApplication {
In both ways, you need to add a few dependencies.

I'm using RestTemplate, but i got Connection timed out: connect after put serviceName instead of localhost:port
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
call API:
FraudCheckResponse fraudCheckResponse = customerConfig.restTemplate().getForObject(
"http://fraud/api/v1/fraud-check/{customerId}",
FraudCheckResponse.class,
customer.getId()
);

Related

Keycloak authorization policy evaluation with spring cloud gateway

I am trying to use keycloak for authorization in spring cloud gateway. Keycloak does not provide any spring based adapters for policy enforcement for reactive stack.However, it does provide an endpoint for policy evaluation.
http://localhost:8080/realms/myrealm/protocol/openid-connect/token -- POST
Request:
grant_type:urn:ietf:params:oauth:grant-type:uma-ticket
response_mode:decision
audience:b2b
permission:spm_audit#GET
Header:
Authorization : bearer <JWT>
# spm_audit is the resource that I have created in keycloak and GET is the scope(using HTTP methods as api scopes).
RESPONSE:
{
"result": true
}
My problem is that above endpoint does not accept URI as permission in request body and I don't have any resource-name to request URL mapping at gateway.
One possible solution could be to use gateway's route id as resource name and pass it in permission
cloud:
gateway:
routes:
- id: spm_audit
uri: http://localhost:8001
predicates:
- Path=/gateway/spm/api/v1/registrations/{regUUID}/audit
filters:
- StripPrefix=1
metadata:
custom_scope: "test scope"
#Fetch the route info in auth manager
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); //(ServerWebExchange exchange)
route.getId();
The problem with this approch is that the route matching filters are applied after authorization filter and exchange.getAttribute(GATEWAY_ROUTE_ATTR) is coming as null, plus I will have to map all api paths in route configuration and will end up with a huge configuration file.
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, #Qualifier("keycloKWebClient")WebClient kycloakWebClient) {
http
.authorizeExchange()
.pathMatchers(
"/gateway/*/public/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.access(keyalokAuthManager(kycloakWebClient))....#this is where I call policy evaluation api
https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_authorization_api
What about using spring-security for resource-servers with a JWT decoder? It would be far more efficient as it would save many round trips to authorization-server (JWT decoder validates access-token with authorization-server public key downloaded once when policy enforcer requires a call to authorization-server for each and every incoming "non public" request).
You can map Keycloak "roles" to spring "granted authorities" and apply Role Based Access Control either with:
http.authorizeExchange().pathMatchers("/protected-route/foo").hasAuthority("ROLE_IN_KEYCLOAK") in your Java conf
#PreAuthorize("hasAnyAuthority('ROLE_IN_KEYCLOAK')") on your components methods.
For that, all you have to do is provide with an authentication converter with custom authorities converter:
interface AuthenticationConverter extends Converter<Jwt, Mono<JwtAuthenticationToken>> {}
interface AuthoritiesConverter extends Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> {}
#Bean
AuthoritiesConverter authoritiesConverter() {
return claims -> {
final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());
// concat client roles to following stream if your app uses client roles in addition to realm ones
return realmRoles.stream().map(SimpleGrantedAuthority::new).toList();
}
}
#Bean
public AuthenticationConverter authenticationConverter(AuthoritiesConverter authoritiesConverter) {
return jwt -> Mono.just(new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt.getClaims())));
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, AuthenticationConverter authenticationConverter) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(authenticationConverter);
}
You should also have a look at this repo: https://github.com/ch4mpy/spring-addons
There is a spring-addons-webflux-jwt-resource-server spring-boot (2.7 or later) starter which would save you quite some configuration hassle. Tutorials in this repo are using servlet variants of the libs, but the 4 starters (servlet/reactive with JWT-decoder/introspection) work the same and you should easily find what to adapt for your reactive app.

Springboot 2.7 with oidc bearer token always redirects to login page

I have a client Springboot app which needs to access an oidc-protected REST service, so no UI component or UI login. I have the following yaml in the client:
spring:
security:
oauth2:
client:
registration:
my-service:
client-id: client-id
client-secret: client-secret
authorization-grant-type: client_credentials
provider:
my-service:
token-uri: https://mytokenhost.mydomain/token
which points to a Keycloak server on which I have configured an oidc client with a service account and enabled it. I use a WebClient to connect to the REST service which is configured like:
#Bean
public WebClient webClient(final OAuth2AuthorizedClientManager authorizedClientManager) {
final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("my-service");
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
I also needed to manually define an OAuth2AuthorizedClientManager for the WebClient to work:
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
final ClientRegistrationRepository clientRegistrationRepository,
final OAuth2AuthorizedClientService authorizedClientService) {
final OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder().refreshToken().clientCredentials().build();
final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
And then use the WebClient to make a call to the REST service:
final String s = webClient
.method(HttpMethod.GET)
.uri("http://localhost:8080/my-rest-service/service?param1=value")
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(
"catalogue-services"))
.retrieve()
.bodyToMono(String.class)
.block();
Via IntelliJ I can see I get a token back, however no matter what I do Springboot redirects to the login page of the REST service. The REST service has yaml:
spring:
security:
oauth2:
client:
registration:
my-service:
client-id: client-id
client-secret: my-secret
authorization-grant-type: client_credentials
provider:
my-service:
token-uri: https://mytokenhost.mydomain/token/openid-connect/token
and a security configuration in the REST service:
#Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http.authorizeRequests(
a ->
a.antMatchers("/", "/error", "/someUrl")
.permitAll()
.anyRequest()
.authenticated())
.oauth2Login();
return http.build();
}
What is happening is that after successfully authenticating against KeyCloak, the REST service security filter is flagging the authentication as an anonymous login, presumably because the role is ROLE_ANONYMOUS and/or principal is anonymousUser. I can see the service account user name come back, the roles are included as well but maybe not being picked up. I have a realm role which is exposed in the token realm_access.roles and a client role which is exposed in resource_access.my-service.roles. When I debug the decision voting in the AffirmativeBase class I get:
AnonymousAuthenticationToken [Principal=anonymousUser,
Credentials=[PROTECTED], Authenticated=true,
Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null],
Granted Authorities=[ROLE_ANONYMOUS]]
I haven't used the Keycloak adapter since they look to be deprecated and the documentation still uses the WebSecurityConfigurerAdapter which is also deprecated in the Spring Security model.
It's likely I'm missing something really simple, but if anyone has done machine-to-machine oidc with Springboot and Keycloak and knows any tricks, any help would be appreciated.
If by "REST service" you mean a spring #RestControtroller (or #Controller with #ResponseBody), then it is an OAuth2 resource-server, not a client (like you configured in your "REST service" yaml file).
You can have a look at those tutorials which provide with OAuth2 concepts you need and sample configurations for resource-servers.

Spring boot admin shows status of gateway server down

I develop a micro services environment.It is developed by the spring cloud.
The gateway server has been developed by the Zuul and has secured by X.509 certificate authentication(mutual authentication).
I use spring boot admin for monitoring the micro services.
I already use Spring Cloud Discovery for my applications and i added a DiscoveryClient to Spring Boot Admin Server:
#Configuration
#EnableAutoConfiguration
#EnableDiscoveryClient
#EnableAdminServer
public class ApiAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(ApiAdminServerApplication.class, args);
}
}
Spring boot admin properties:
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=ALWAYS
spring.security.user.name=admin
spring.security.user.password=password
Service discovery properties:
management.endpoint.metrics.enabled=true
management.endpoints.web.exposure.include=*
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=${spring.application.name}
Gateway properties:
server.ssl.enabled=true
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:tls/keyStore.p12
server.ssl.key-store-password=changeit
server.ssl.trust-store=classpath:tls/trustStore.jks
server.ssl.trust-store-password=changeit
server.ssl.trust-store-type=JKS
server.ssl.client-auth=need
The gateway server is shown in spring boot admin panel as an application but its status is down.
How do I config spring boot admin for monitoring https gateway application?
Spring Boot Admin makes a call to /actuator/health endpoint to know the application health. Please check if this endpoint is working fine, otherwise, you might need to configure it based on your requirements.
By default Spring check for a number of health indicators mentioned here:
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#auto-configured-healthindicators
You can configure your own indicator as well, a snippet from documentation:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
#Component
public class MyHealthIndicator implements HealthIndicator {
#Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
I disable SSL authentication in the application.properties file of gateway :
management.server.ssl.enabled=false
And ignore actuator url path in the WebSecurityConfigurerAdapter class of gateway:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers( "/actuator/health/**").permitAll()
.anyRequest().authenticated().and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService());
}
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/**");
}
But be careful , this solution expose important health info of the gateway server for all request.

feign request microservice using domain url

I am invoking microservice api using feign like this now:
Response<List<AppResponse>> apps = appController.getApps();
And this is server side:
#RequestMapping(value = "/app")
#FeignClient(name = "soa-service")
public interface IAppController {
#GetMapping(value = "/list")
Response<List<AppResponse>> getApps();
}
Because the client side and server side registerd to eureka(the eureka could find the internal registed ip address),the invoke works fine.My question is : when the client and server not in one network(maybe the client not registed to eureka and deploy to external net). Is it possible to invoke microservice using domain url like "www.api.example.com/app/list"?
ps:I know one solution to change my invoke using okhttpclient,but the problem is: I must change all old feign invoke to new okhttp rest invoke.
#RequestMapping(value = "/app")
#FeignClient(name = "soa-service", url = "http://www.api.example.com/app/list")
public interface IAppController {
#GetMapping(value = "/list")
Response<List<AppResponse>> getApps();
}

Spring boot: Can not access secured resource from another Origin - CORS - Spring Security - Spring data rest

I can not access secured resource from another Origin. Searched a few days for solution and didn't find it, so I posted question here.
This is the story:
I created first Spring Boot Application that runs on default port 8080.
It depends on spring-boot-starter-data-rest and other dependencies and it has a GreetingRepository:
public interface GreetingRepository extends JpaRepository<Greeting, Long>, JpaSpecificationExecutor<Greeting> {}
globally enables CORS with RepositoryRestConfigurerAdapter:
#Configuration
public class GlobalRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getCorsRegistry()
.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*");
}
}
I created second Spring Boot Application that runs on port 9000 that will access this Greetings resource.
And it works. Second application sends HTTP request with method GET to http://localhost:8080/api/greetings and it gets response with Json data, with HEADER Access-Control-Allow-Origin: *. Everything is fine.
But.
Then I wanted to secure my resource in first application. There I included spring-boot-starter-security dependency and made configuration in WebSecurityConfigurerAdapter:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService myUserDetailsService;
#Autowired
PasswordEncoder myPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(myPasswordEncoder);
}
#Bean
public UserDetailsService createBeanUserDetailService() {
return new MyUserDetailsService();
}
#Bean
public PasswordEncoder createBeanPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
and made UserDetailsService and so on. (Important: I tested security before adding CORS, so this security configuration works and that is not a problem).
Then, after adding security in first application, second application sends same HTTP request with method GET to http://localhost:8080/api/greetings as the first time.
Now it gets an error:
Failed to load http://localhost:8080/api/greetings: Redirect from 'http://localhost:8080/api/greetings' to 'http://localhost:8080/login' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access.
I can not find solution for this problem. So CORS works for Spring Repository resources, and Spring Security works, but I can not access secured resource from another Origin because of /login page. How to solve this?
When Spring security is enabled, the security filters take precedence over CorsFilter. To make Spring Security aware of your CORS configuration call the cors() method in your HttpSecurity setup.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
This will give CorsFilter precedence over other Spring security filters. CorsFilter recognizes CORS preflight requests (HTTP OPTIONS calls) and allows it to go through without any security.