Websocket (java ee) how to get role of current user - security

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
}

Related

Requesting a DbContext from a scope in a SignalR Core hub, passing it a connection string

I have an ASP .Net Core 2.2 Web API with a SignalR hub.
When the API receives a message form the client, it needs to save this message to the database. It does this as follows:
The SignalR Hub:
public class ChatHub : Hub
{
public async Task SendMessageToGroup(int clientId, int groupName, string message)
{
await SaveMessage(clientId, groupName, message);
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
private async Task<bool> SaveMessage(int clientId, string groupName, string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();
Message newMessage = new Message()
{
Message = message,
GroupName = groupName,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(pwMessage);
dbContext.SaveChanges();
}
return true;
}
}
All would be well except for the fact that this is a multi-tenant application. Normally, when the client calls the API's controller methods using HTTP requests, the client sends through a "TenantId" header with each request. I then have middleware that intercepts this request, grabs the TenantId from the header, calls a service to retrieve this Tenant using the tenantId, and saves the Tenant object in the HttpContext. Then, on the DbContext's OnConfiguring() method, I use this Tenant Object (stored in the HttpContext) to set the connectionString of the dbContext to whatever database this tenant uses. So:
Middleware:
public class TenantIdentifier
{
private readonly RequestDelegate _next;
public TenantIdentifier(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
Tenant tenant = await GetTenant(tenantId);
httpContext.Items["Tenant"] = tenant;
await _next.Invoke(httpContext);
}
}
DbContext.cs:
public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}
public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
optionsBuilder.UseMySql(connectionString);
}
Now, when the client calls the SignalR hub, and I create a new scope in the hub and request the DbContext, it's connection string is null. This appears to be because, unlike an HTTP request, calling a SignalR hub doesn't trigger the middleware (which is responsible fro identifying the tenant)
How can I, when requesting a DbContext from the scope, manually pass it the connection string, instead of relying on it to try and generate the connectionString in the OnConfiguring() event (which won't work)
Hope this makes sense :/ Thank you
If you add the IHttpContextAccessor to your Hub class constructor - are you able to access the current context (and headers) there?
public class ChatHub : Hub
{
private IHttpContextAccessor currentContext;
public ChatHub(IHttpContextAccessor currentContext)
{
this.currentContext = currentContext;
}
}
Of course, remembering to register the HttpContextAccessor in the DI too:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}

How can microservice can talk to other microservice in JHipster

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.

JHipster: forbidden error when trying to access microservice from gateway

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);
};
}}

How to write client proxy for SPI and what the difference between client and server proxies?

I have developed own idGenerator based on Hazelcast IdGenerator class (with storing each last_used_id into db). Now I want to run hazelcast cluster as a single java application and my web-application as other app (web-application restart shouldn't move id values to next block). I move MyIdGeneratorProxy and MyIdGeneratorService to new application, run it, run web-application as a hazelcast-client and get
IllegalArgumentException: No factory registered for service: ecs:impl:idGeneratorService
It was okay when client and server were the same application.
It seems it's unable to process without some clientProxy. I have compared IdGeneratorProxy and ClientIdGeneratorProxy and it looks the same. What is the idea? How to write client proxy for services? I have found no documentation yet. Is direction of investigations correct? I thought it is possible to divide hazelcast inner services (like a id generator service) and my business-processes. Should I store custom ClientProxy (for custom spi) in my web-application?
This is a demo how to create a client proxy, the missing part CustomClientProxy function call, is quit complicated(more like a server proxy,here is called ReadRequest, the server is called Operation), you can find a how AtomicLong implement.For every client proxy method you have to make a request.
#Test
public void client() throws InterruptedException, IOException
{
ClientConfig cfg = new XmlClientConfigBuilder("hazelcast-client.xml").build();
ServiceConfig serviceConfig = new ServiceConfig();
serviceConfig.setName(ConnectorService.NAME)
.setClassName(ConnectorService.class.getCanonicalName())
.setEnabled(true);
ProxyFactoryConfig proxyFactoryConfig = new ProxyFactoryConfig();
proxyFactoryConfig.setService(ConnectorService.NAME);
proxyFactoryConfig.setClassName(CustomProxyFactory.class.getName());
cfg.addProxyFactoryConfig(proxyFactoryConfig);
HazelcastInstance hz = HazelcastClient.newHazelcastClient(cfg);
Thread.sleep(1000);
for (int i = 0; i < 10; i++)
{
Connector c = hz.getDistributedObject(ConnectorService.NAME, "Connector:" + ThreadLocalRandom.current()
.nextInt(10000));
System.out.println(c.snapshot());
}
}
private static class CustomProxyFactory implements ClientProxyFactory
{
#Override
public ClientProxy create(String id)
{
return new CustomClientProxy(ConnectorService.NAME, id);
}
}
private static class CustomClientProxy extends ClientProxy implements Connector
{
protected CustomClientProxy(String serviceName, String objectName)
{
super(serviceName, objectName);
}
#Override
public ConnectorState snapshot()
{
return null;
}
#Override
public void loadState(ConnectorState state)
{
}
#Override
public boolean reconnect(HostNode node)
{
return false;
}
#Override
public boolean connect()
{
return false;
}
}
EDIT
In hazelcast the IdGenerate is implemented as a wrapper for AtomicLong, you should implement you IdGenerate by you own, instead of extend IdGenerate.
So you have to implement these(more like a todo list XD):
API
interface MyIdGenerate
Server
MyIdGenerateService
MyIdGenerateProxy
MyIdGenerateXXXOperation
Client
ClientMyIdGenerateFactory
ClientMyIdGenerateProxy
MyIdGenerateXXXRequest
I also made a sequence(same as IdGenerate) here, this is backed by zookeeper or redis,also it's easy to add a db backend,too.I will integrate to hazelcast if I got time.

How can I limit login attempts in Spring Security?

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);
}

Resources