Keycloak: Send AdminEvent from custom Endpoint (SPI) - java

I have a Keycloak extension (Custom Endpoints, SPI). Now I want to add sending of AdminEvents, which I implemented as follows:
private void logAdminEvent(ClientConnection clientConnection, UserRepresentation rep, OperationType operation, ResourceType resource) {
RealmModel realm = session.getContext().getRealm();
// beware: clientConnection must not be null because of missing check for NullPointer in Keycloak
ClientModel client = realm.getClientByClientId(ROLE_ATTRIBUTE_CLIENT);
AdminAuth adminAuth = new AdminAuth(realm, authResult.getToken(), authResult.getUser(), client);
AdminEventBuilder adminEvent = new AdminEventBuilder(realm, adminAuth, session, clientConnection);
adminEvent
.operation(operation)
.resource(resource)
.authIpAddress(authResult.getSession().getIpAddress())
.authClient(client)
.resourcePath(session.getContext().getUri())
.representation(rep);
adminEvent
.success();
}
I am aware that the admin event logging must be activated in Keycloak admin console, which I did.
Maybe it is relevant that the logged in user has no administration privileges, but it also did not work when I gave admin privileges.
I need Ideas or Hints to what I am doing wrong here. Documentation and web research unfortunately did not help.

Take a look at Keycloak sources, especially something like RootAdminResource. As far as i remember all admin resources (e.g. controllers) create events via builder that cloned from builder that was injected via constructor by parent resource. You may be missing some initialization tricks.

Ok, we found that.
First, for update / delete, we had to add the realm to the adminEvent.
Second, for create, we had the event logging after the
session.getTransactionManager().commit();
took place. Setting commit after the adminEvent.success() fixed the Issue.
Maybe this can help anyone.

Related

Keycloak - how to handle multiple work contexts

I have an application where single user can work in contexts of multiple companies. We call such a connection (user<->company) a permit. Every one of this permits can have different sets of permissions/roles. We want user to login just once and then he can simply change permits within application without need to enter password again.
Till now we had only one application and kept this whole permission model in our own DB. Unfortunately now we have to support second application which should inherit those permits. I was wondering wether is possible to move that model to keycloak so we don't have to replicate it to every single db and keep it in sync manually.
I have searched keycloak documentation regarding this topic but have found no information att all, which seems quite odd, because I don't think we are the first one working with multiple context application.
So now I'm asking is it possible to configure our model in keycloak and if so, how to do it? Eventually are there different options? I guess that I can provided that model as a claim with json structure but that doesn't feel right to me. I was thinking about custom IDP which could provide such claims based on DB so there no spelling errors and less repetition but I feel there should be a better way.
You could try to write your own Keycloak provider (SPI). There is a built in mechanism that allows you to expose REST endpoint on the Keycloak: https://github.com/keycloak/keycloak/tree/master/examples/providers/domain-extension
That REST could be called with authorized context only for example by passing Access-Token (Authorization header with Bearer value). On the provider level (through implementation of: org.keycloak.services.resource.RealmResourceProviderFactory and org.keycloak.services.resource.RealmResourceProvider) you have access to user's Keycloak session and object UserModel like in the following code:
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(keycloakSession, keycloakSession.getContext().getRealm());
UserModel userModel = authResult.getUser();
UserModel class has methods for getting and setting attributes, so some information that indicates the current permit/company ID can be stored there. You can use REST methods exposed on the Keycloak to modify the model within the 'session' (represented by Access-Token).
The Github example shows also how to use another Keycloak provider (ex. built-in JPA provider) from you custom provider's level, so using that approach you could try to connect to the database with your permits/company informations. Of course the datasource representing you database should also be registered as Keycloak datasource.

log out from Oauth2 server AND from all clients

We have a java web app, which contains a lot of wars. We have an Oauth2 server(written by us) and we will have a lot clients( around 8). All of this will be under the same domain. Except of this we have another app( running on completely different tomcat. There a Liferay is used). The idea is that that the user will use them as they are using one app and they should not see big difference.
This is way now what I need is that when I log out from one place in some way to say the oauth2 server and all other clients to log out, too.
Because for client should be : I already logged out why in some parts I'm still logged in?
Currently I'm not sure how to do it.
And to a lot of places I read that normally this is not the practice.
Can you give me hints and explain me from where I can start? Maybe to use Oauth2 in my case in not the best choice?
For your requirement, you can implement OAuth2 using JDBC Token Store from Spring Security. For this to work once user logs out, all client should invoke your Delete token API where you can remove the Access Token
#FrameworkEndpoint
public class RevokeTokenEndpoint {
#Resource(name = "tokenServices")
ConsumerTokenServices tokenServices;
#RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
#ResponseBody
public void revokeToken(HttpServletRequest request) {
//Delete the Token
}
}
Also, you should delete the refresh token.This way, the token would be invalidated once user logs-out and subsequent client can no longer use the same token

Implementing Collaborative Authentication in Java Web Application

