Using a custom identity provider in Quarkus - quarkus

In my current project, we store user login info inside a MongoDB collection. We would like to implement an authentication mechanism that checks the credentials from a request against the information stored in said MongoDB. There is a tutorial for doing this with JPA + Postgres but there is no information on using MongoDB in the same capacity. I suspect that I would need to write a custom IdentityProvider for this case. I tried using the JPA identity provider as a base, but it looks like the security-jpa source code contains only an abstract identity provider, while the actual provider is generated automatically using black magic. Has anyone ever had success adapting the existing Quarkus security architecture to MongoDB or anything else that is not covered by security-jpa?

After some research, I was able to get a custom IdentityProvider to work. Here's a very simple demo (without any MongoDB logic):
#ApplicationScoped
public class DemoIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest> {
private static final Map<String, String> CREDENTIALS = Map.of("bob", "password124", "alice", "hunter2");
#Override
public Class<UsernamePasswordAuthenticationRequest> getRequestType() {
return UsernamePasswordAuthenticationRequest.class;
}
#Override
public Uni<SecurityIdentity> authenticate(UsernamePasswordAuthenticationRequest request,
AuthenticationRequestContext authenticationRequestContext) {
if (new String(request.getPassword().getPassword()).equals(CREDENTIALS.get(request.getUsername()))) {
return Uni.createFrom().item(QuarkusSecurityIdentity.builder()
.setPrincipal(new QuarkusPrincipal(request.getUsername()))
.addCredential(request.getPassword())
.setAnonymous(false)
.addRole("admin")
.build());
}
throw new AuthenticationFailedException("password invalid or user not found");
}
}
Note that in order to access QuarkusSecurityIdentity, the quarkus-security extension needs to be included as dependency in pom.xml:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
Furthermore, quarkus.http.auth.basic=true needs to be added to application.properties for the identity provider be used with basic auth.

Related

Setting basic auth in microprofile rest client

I have a service which exposes a number of Jax-RS interfaces for its services. I now want to use those interfaces to connect with the services. I am using Quarkus, which means I am using the microprofile rest client. Because I already have the JaxRS interface, using the #RegisterRestClient method is not really viable. Instead I am using the RestClientBuilder.
MyService client = RestClientBuilder.newBuilder()
.baseUri(URI.create("https://localhost:8080"))
.build(MyService.class);
The problem I am running into is authentication. The services i need to reach are locked behind basic Auth. All the guides I have found for the microprofile REST client are variations of this where the solution is to add a headerparam. This is not possible however, because I already have the interface premade, and copy-pasting the entire thing to add a header parameter is really something i would rather avoid.
It should also be mentioned that i have tried a #Provider filter to set the headers, but I can't seem to figure out how to only target a single REST client using that method, and I have several.
So: How do i set up basic authentication without messing with the Jax-Rs interface itself, using the microprofile rest client?
You should be able to use the #ClientHeaderParam annotation on MyService.
Something like:
#Path("/my")
#ClientHeaderParam(name = "Authorization", value = "{lookupAuth}")
public interface MyService {
default String lookupAuth() {
return "Basic " +
Base64.getEncoder().encodeToString("someuser:somepass".getBytes());
}
}
See this for more details
If modifying the interface is not possible, you have two options:
Create an interface that extends the one you use with this annotation:
#ClientHeaderParam(name = "Authorization", value = "{lookupAuth}")
public interface MyServiceWrapper extends MyService {
default String lookupAuth() {
return "Basic " + Base64.getEncoder().encodeToString("someuser:somepass".getBytes());
}
}
Create a ClientRequestFilter that fills the Authorization header:
#Priority(Priorities.AUTHENTICATION)
public class BasicRequestFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, getAccessToken());
}
private String getAccessToken() {
return "Basic " + Base64.getEncoder().encodeToString("someuser:somepass".getBytes());
}
}
And register the filter, e.g. programmatically:
MyService client = RestClientBuilder.newBuilder()
.register(BasicRequestFilter.class)
.baseUri(URI.create("https://localhost:8080"))
.build(MyService.class);
You can register a per-instance org.jboss.resteasy.client.jaxrs.internal.BasicAuthentication (or you can write a similar component) and register it using RestClientBuilder.

