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);
Related
We have a ServiceStack host, in which we have modularised the services. In addition we have a custom authentication solution based on the Basic Authentication. But what we would like to do is have different authentication methods for different services, maybe based on routes? Is this possible?
Secondly, is it possible to assign a common route prefix based on the service? As I said we have modularised our services, and in the AppHost definition we enter the assemblies of the different services, but is it possible to change the route prefix, i.e. Service1 to localhost/api1/servicemethods, Service2 to localhost/api2/servicemethods etc.?
You can limit that a Service should only authenticate with a specific provider by specifying the provider name in the [Authenticate] attribute, e.g:
[Authenticate(AuthenticateService.ApiKeyProvider)]
public class ApiKeyAuthServices : Service
{
public object Any(ApiKeyOnly request) => ...;
}
[Authenticate(AuthenticateService.JwtProvider)]
public class JwtAuthServices : Service
{
public object Any(JwtOnly request) => ...;
}
Otherwise inside your Service you can inspect how the request was authenticated by looking at base.SessionAs<AuthUserSession>().AuthProvider.
For defining dynamic routes have a look at:
Auto Route Generation Strategies
Dynamically adding Route Attributes
Customizing Defined Routes
Although ServiceStack isn't designed to define different sets of Apps within the same AppHost so if that's what you're trying to do I'd recommend instead having different AppHosts and using the Service Gateway for any Service-to-Service communication.
Many thanks for your reply. I must be doing something fundamentally wrong, even though I have registered two custom authproviders, both based on the BasicAuthProvider, using AuthenticateService.GetAuthProviders() returns an empty array.
This is the code I use to register the AuthProviders, and they both allow me to login, so I know they are working.
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new RMCredentialsAuthProvider(),
new RMKOTAuthProvider()
}));
The code from one of the custom providers is
public class RMKOTAuthProvider : BasicAuthProvider
{
#region Public Constructors
public RMKOTAuthProvider() : base()
{
}
#endregion Public Constructors
#region Public Methods
public override Task<IHttpResult> OnAuthenticatedAsync(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo, CancellationToken token = default)
{
session.FirstName = session.UserAuthName;
session.Roles = new List<string>
{
"KOT"
};
authService.SaveSessionAsync(session, SessionExpiry);
return base.OnAuthenticatedAsync(authService, session, tokens, authInfo, token);
}
public override Task<bool> TryAuthenticateAsync(IServiceBase authService, string userName, string password, CancellationToken token = default)
{
try
{
if (userName.IsNullOrEmpty() || password.IsNullOrEmpty())
return Task.FromResult(false);
var result = VerifyUser(username, password);
return Task.FromResult(result);
}
catch (InvalidCastException)
{
return Task.FromResult(false);
}
}
#endregion Public Methods
}
Can you please explain what step I am missing such that GetAuthProviders() can list the providers, and I can use the metadata you described earlier.
Many thanks in advance for your help with this.
I am using JHipster's Gateway with JWT and I have a microservice.
When the rest call is forwarded from the gateway to the microservice, in the microservice business class, I want to get the user id of the authenticated user.
The reason for this is I want to save it in the DB with the entity so that one user's data can be completely separate from other user's data (and a user cannot update another user's data...etc..).
While I can get the logged in user name, I don't have the user id.
What is the correct approach to resolving this issue:
call the gateway from the microservice ?
(this doesn't make too much sense to me as the gateway is calling the service and I'll want to know this info for most services).
update the TokenProvider in the gateway to include a user Id ? (not certain how to do this). Is this the correct approach ?
any other suggestions ?
Thanks,
Fergal.
Note: I see other similar questions. This is not a duplicate question. Do not mark this a duplicate unless absolutely certain. Note - I am using JWT
To solve this, I added the user id in the token from the gateway to each microservice.
Here is how I solved this in the JHipster generated code:
In Gateway, add UserService to UserJWTController, and get the user id, and
use it when you are creating a token.
public ResponseEntity<JWTToken> authorize(#Valid #RequestBody LoginVM loginVM) {
...
...
Optional<User> user = userService.getUserWithAuthoritiesByLogin(loginVM.getUsername());
Long userId = user.get().getId();
String jwt = tokenProvider.createToken(authentication, rememberMe, userId);
...
add the claim to the token:
claim(USER_ID_KEY, userId)
note, I added this to Token Provider:
private static final String USER_ID_KEY = "userId";
and then in my microservice's app, I did this:
created a new class:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class SamAuthenticationToken extends UsernamePasswordAuthenticationToken {
public Long getUserId() {
return userId;
}
private final Long userId;
public SamAuthenticationToken(Object principal, Object credentials, Long userId) {
super(principal, credentials);
this.userId = userId;
}
public SamAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, Long userId) {
super(principal, credentials, authorities);
this.userId = userId;
}
}
and then I changed TokenProvider.getAuthentication to add the following lines:
Long userId = null;
Object userIdObj = claims.get(USER_ID_KEY);
if (userIdObj != null) {
String userIdStr = userIdObj.toString();
userId = Long.parseLong(userIdStr);
log.debug("Claim--> {}", userId);
} else {
log.debug("No user id in token");
}
User principal = new User(claims.getSubject(), "", authorities);
return new SamAuthenticationToken(principal, token, authorities, userId);
and then I added a new method to SecurityUtils
public static Optional<Long> getUserId() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
if (authentication instanceof SamAuthenticationToken) {
SamAuthenticationToken samAuthenticationToken = (SamAuthenticationToken) authentication;
return samAuthenticationToken.getUserId();
}
return null;
});
}
and finally, I can now call this method from any Business class:
Optional<Long> userId = SecurityUtils.getUserId();
if (userId.isPresent()) {
log.info("User Id--->{}", userId.get());
} else {
log.info("No userId present.");
}
Any feedback welcome.
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'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);
};
}}
Is there some configuration or available module in Spring Security to limit login attempts (ideally, I'd like to have an increasing wait time between subsequent failed attempts)? If not, which part of the API should be used for this?
From Spring 4.2 upwards annotation based event listeners are available:
#Component
public class AuthenticationEventListener {
#EventListener
public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
// update the failed login count for the user
// ...
}
}
Implement an AuthenticationFailureHandler that updates a count/time in the DB. I wouldn't count on using the session because the attacker is not going to be sending cookies anyway.
I recently implemented a similar functionality to monitor login failures using JMX. Please see the code in my answer to question Publish JMX notifications in using Spring without NotificationPublisherAware. An aspect on the authenticate method of authentication provider updates MBean and works with a notification listener (code not shown in that question) to block user and IP, send alert emails and even suspend the login if failures exceed a threshold.
Edit
Similar to my answer to question Spring security 3 : Save informations about authentification in database, I think that capturing an authentication failure event (as opposed to customizing a handler) and storing information in database will also work and it will keep the code decoupled as well.
As suggested by Rob Winch in http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security, I just subclassed DaoAuthenticationProvider (which could also have been done using an aspect as Ritesh suggests) to limit the number of failed logins, but you could also assert pre-conditions as well:
public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Could assert pre-conditions here, e.g. rate-limiting
// and throw a custom AuthenticationException if necessary
try {
return super.authenticate(authentication);
} catch (BadCredentialsException e) {
// Will throw a custom exception if too many failed logins have occurred
userService.recordLoginFailure(authentication);
throw e;
}
}
}
In Spring config XML, simply reference this bean:
<beans id="authenticationProvider"
class="mypackage.LimitingDaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService"
p:passwordEncoder-ref="passwordEncoder"/>
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
Note that I think that solutions which rely on accessing an AuthenticationException's authentication or extraInformation properties (such as implementing an AuthenticationFailureHandler) should probably not be used because those properties are now deprecated (in Spring Security 3.1 at least).
You could also use a service which implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> to update the record in DB.
See spring application events.
Here is my implementation, hope help.
Create a table to store any invalid login attempts.
If invalid attempts > max allowed, set UserDetail.accountNonLocked to false
Spring Security will handle the "lock process" for you. (refer to AbstractUserDetailsAuthenticationProvider)
Last, extends DaoAuthenticationProvider, and integrate the logic inside.
#Component("authenticationProvider")
public class YourAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
UserAttemptsDao userAttemptsDao;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
Authentication auth = super.authenticate(authentication);
//if corrent password, reset the user_attempts
userAttemptsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//invalid login, update user_attempts, set attempts+1
userAttemptsDao.updateFailAttempts(authentication.getName());
throw e;
}
}
}
For full source code and implementation, please refer to this - Spring Security limit login attempts example,
create a table to store the values of failed attempts ex : user_attempts
Write custom event listener
#Component("authenticationEventListner")
public class AuthenticationEventListener
implements AuthenticationEventPublisher
{
#Autowired
UserAttemptsServices userAttemptsService;
#Autowired
UserService userService;
private static final int MAX_ATTEMPTS = 3;
static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
#Override
public void publishAuthenticationSuccess(Authentication authentication) {
logger.info("User has been logged in Successfully :" +authentication.getName());
userAttemptsService.resetFailAttempts(authentication.getName());
}
#Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
logger.info("User Login failed :" +authentication.getName());
String username = authentication.getName().toString();
UserAttempts userAttempt = userAttemptsService.getUserAttempts(username);
User userExists = userService.findBySSO(username);
int attempts = 0;
String error = "";
String lastAttempted = "";
if (userAttempt == null) {
if(userExists !=null ){
userAttemptsService.insertFailAttempts(username); }
} else {
attempts = userAttempt.getAttempts();
lastAttempted = userAttempt.getLastModified();
userAttemptsService.updateFailAttempts(username, attempts);
if (attempts + 1 >= MAX_ATTEMPTS) {
error = "User account is locked! <br>Username : "
+ username+ "<br>Last Attempted on : " + lastAttempted;
throw new LockedException(error);
}
}
throw new BadCredentialsException("Invalid User Name and Password");
}
}
3.Security Configuration
1) #Autowired
#Qualifier("authenticationEventListner")
AuthenticationEventListener authenticationEventListner;
2) #Bean
public AuthenticationEventPublisher authenticationListener() {
return new AuthenticationEventListener();
}
3) #Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//configuring custom user details service
auth.authenticationProvider(authenticationProvider);
// configuring login success and failure event listener
auth.authenticationEventPublisher(authenticationEventListner);
}