I've a requirement to build a Java based web application where a resource should be available only when all the authorized users of that resource are logged in. Also, if any authorized user logs out, the resource should no longer be available to any of them.
The resource could be of any type(html pages, pdf documents, spread sheets etc.,)
Is there any existing authentication standards/protocols that supports this type of requirement or I've to build this from scratch?
the resource should be available only when all the authorized users of that resource are logged in. Also, if any authorized user logs out, the resource should no longer be available to any of them.
Once you have given access to the resource to an user, this user will be able to download / take screenshots / save / record the resource, no matter if it's a PDF document, an image, an audio file. I don't know the context and the goal of what you're trying to build, but you should know that it will be insecure in any case.
Even putting this consideration aside, you'll need a real-time solution. Once the user has loaded the page containing the resource, you need to be able to hide or deny modification rights to him. This means you have to use something like WebSockets or Ajax Polling on the client side to have the frontend know when your server considers that not all the required users are online, and that the access to the resource should be "denied". But once more since this is client-side code it can easily be changed or altered, the requests it is sending can easily be blocked by the user, so it is once again inherently insecure.
I'd suggest giving a little bit of context here and describing what is the problem you're trying to solve, because most likely there's a more reasonable solution to solve it.
If what you need to do is to deny modification rights if not all the "resource owners" are online, it is more easily doable since the modifications will happen on the server side. In this case, a solution using WebSockets could quite easily be implemented but I don't know a library or framework that does such a thing. Most likely you will have to build it yourself.
If you're not constrained to use a specific web framework, feel free to try the following filter based implementation for jersey. Note that you still need to add a fair amount of custom code for handling the logic of "Collective authentication" as jersey only provides the basic tools required for this, and it doesn't explicitly implement the whole concept. Here's how you could do it, on a high level:
class AuthorizationProvider {
public void authenticate(ContainerRequestContext requestContext) {
// Here you would need to query your database to get the Collection of Users belonging
// to the "Collective" Role. You would then check if they are all logged in.
// A really abstract version would look like this, assuming you've already queried the DB
// and have a reference to the above mentioned Collection.
if (collectiveUsers.size == collectiveUsers.stream().filter(User::isLoggedIn).count()) {
return true;
}
return false;
}
}
class AuthorizationRequestFilter implements ContainerRequestFilter {
private final AuthorizationProvider authorizationProvider;
#Override
public void filter(ContainerRequestContext requestContext) {
if (authorizationProvider.authenticate(requestContext)) {
// serve whatever it is you want to serve if all required users are logged in
} else {
// otherwise reject the request
requestContext.abortWith(Response
.status(Response.Status.UNAUTHORIZED)
.entity("Resource available only after collective login")
.build());
}
}
}
#ApplicationPath("/")
class MyApplication extends ResourceConfig {
public MyApplication() {
// Register the filter
register(AuthorizationRequestFilter.class);
}
}
Apart from this, you would also need to handle the Login part.
You would assign these specific users the Collective role, and you would mark them as logged in, whenever they successfully pass through login authentication.
If all the above conditions are met, you should be able to successfully serve your "Collective only" page, only when all "Collective" users are logged in.
This also covers the part where if either one of these users logs out, you store the state in your database (mark the Collective user with isLoggedIn = false). So from this point on, whenever somebody requests the page, it will return Unauthorized.
Conversely, you can also attempt to implement SSE (Server sent events) to actively update the frontend part, if somebody logs out. With this, the page will actively be disabled even if somebody has already managed to get it previously.
Container request filter source and example, for reference, jersey docs

SecurityUser in Async Spring Listeners

I am facing a problem and hope that someone can give me some advice.
I have a restful Spring application which allows HTTP-requests. I am using spring security and offer oauth2. In my basic service, called by a controller, I am fetching the currently logged user via:
SecurityUser loggedUser = (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
So far, that's all working fine. Furthermore, I am using events and event listeners implementing the org.springframework.context.ApplicationListener interface. I configured the application event multicaster the way that events are handled asynchronously (using a SimpleAsyncTaskExecutor).
The (obvious) problem arises when I try to use my service (and methods which rely on the currently logged user) in my listeners. They cannot access the context as they are working asynchronous. Hence, I cannot fetch the current user.
Can you give any advice how to solve the problem? Are there more options than to
save and extend the context somehow so that the async listeners can
still access it? if so, how to do it?
change all the services methods to hand over the user via a parameter
(instead of the service fetching the current user itself) and store
its id e.g. in the event.
Thank you very much. Myself
You can use a DelegatingSecurityContextAsyncTaskExecutor instead of a SimpleAsyncTaskExecutor. The problem is that you only can get the user's context when the user is logged.

Grails Spring Security: Anonymous pages not showing authentication information

I'm using the Spring Security plugin in Grails. I have a controller which uses annotations for some of the secure actions but not for non-secure content. And sure enough, the sec:isLoggedIn and other sec:loggedInUserInfo tags work for the secured actions, but they always show up as non logged in even when the user is logged in for the non-secure views. Here's what my controller looks like:
class ContentController {
def anonymousContent() {
getContent(params, 'pages')
}
#Secured(['ROLE_USER', 'ROLE_ADMIN'])
def secureContent() {
getContent(params, 'secure')
}
private getContent(params, path) {
def viewPath = "${path}/${params.view}"
render(view: viewPath, model: params)
}
}
I should mention that I am using some custom authentication as part of a SSO solution which basically has me overriding a couple classes like AuthenticationProvider, AbstractAuthenticationToken, LoginUrlAuthenticationEntryPoint, AbstractAuthenticationProcessingFilter, but I wouldn't think it should be causing this issue.
Any ideas would be appreciated. Thanks
For anyone else running in to this problem, I managed to find the issue was related to mod_proxy. I was using it to hide my context path which was in turn causing the servlet to have trouble reading the session cookie for my app. For unauthenticated pages this would mean it created a whole new session each time. For authenticated pages, it would create a new session as well, but thanks to our SSO authentication mechanism it would actually re-authenticate with each request. Probably good we caught it as that's an expensive operation. The answer was really to set the cookie path (setCookiePath) to root ('/') in our tomcat configuration. Hope that helps someone else :)

Categories