jhipster fine grain authorization, remove ROLE based authorization

I have been searching on how to remove the ROLE based authorization and replace it with fine grain authorization. What I meant by fine grain is
All method has a #PreAuthorize("isAuthorize('GETCLIENT')") or directly #IsAuthorize("GETCLIENT").
If the user has GETCLIENT in Authorization List, then the method can be executed. Otherwise, the system give error message or just deny access.
Any clue or information regarding how to do that is very much appreciated.
Thank you.
Like I said in the comment one quick and easy way to do this is to add your new custom authorities in the AuthoritiesConstants.java class. You have examples of how to do this here and here.
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
public static final String GETCLIENT = "ROLE_GETCLIENT"; // custom
private AuthoritiesConstants() {
}
}
Remember to insert the new role into your jhi_authority database table. You can assign new authorities to a user via the user management interface admin/user-management, it's possible the user needs to relog for the change to take effect.
Then in the method you want to secure just add:
#GetMapping("/clients/{id}")
#PreAuthorize("hasRole(\"" + AuthoritiesConstants.GETCLIENT + "\")")
public ResponseEntity<ClientDTO> getClient(#PathVariable Long id) {
log.debug("REST request to get Client : {}", id);
Optional<ClientDTO> clientDTO = clientService.findOne(id);
return ResponseUtil.wrapOrNotFound(clientDTO);
}
I said #Secured before but in reality you should use #PreAuthorize since it is more powerful and lets you work with Spring Expression Language (SpEL).
The go to resource to understand how JHipster security works is here, but in reality it just follows the standard Spring Security guidelines (as with many other things) so the official documentation about Spring Security should apply too.
Also, if you find this is too simple or that it is breaking the default conventions I found this guide about custom privileges to be particularly great. It's a bit more work, but should work better since you separate authorities (roles) from privileges.

Implementation of DynamoDB for Spring Boot

I am trying to implement a backend DynamoDB for my Spring Boot application. But AWS recently updated their SDKs for DynamoDB. Therefore, almost all of the tutorials available on the internet, such as http://www.baeldung.com/spring-data-dynamodb, aren't directly relevant.
I've read through Amazon's SDK documentation regarding the DynamoDB class. Specifically, the way the object is instantiated and endpoints/regions set have been altered. In the past, constructing and setting endpoints would look like this:
#Bean
public AmazonDynamoDB amazonDynamoDB() {
AmazonDynamoDB amazonDynamoDB
= new AmazonDynamoDBClient(amazonAWSCredentials());
if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
}
return amazonDynamoDB;
}
#Bean
public AWSCredentials amazonAWSCredentials() {
return new BasicAWSCredentials(
amazonAWSAccessKey, amazonAWSSecretKey);
}
However, the setEndpoint() method is now deprecated, and [AWS documentation][1] states that we should construct the DynamoDB object through a builder:
AmazonDynamoDBClient() Deprecated. use
AmazonDynamoDBClientBuilder.defaultClient()
This other StackOverflow post recommends using this strategy to instantiate the database connection object:
DynamoDB dynamoDB = new DynamoDB(AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(new EndpointConfiguration("http://localhost:8000", "us-east-1")).build());
Table table = dynamoDB.getTable("Movies");
But I get an error on IntelliJ that DynamoDB is abstract and cannot be instantiated. But I cannot find any documentation on the proper class to extend.
In other words, I've scoured through tutorials, SO, and the AWS documentation, and haven't found what I believe is the correct way to create my client. Can someone provide an implementation that works? I'm specifically trying to set up a client with a local DynamoDB (endpoint at localhost port 8000).
I think I can take a stab at answering my own question. Using the developer guide here for DynamoDB Mapper you can implement a DynamoDB Mapper object that takes in your client and performs data services for you, like loading, querying, deleting, saving (essentially CRUD?). Here's the documentation I found helpful.
I created my own class called DynamoDBMapperClient with this code:
private AmazonDynamoDB amazonDynamoDB = AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(
new EndpointConfiguration(amazonDynamoDBEndpoint, amazonAWSRegion)).build();
private AWSCredentials awsCredentials = new AWSCredentials() {
#Override
public String getAWSAccessKeyId() {
return null;
}
#Override
public String getAWSSecretKey() {
return null;
}
};
private DynamoDBMapper mapper = new DynamoDBMapper(amazonDynamoDB);
public DynamoDBMapper getMapper() {
return mapper;
}
Basically takes in endpoint and region configurations from a properties file, then instantiates a new mapper that is accessed with a getter.
I know this may not be the complete answer, so I'm leaving this unanswered, but at least it's a start and you guys can tell me what I'm doing wrong!

