can't access to ProperySource using external configuration in spring boot - spring

i'm facing a problem with external configuration in my micro services project. when i access http:localhost:8888/user-service/default, i get the propertySource in the response:
{
"name":"user-service",
"profiles":[
"default"
],
"label":null,
"version":"8f38f7c09c5128f09e3181f7b4c187b3ad0383ec",
"state":null,
"propertySources":[
{
"name":"file:///D:/Projects/Freelance/hanouti/config-server/src/main/resources/shared/user-service.yml",
"source":{
"spring.datasource.username":"....",
"spring.datasource.password":"....",
"spring.datasource.url":"jdbc:mysql://${MYSQL_HOST:localhost}:3306/user_service_db?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC",
"spring.datasource.hikari.maximum-pool-size":25,
"spring.datasource.hikari.minimum-idle":1,
"spring.security.oauth2.resourceserver.jwt.issuer-uri":"http://localhost:8180/auth/realms/hanouti",
"spring.security.oauth2.resourceserver.opaquetoken.client-id":"user-service",
"spring.security.oauth2.resourceserver.opaquetoken.client-secret":"....",
"keycloak.serverUrl":"http://localhost:8180/auth",
"keycloak.realm":"hanouti",
"eureka.instance.prefer-ip-address":true,
"eureka.client.serviceUrl.defaultZone":"http://localhost:8761/eureka/",
"security.oauth2.resource.user-info-uri":"http://localhost:8180/auth/realms/master/protocol/openid-connect/userinfo",
"server.port":8001
}
}
]
}
but when i try to access it using #Value, it returns a null value.
this is my config class:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class KeycloakConfig {
#Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}")
public String clientId;
#Value("${spring.security.oauth2.resourceserver.opaquetoken.client-secret}")
public String clientSecret;
#Value("${keycloak.serverUrl}")
public String serverUrl;
#Value("${keycloak.realm}")
public String realm;
}
EDIT :
the configuration is loaded by the user service and used for bootstraping

Related

Spring Cloud Gateway : actuator refresh does not reload properties

I am actually working on a project with Spring Cloud Gateway.
I have a Configuration class which gets its Properties from a custom PropretySourceFactory. I want to make a hot reload of the properties so I call actuator/refresh (curl localhost:8080/actuator/refresh -d {}H "Content-Type: application/json") but it does not reload my configuration properties. No error or exceptions.
Here is the Configuration class:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#ConfigurationProperties
#PropertySource(value="", factory=NatsPropertySourceFactory.class)
public class NatsConfiguration {
private String apiKey;
private String apiKeyConsumer;
private String apiKeyValidity;
private String apiKeyCreation;
private int ratelimitMaxTokens;
private int ratelimitReplenishFrequency;
private int ratelimitReplenishRate;
// Getters and setter
//...
}
value is empty on PropertySource because I will not get my configuration from a file but from a message queue.
and the NatsPropertySourceFactory:
public class NatsPropertySourceFactory implements PropertySourceFactory{
final private NatsConfigurationService natsConfigurationService = new NatsConfigurationServiceImpl();
#Override
public PropertySource<?> createPropertySource(String arg0, EncodedResource arg1) throws IOException {
MapPropertySource result = null;
Logger log = LoggerFactory.getLogger(this.getClass());
try {
result = new MapPropertySource("nats", natsConfigurationService.getConfiguration());
} catch (ConfigurationException e) {
log.error("RECUPERATION DE CONFIGURATION DEPUIS NATS EN ERREUR", e);
System.exit(1);
}
return result;
}
}
My properties are not used with #Value, so I should not need #RefreshScope.
When I call /actuator/refresh the NatsConfiguration class is not recreated.
For information I use webflux with SpringSecurity (actuator urls are permitAll: pathMatchers("/actuator/**").permitAll())
#EnableWebFluxSecurity
#EnableWebFlux
public class SecurityConfiguration implements WebFluxConfigurer {
Where am I wrong?
By the way, I found the exact behaviour of /actuator/refresh: it reinstanciates the #Configuration class but does nothing for the PropertySourceFactory.
I have found a workaround: I created a REST Controler which calls the createPropertySource method of the PropertySourceFactory and then calls the /actuator/refresh url. It does exactly what I wanted: the #Configuration class is up to date with the new properties given by the PropertySourceFactory.

Spring Cloud Contracts and Spring Security issues

I am using Spring Cloud Contracts in projects to test microservices, everything is ok. But when I added Spring Security in the producer side, the GET return 401 status code instead of 200.
#Autowired
WebApplicationContext context;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(this.context);
}
My question is:
I have to avoid Security settings in the contract tests?
If I want to consider the security configuration, how to make it work.
I successfully used a custom annotation on the base class, as documented here test-method-withsecuritycontext
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUserDetails {
String username() default "email#example.com";
String role() default "DEFAULT_ROLE";
String password() default "123456";
}
and then
#WithMockCustomUserDetails
class AccountBase {
...
}
Two options AFAIK.
A) Use authorization header
request {
method 'POST'
urlPath '/check'
headers {
contentType(applicationJsonUtf8())
header(authorization(), "Bearer eyJhb.... ")
}
}
B)
Add #WithMockUser in my base test
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.WebApplicationContext;
#SpringBootTest
#WithMockUser //this will ensure a mock user will be injected to all requests
public abstract class BaseTestCloudContract {
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}

