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.
Related
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.
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.
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()
);
I was trying it implement custom authentication, Authentication works fine, but have problems with Authorization. I am using JWT tokens, Any API I try to access it throwing me a 403 forbidden error. I am not sure what is wrong. I have the full source code in github. https://github.com/vivdso/SpringAuthentication, Spring boot magic is not working on this. Any pointers are apperciated.
Using MongoDb as my repository to store user accounts and roles.
InMemory Authentication is working fine, but Custom Authentication always returs 403, Below is my I extended WebSecurityConfigurerAdapter
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// authenticationManagerBuilder.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
// authenticationManagerBuilder.inMemoryAuthentication().withUser("user").password("user").roles("USER");
authenticationManagerBuilder.authenticationProvider(getCustomAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/customer").hasAuthority("ADMIN")
.antMatchers(HttpMethod.GET, "/order").hasAuthority("USER").and()
.csrf().disable();
}
#Bean
protected CustomAuthenticationProvider getCustomAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
I don't have any custom implementation for authorization.
The issue is resolved, I have updated Github repository. The spring boot security was working fine, the issue was the roles assigned to the user collection was a Json string object (e.g. {"role":"ROLE_ADMIN"}) instead of sting object "ROLE_ADMIN".
Thanks
I am building a REST based web application and i have applied the spring security to it successfully but my concern is that i have added a custom filter
http
.authorizeRequests()
.antMatchers("/mypro/userCont/signup").permitAll()
.and()
.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
.httpBasic();
i want that this filter should only run for secured urls not for unsecured urls if it get runs for non secured url then it should not interrupt me to get resource on non-secure url.
My scenerio is if user is not logged in for secured url custom filter should be run which check the Principal using below code:
Authentication auth = (Authentication) SecurityContextHolder.getContext().getAuthentication();
if the auth is null user should seen default spring security login popup
and if i hit the non secure url i should get to the resource.
Can anyone help me.
You can skip security entirely for particular resources by configuring WebSecurity. In this case, Spring security will totally ignore that URL pattern:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity
.ignoring()
.antMatchers("/resources/**"); //skip security entirely
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilter(mycustomFilter)
.authorizeRequests().anyRequest()
.and()
.httpBasic();
}
}
or, you can just simply use the permitAll() method to exclude the ones you need to allow anonymous access.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilter(mycustomFilter)
.authorizeRequests()
.antMatchers("/not-secured/**").permitAll()
.and()
.httpBasic();
}
}