Spring AbstractRoutingDataSource is caching Datasource

I have a Spring MVC application deployed as Multi-Tenant (Single Instance) on Tomcat. All users login to the same application.
User belongs to a Region, and each Region has a separate Database instance.
We are using Dynamic DataSource Routing using Spring AbstractRoutingDataSource".
This works correctly only for the first time, when User_1 from Region_1 logs into the application, Datasource_1 is correctly assigned.
But subsequently when User_2 from Reqion_2 logs into the application, AbstractRoutingDataSource never gets called and Datasource_1 gets assigned.
It looks like Spring AbstractRoutingDataSource is caching the Datasouce.
Is there a way to change this behaviour of AbstractRoutingDataSource and get it working correctly?
You should provide more details for a better understanding.
I think the problem might be related to changing the tenant identifier. You may have a ThreadLocal storage to store the tenant identifier.
public class ThreadLocalStorage {
private static ThreadLocal<String> tenant = new ThreadLocal<>();
public static void setTenantName(String tenantName) {
tenant.set(tenantName);
}
public static String getTenantName() {
return tenant.get();
}
}
AbstractRoutingDataSource should use this to retrieve the tenantId
public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return ThreadLocalStorage.getTenantName();
}
}
And you should set the tenantId on each request for the current thread that is handling the request so that the following operations will be done on the correct database. For you may add a Spring Security filter to extract the tenant identifier from JWT token or from host subdomain.

Grails 2.2.3 + Spring security core + Multi-tenant Single DB plugin

I am new to grails and I am trying to implement spring security core and Multi-tenant single db plugin.
I have implemented the spring security core first and implemented custom AuthenticationProvider and Authentication.
Then I have installed the multi-tenant single db plugin and ran the 'mt-spring-security' script that automatically created custom tenantResolver and tenantRepository. I have hard-coded the tenantId in tenantResolver for testing purpose.
I have added the #MultiTenant annotation in the domain classes.
#MultiTenant
class ClientUser implements Serializable {
long idclient_user
Userprofile user
Client client
int tenantId
...
}
In the AuthenticationProvider, the ClientUser data is not filtered for the current tenant. It is bringing the data the all the tenant.
class ClientAuthenticationProvider implements AuthenticationProvider {
Authentication authenticate(Authentication auth) throws AuthenticationException {
ClientAuthentication authentication = auth
String password = authentication.credentials
String username = authentication.name
String clientName = authentication.clientName
...
Userprofile.withTransaction { status ->
def user = Userprofile.findWhere(username: username)
def client = Client.findWhere(clientname: clientName)
def clientUser = ClientUser.findWhere(client: client, user: user) <-- NOT FILTERED FOR THE CURRENT TENANT. I HARD-CODED INVALID TENANTID IN THE TENANTRESOLVER AND EXPECTING IT TO FAIL BUT IT STILL FINDS THE USER.
if (!clientUser) {
throw new UsernameNotFoundException('User not found', username)
}
...
}
...
result
}
I am not sure how the multi-tenant and spring security works together. I am having a hard time understanding the Architecture/design.
If anyone could provided me with a sample implementation or point me in the right direction, it will be really helpful.
Thanks,
dinesh
The problem was that the multitenant filter was registered before the spring security filter so the tenantResolver was not called until after the Spring security authentication. I fixed this problem by setting the resolveTenantBeforeLogin to true in the config.groovy
In config.groovy, add this line
multiTenant.resolveTenantBeforeLogin=true
After i added this line, the tenantResolver is called first and then the authentication.

Resources