I'm following these recommendations: https://jhipster.github.io/microservices-architecture/ to organize the architecture of my microservices applications.
I have a setting with some microservices to be accessed by a gateway. I've already put the same secret (jhipster.security.authentication.jwt.secret) for all components (gateway, microservices and registry).
But, when I try to consume a microservice from the gateway, by the following way, I always receive a Access Denied Message (Forbidden 403):
#FeignClient(value = "storageservice")
public interface StorageServiceClient {
#RequestMapping(method = RequestMethod.GET, value = "api/applications")
ResponseEntity<List<ApplicationDTO>> getApplications();
}
I'm testing by this way (in my Gateway):
#RestController
#RequestMapping("/api")
public class StorageServiceTest {
#Inject
private DiscoveryClient discoveryClient;
#Inject
private RestTemplate template;
#Inject
private SecurityConfiguration security;
#Inject
private StorageServiceClient storageServiceClient;
#Inject
private AuthenticationManager authenticationManager;
#RequestMapping(value = "/apps",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public ResponseEntity<List<ApplicationDTO>> getApplications(){
ResponseEntity<List<ApplicationDTO>> apps = storageServiceClient.getApplications();
Stream.of(apps).forEach(System.out::println);
return apps;
}
}
The interest thing is that I've tested the direct access to the microservice (via postman) and the routed access by the gateway and either have worked well. But, specifically in this case when I try to consume the microservice data from gateway via API, I receive the 403 error.
In my microservice I have the configuration (in MicroServiceConfiguration class ".antMatchers("/api/**").authenticated()"). But in all the cases, when I'm testing via Postman, I'm always passing a valid JWT Token. But only in this specific case it doesn't works... I even tried to use a #Header annotation in my "StorageServiceClient" but it didn't any effect.
*********** EDITED ***********:
I've solved the authentication issue by creating a RequestInterceptor like that:
#Configuration
public class FeignConfig {
#Inject
private JHipsterProperties properties;
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
String token = Jwts.builder().
setSubject("admin")
.claim("auth", "admin")
.signWith(SignatureAlgorithm.HS512, properties.getSecurity().getAuthentication().getJwt().getSecret())
.compact();
requestTemplate.header("Authorization", "Bearer " + token);
};
}}
Related
In my Minimal API, I use and integrate with Kofax TotalAgility WCF endpoints. I wanted to implement this integration properly, so I added a remote assembly and added the WCF contract in it along with the service interface and implementation:
Service Interface:
public interface IKofaxService
{
public Task<string> CreateJob(long letterId);
public Task ActionHandler(PortalActionRequest request);
}
Service implementation:
public class KofaxService : IKofaxService
{
private readonly ILogger<KofaxService> logger;
private readonly KofaxSetup config;
private readonly KtaJob.IJobService jobService;
private readonly KtaActivity.IActivityService activityService;
public KofaxService(ILogger<KofaxService> inLogger, KofaxSetup inConfig)
{
logger = inLogger;
// Here is the problem: THe constructor's parameter should be IOptions<Kofaxsetup> instead of just KofaxSetup and this below line will become:
// config = inConfig.Value;
config = inConfig;
//WCF Generated Stuff within this remote assembly
jobService = new KtaJob.JobServiceClient(GetBinding(), GetEndpointAddress(config.KtaUrlApiJob));
activityService = new KtaActivity.ActivityServiceClient(GetBinding(), GetEndpointAddress(config.KtaUrlApiActivity));
}
public async Task<string> CreateJob(long letterId)
{
...
}
public async Task ActionHandler(PortalActionRequest request)
{
...
}
}
In order to have a Servces.AddKofaxTotalAgility() like fluent API, I added the extension method like so (in the remote assembly):
Service extension method:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddKofaxTotalAgility(this IServiceCollection services)
{
services.AddScoped<IKofaxService, KofaxService>();
return services;
}
}
Also in the remote assembly, I have a class representing the setting object from appSetting's section:
Config class:
public class KofaxSetup
{
public string KtaUrlApiActivity { get; set; } = string.Empty;
public string KtaUrlApiJob { get; set; } = string.Empty;
public string SessionId { get; set; } = string.Empty;
public string ProcessId { get; set; } = string.Empty;
}
Back in the Minimal API project, I added a reference to the remote assembly and also have the settings in appSettings.json file:
appSettings.json:
{
...
"KofaxSetup": {
"KtaUrlApiActivity": "https://kofax.somewhere.com/TotalAgility/Services/SDK/ActivityService.svc",
"KtaUrlApiJob": "https://kofax.somewhere.com/TotalAgility/Services/SDK/JobService.svc",
"SessionId": "7DB87F70018D4770BF6114B1C9BA6041",
"ProcessId": "66EC6EED5D024E7AB0013D60F7A04A1A"
},
...
}
Lastly, modifications to Program.cs are as follows:
Minimal API Program.cs
var builder = WebApplication.CreateBuilder(args);
...
// Trigger KofaxSetting object from AppSetting's section
builder.Services.Configure<KofaxSetup>(builder.Configuration.GetSection(nameof(KofaxSetup)));
...
// Add the service to the DI
builder.Services.AddKofaxTotalAgility();
...
All of this just results in this exception at startup:
Exception # var app = builder.Build();
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: DACRL.Integrations.Kofax.IKofaxService Lifetime: Scoped ImplementationType: DACRL.Integrations.Kofax.KofaxService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.) (Error while validating the service descriptor 'ServiceType: DACRL.Application.Core.Services.ILetterService Lifetime: Transient ImplementationType: DACRL.Api.Services.LetterService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.) (Error while validating the service descriptor 'ServiceType: DACRL.Application.Core.Services.ILetterService Lifetime: Transient ImplementationType: DACRL.Api.Services.LetterService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.)'
1/2:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: DACRL.Integrations.Kofax.IKofaxService Lifetime: Scoped ImplementationType: DACRL.Integrations.Kofax.KofaxService': Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.
2/2:
InvalidOperationException: Unable to resolve service for type 'DACRL.Integrations.Kofax.Configs.KofaxSetup' while attempting to activate 'DACRL.Integrations.Kofax.KofaxService'.
Note that the ILetterService is working properly, and this is the service that internally attempts to receive the IKofaxService from DI in its parameter. I'm thinking the error has something to do with the object KofaxSetup
Is there a best practice that I'm missing here? Am I supposed to have a parameter-less constructor somewhere? Is the Logger<KofaxService> injection within the service's implementation not valid?
I actually sorted the issue out but didn't want to waste a well-written question.
The problem was fact, the KofaxSetup class. I was receiving it as its type directly in the Service's constructor. I had to use IOptions<KofaxSetup> instead to solve the issue.
With AspNetCore.SignalR (1.0.0 preview1-final) and AspNetCore.All (2.0.6), how can I invoke a method on a hub in server code that is not directly in a Controller and is in a class that cannot be made via Dependency Injection?
Most examples assume the server code is in a Controller and should 'ask' for the hub via an injectable parameter in a class that will created by DI.
I want to be able to call the hub's method from server code at any time, in code that is not injected. The old SignalR had a GlobalHost that enabled this approach. Basically, I need the hub to be a global singleton.
Now, everything seems to be dependent on using Dependency Injection, which is introducing a dependency that I don't want!
I've seen this request voiced in a number of places, but haven't found a working solution.
Edit
To be more clear, all I need is to be able to later access the hubs that I've registered in the Configure routine of the Startup class:
app.UseSignalR(routes =>
{
routes.MapHub<PublicHubCore>("/public");
routes.MapHub<AnalyzeHubCore>("/analyze");
routes.MapHub<ImportHubCore>("/import");
routes.MapHub<MainHubCore>("/main");
routes.MapHub<FrontDeskHubCore>("/frontdesk");
routes.MapHub<RollCallHubCore>("/rollcall");
// etc.
// etc.
});
If I register them like this:
services.AddSingleton<IPublicHub, PublicHubCore>();
it doesn't work, since I get back an uninitiated Hub.
No It's not possible. See "official" answer from david fowler https://github.com/aspnet/SignalR/issues/1831#issuecomment-378285819
How to inject your hubContext:
Best solution is to inject your hubcontext like IHubContext<TheHubWhichYouNeedThere> hubcontext
into the constructor.
See for more details:
Call SignalR Core Hub method from Controller
Thanks to those who helped with this. Here's what I've ended up on for now...
In my project, I can call something like this from anywhere:
Startup.GetService<IMyHubHelper>().SendOutAlert(2);
To make this work, I have these extra lines in Startup.cs to give me easy access to the dependency injection service provider (unrelated to SignalR):
public static IServiceProvider ServiceProvider { get; private set; }
public static T GetService<T>() { return ServiceProvider.GetRequiredService<T>(); }
public void Configure(IServiceProvider serviceProvider){
ServiceProvider = serviceProvider;
}
The normal SignalR setup calls for:
public void Configure(IApplicationBuilder app){
// merge with existing Configure routine
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myHub");
});
}
I don't want all my code to have to invoke the raw SignalR methods directly so I make a helper class for each. I register that helper in the DI container:
public void ConfigureServices(IServiceCollection services){
services.AddSingleton<IMyHubHelper, MyHubHelper>();
}
Here's how I made the MyHub set of classes:
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
public class MyHub : Hub { }
public interface IMyHubHelper
{
void SendOutAlert(int alertNumber);
}
public class MyHubHelper : IMyHubHelper
{
public IHubContext<MyHub> HubContext { get; }
public MyHubHelper(IHubContext<MyHub> hubContext)
{
HubContext = hubContext;
}
public void SendOutAlert(int alertNumber)
{
// do anything you want to do here, this is just an example
var msg = Startup.GetService<IAlertGenerator>(alertNumber)
HubContext.Clients.All.SendAsync("serverAlert", alertNumber, msg);
}
}
This is a nice solution. In .NET Core 2.1 the service provider is disposed and you get cannot access disposed object. The fix is to create a scope:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider.CreateScope().ServiceProvider;
I am planning to create a microservice aplication with a dedicated service for dealing with data (mostly a Mongodb based service). I am wondering if there is a way using which my other microservices will be able to communicate with this service to make use of the shared data. Is it possible with JHipster API Gateway ?
If not how can I achieve this. I dont want to keep multiple copies of the same data within each microservice.
You can also use Feign clients with JHipster.
Annotate your SpringBootApplication with #EnableFeignClients
...
import org.springframework.cloud.openfeign.EnableFeignClients;
...
#SpringBootApplication
#EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
#EnableDiscoveryClient
#EnableFeignClients
public class MyApp {
...
}
Create a Feign client in your microservice
...
import org.springframework.cloud.openfeign.FeignClient;
...
#FeignClient("another-service")
public interface AnotherClient {
#RequestMapping(method = RequestMethod.GET, value = "/api/another")
List<AnotherDTO> getAll();
}
Inject the Feign client with #Autowired and call it. It should be ready to use.
#RestController
#RequestMapping("/api")
public class MyResource {
...
#Autowired
private AnotherClient anotherClient;
...
#GetMapping("/another")
#Timed
public List<AnotherDTO> getAll() {
log.debug("REST request to get all");
return anotherClient.getAll();
}
}
For us, it worked without implementing a ClientHttpRequestInterceptor and setting a JWT token.
You can register your microservices to the same registry and then they can call each other.
UPDATE : Here is how I made it work.
In the microservice consuming the data one, use RestTemplate with the current user's jwt-token in the Authorization-header for the API calls :
#Component
public class AuthenticateClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
String token = SecurityUtils.getCurrentUserJWT();
httpRequest.getHeaders().add("Authorization","Bearer "+token);
return clientHttpRequestExecution.execute( httpRequest, bytes );
}
}
My custom restTemplate using ClientHttpRequestInterceptor for adding token in header.
#Configuration
public class CustomBean {
#Autowired
AuthenticateClientHttpRequestInterceptor interceptor;
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(interceptor));
return restTemplate;
}
}
And in the resource controller where your are making the call for data:
#RestController
#RequestMapping("/api")
public class DataResource {
#Autowired
RestTemplate restTemplate;
#PostMapping("/hello")
#Timed
public ResponseEntity<Hello> createHello(#RequestBody Hello Hello) throws URISyntaxException {
//The name your data micro service registrated in the Jhipster Registry
String dataServiceName = "data_micro_service";
URI uri = UriComponentsBuilder.fromUriString("//" + dataServiceName + "/api/datas")
.build()
.toUri();
//call the data microservice apis
List<Data> result = restTemplate.getForObject(uri, Data[].class);
return ResponseEntity.created(new URI("/api/hellos/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
}
Typically microservices talk to each other. Thats the whole point. With Eureka discovery in place you simply call the microservice by name instead of the FQDN which we normally would use without microservice.
For e.g. your book-service will call the author-service like this
http://author-service/authors
full example here https://spring.io/blog/2015/01/20/microservice-registration-and-discovery-with-spring-cloud-and-netflix-s-eureka
Please don't forget that JHipster is an opinionated framework based off of Spring Cloud so you can find most of this stuff by searching Spring docs.
you can use below solution :
Microservice A (i.e UAA-SERVICE), and Microservice B
Microservice B want to connect microservice A and call services with Feign client.
1)This code for Microservice B
Client proxy :- #AuthorizedFeignClient(name = "UAA-SERVICE")
#AuthorizedFeignClient(name = "UAA-SERVICE")
public interface UaaServiceClient {
#RequestMapping(method = RequestMethod.GET, path = "api/users")
public List<UserDTO> getUserList();
#RequestMapping(method = RequestMethod.PUT, path = "api/user-info")
public String updateUserInfo(#RequestBody UserDTO userDTO);
}
UAA-SERVICE : find this name with running Application Instances with registry.
2) In Microservice B (application.yml)
Increase feign client connection Time Out:
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 50000
Increase hystrix Thread time out:-
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
shareSecurityContext: true
3) add #EnableFeignClients in main #SpringBootApplication class.
This solution is working fine for me.
I am using Jhipster Microservice Archi which suit better for my need.
I am trying to find a way to send request from Gateway to Microservice, Or at least send some more user information (Phone number, email ...) to microservice. Is there is any way to do that ?
What i wanted to do is basically when an user are just freshly registered, i want to send a request from the gateway to microservice in order to create for example TaxiLocation if the authority of the user is taxi, or create a shop if the user is a Shop manager ...
Thanks for your help.
What you want to do is communication between microservices, so here, the backend of your gateway wants to send request to your other microservice.
Am I correct?
If it's the case, you can read this ticket, it has been discussed:
https://github.com/jhipster/generator-jhipster/issues/3649
You have this tutorial too, by Xetys, from JHipster team:
http://stytex.de/blog/2016/03/25/jhipster3-microservice-tutorial/
And, you have a specific module which can do the job too, made by Christophe, from JHipster team too:
https://github.com/cbornet/generator-jhipster-swagger-cli
Hope, it will help you.
Thanks for your help, i finally figured it out :
1 - Create a Feign Config with a RequestInterceptor so then request will be authentificate
public class FeignConfig {
#Inject
private JHipsterProperties properties;
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
String token = Jwts.builder().
setSubject("xxx")
.claim("auth", "xxx")
.signWith(SignatureAlgorithm.HS512, properties.getSecurity().getAuthentication().getJwt().getSecret())
.compact();
requestTemplate.header("Authorization", "Bearer " + token);
};
}
}
2- Create MicroserviceClient & MicroserviceClientFallback
#FeignClient(name = "xxxxxxxx", fallback = MicroserviceClientFallback.class, configuration = FeignConfig.class)
public interface MicroserviceClient {
#RequestMapping(value = "/api/createuser",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
UserInfo createUser(UserInfo user);
}
#Component
public class MicroserviceClientFallback implements MicroserviceClient {
private final Logger log = LoggerFactory.getLogger(MicroserviceClientFallback.class);
#Override
public UserInfo createUser(UserInfo user) {
log.warn("Triggered fallback for createUser : {}", user);
return null;
}
}
3 - Finally Call it from a rest controller
#Inject
private MicroserviceClient microserviceClient;
...
microserviceClient.createUser(userInfo);
I just added a Websocket endpoint to my java ee jax-rs application. Within Jax-Rs endpoints i can access the role of the user via SecurityContext.
But within websocket i can't inject context stuff. So how to know the role of the user that tries to open a websocket session?
For this you will have to modify the Websocket handshake. You can do this as below:
1) Modify you websocket endpoint to use custom configurator
#ServerEndpoint(value = "/someWSEndpoint", configurator = SomeCustomConfigurationClass.class)
public class SomeWSService {
...
}
2) Modify WS Handshake similar to
public class SomeCustomConfigurationClass extends ServerEndpointConfig.Configurator {
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
config.getUserProperties().put("UserPrincipal",request.getUserPrincipal());
config.getUserProperties().put("userInRole", request.isUserInRole("someRole"));
}
}
3) Now you can access this in you ws endpoint class as
#OnOpen
public void onOpen(final Session session, EndpointConfig config) {
Principal userPrincipal = (Principal) config.getUserProperties().get("UserPrincipal");
Boolean userInRole = (Boolean) config.getUserProperties().get("userInRole");
//do what ever you like with it
}