Boilerplate project with spring boot 2.0.0 not exposing custom actuator endpoints

I am trying to upgrade a spring boot boilerplate project to Spring boot 2.0.0.
I followed the official migration guide(this and this) but it is unable to expose the actuator custom endpoints.
I tested with this dummy endpoint:
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#Component
#Endpoint(id="testing-user")
public class ActiveUsersEndpoint {
private final Map<String, User> users = new HashMap<>();
ActiveUsersEndpoint() {
this.users.put("A", new User("Abcd"));
this.users.put("E", new User("Fghi"));
this.users.put("J", new User("Klmn"));
}
#ReadOperation
public List getAll() {
return new ArrayList(this.users.values());
}
#ReadOperation
public User getActiveUser(#Selector String user) {
return this.users.get(user);
}
public static class User {
private String name;
User(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
}
The endpoints work well if exposed directly from the child project but doesn't work if the endpoints are exposed from the parent boilerplate project which is being added as a dependency.
In my application.yml, I have added:
management:
endpoints:
web:
base-path: /
exposure:
include: '*'
There aren't many resources available and those which are, aren't helping.
Found the answer.
Instead of creating the bean with #Component, have a configuration file to create all the beans of your endpoints. For example, the configuration file might look like this:
#ManagementContextConfiguration
public class HealthConfiguration {
#Bean
public ActiveUsersEndpoint activeUsersEndpoint() {
return new ActiveUsersEndpoint();
}
// Other end points if needed...
}
The important thing was to have spring.factories file in resources.
The file will point to the configuration file where you created the beans of all your endpoints:
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=com.foo.bar.HealthConfiguration

Spring Boot REST with Hadoop HBASE

I'm looking to build an simple RESTFull API to access into HBase.
I looked Python HappyBase, but my cluster is kerberised. Now I'm into Spring.
I used to make simple API REST with Solr Cloud and Spring Boot.
Is it possible to do same with Hbase ?
I have no idea if I have to use Spring Boot 'Yarn App'
=> https://spring.io/guides/gs/yarn-basic/
Or Spring Hadoop.
=> https://projects.spring.io/spring-hadoop/
Just want a really simple API.
Thanks for help.
I wrote a simple demo project for using hbase in spring boot restful application without xml.
This demo mainly depends spring-data-hadoop and hbase-client.
gradle dependencies:
compile('org.springframework.boot:spring-boot-starter-data-rest')
compile('org.springframework.boot:spring-boot-starter-web')
compile 'org.springframework.data:spring-data-hadoop:2.5.0.RELEASE'
compile('org.apache.hbase:hbase-client:1.3.1'){
exclude group :'log4j',module:'log4j'
exclude group :'org.slf4j',module:'slf4j-log4j12'
exclude group: 'javax.servlet', module: 'servlet-api'
}
compile('org.springframework.boot:spring-boot-configuration-processor')
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
Configure the hbase connection parameters in spring boot's application.properties (No XML!):
spring.data.hbase.zkQuorum=192.168.0.109:2181
spring.data.hbase.zkBasePath=/hbase
spring.data.hbase.rootDir=file:///home/hbase-1.2.2
class HbaseProperties.java:
#ConfigurationProperties(prefix = "spring.data.hbase")
public class HbaseProperties {
// Addresses of all registered ZK servers.
private String zkQuorum;
// Location of HBase home directory
private String rootDir;
// Root node of this cluster in ZK.
private String zkBasePath;
// getters and setters...
}
HbaseConfig.java, inject the configurations into the HbaseTemplate:
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.hadoop.hbase.HbaseTemplate;
#Configuration
#EnableConfigurationProperties(HbaseProperties.class)
public class HbaseConfig {
#Autowired
private HbaseProperties hbaseProperties;
#Bean
public HbaseTemplate hbaseTemplate() {
org.apache.hadoop.conf.Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", this.hbaseProperties.getZkQuorum());
configuration.set("hbase.rootdir", this.hbaseProperties.getRootDir());
configuration.set("zookeeper.znode.parent", this.hbaseProperties.getZkBasePath());
return new HbaseTemplate(configuration);
}
}
Service class, we can use the configured HbaseTemplate now:
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.hadoop.hbase.HbaseTemplate;
import org.springframework.stereotype.Service;
import com.zql.hbasedemo.vo.Quote;
#Service
public class FeedService {
#Autowired
private HbaseTemplate hbaseTemplate;
#PostConstruct
public void test(){
Quote quote = new Quote();
quote.setEventType("ft");
quote.setHandicap("4");
quote.setMarket("OU");
quote.setMatchId("27350208");
quote.setSelection("OVER");
quote.setPrice("1.93");
saveQuote(quote);
}
public Quote saveQuote(Quote quote) {
hbaseTemplate.put("quotes", quote.getMatchId(), "data", quote.getMarket() + ":" + quote.getSelection(),
quote.getPrice().getBytes());
return quote;
}
}
Rest Controller.
#RestController
public class FeedController {
#Autowired
private FeedService feedService;
#SuppressWarnings({ "unchecked", "rawtypes" })
#PostMapping(value = "/feed/quote", consumes = "application/json", produces = "application/json")
public ResponseEntity<Quote> saveQuote(#RequestBody Quote quote) {
Quote result = feedService.saveQuote(quote);
return new ResponseEntity(result, new HttpHeaders(), HttpStatus.OK);
}
}

Override default dispatcherServlet when a custom REST controller has been created

Following my question here, I have succeded in creating a custom REST controller to handle different kinds of requests to /api/urls and operate accordingly.
However, there is still a default controller handling requests at /urls which affects my application: When receiving a request that is not /api/something, it should fetch my database for the URL linked to said /whatever and redirect the user there. Moreover, under /api/urls I've developed certain validation rules to ensure integrity and optimization of the requests, which does not jhappen in /urls so anyone could insert any kind of data into my database.
What would be a possible way to disable this default handler? Seeing the logs I headed to register my own ServletRegistrationBean as instructed here but this is for having two isolated environments as far as I understand
My goal is to simply "disconnect" /urls URL from the default REST controller -which is no longer of any use to me now that I have my own one- and just use the custom one that I implemented in /api/urls (Or whatever other URL I may decide to use such as "/service/shortener* if possible)
Below are my Java classes:
Url.java (getters and setters omitted for brevity):
#Document
public class Url {
#Id private String id;
private String longURL;
private String hash;
private String originalUrl;
private String shortUri;
private Date creationDate;
}
UrlRepository.java
import org.springframework.data.mongodb.repository.MongoRepository;
public interface UrlRepository extends MongoRepository<Url, String> {
// Empty
}
UrlController.java:
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/urls")
public class UrlController {
#Autowired
private UrlRepository repo;
#RequestMapping(method=RequestMethod.GET)
public List<Url> getAll() {
System.out.println("Showing all stored links");
List<Url> results = repo.findAll();
return results;
}
#RequestMapping(method=RequestMethod.GET, value="{id}")
public Url getUrl(#PathVariable String id) {
System.out.println("Looking for URL " + id);
return null;
}
#RequestMapping(method=RequestMethod.POST)
public Url create(#RequestBody Url url) {
System.out.println("Received POST " + url);
return null;
}
#RequestMapping(method=RequestMethod.DELETE, value="{id}")
public void delete(#PathVariable String id) {
//TBD
}
#RequestMapping(method=RequestMethod.PUT, value="{id}")
public Url update(#PathVariable String id, #RequestBody Url url) {
//TBD
}
}
Application.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Instead of trying to hack your way around Spring Boot and Spring Data REST I strongly suggest to work WITH the frameworks instead of around them.
To change the default context-path from / to /api simply add a property to your application.properties file.
server.context-path=/api
Now you would need to change your controller mapping to /urls instead of /api/urls.
If you only want /api for Spring Data REST endpoints use the following property
spring.data.rest.base-uri=/api
This will make all Spring Data REST endpoints available under /api. You want to override the /urls so instead of using #Controller use #RepositoryRestController this will make your controller override the one registered by default.
#RepositoryRestController
#RequestMapping("/urls")
public class UrlController { ... }

Resources