Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Added basic external authentication with a Keycloak Default handler #1447

Closed
wants to merge 1 commit into from

Conversation

mattydebie
Copy link
Contributor

WIP for getting external authentication in flowable. With the code as it is, it's possible to register/login with OAUTH (KeyCloak for example, but I also tested with another OAuth from a client).

remark: SAML should also be possible

Short description of what happened:

I've added an SSOHandler interface which you can inject yourself to add your custom logic/sso. This SsoHandler should provide the correct external url to the sso login page. When everything works out the SSOHandler has to return a SSOUserInfo object.
This userinfo is used to decide wether to Login the user or to register a new account for that user.
When:

  • registering: the user gets a random password (UUID4) and possibly gets a tenant and privileges. If not, the user has to wait for an admin to grant him/her some privileges
  • logging in: the normal login flow is followed.

Comments and remarks are super welcome, as this can be improved A LOT.

What it doesn't do (yet)

  • When revoking some privileges in you OAuth, flowable will not update these yet.
  • Right now the way to get to the OAuth login page is through a URL beneath the login form. In the future it should be possible to redirect to that URL directly (so now regular accounts can be used)

Signed-off-by: Matthias De Bie <mattydebie@gmail.com>
@filiphr
Copy link
Contributor

filiphr commented Dec 20, 2018

Thanks for the PR @mattydebie. Just to let you know that we haven't forgotten the PR and that we are going to review it asap. Same for your 2nd open PR.

@mattydebie
Copy link
Contributor Author

Hey @filiphr , thanks for the update. Really appreciate you guys taking the time!
Don't hold back on the feedback, I want to make this a stable, correct way for using SSO. So I'm willing to take some time to fix the remaining remarks/issues for sure.

Copy link
Contributor

@filiphr filiphr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @mattydebie for working on an external authentication within the Flowable UI applications. Sorry for the delay, but the holidays got in between 😉. I went through the PR and added some comments.

As you know the UI applications are Spring Boot applications which means that they are using Spring Security for the security. Have you had a look at the Keycloak Spring Boot Adapter which uses the Keycloak Spring Security Adapter. There are some other resources out there. I personally haven't tried it out, but I've seen a presentation about it on a conference.

Maybe if we go through that route, using Spring Boot and Spring Security authentication, we would be able to be more flexible with other implementations, as in the end we delegate this to the 3rd party providers. I think in such case it is not even needed to use our privileges and users as the 3rd party provider manages that. The only thing that would need to be done is to make sure that the 3rd party provider has the needed privileges. I think that in such case all other apps need to go through the 3rd party provider and not through IDM, as we are going to have an SSO over an SSO (IDM is a kind of an SSO as well) 😄.

When we bring this to the Flowable UI application this is going to be something extremely powerful 😄

protected PrivilegeService privilegeService;

@Autowired
protected SSOHandler ssoHandler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Flowable UI IDM Application is a Spring Boot application so we can make this an optional bean. Then we won't need the isActiveMethod (I think)

@RequestMapping(value = "/sso/external", method = RequestMethod.GET)
public void getSsoExternalUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {

String urlBase = request.getRequestURL().toString().replace("/sso/external", "/sso/return");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite follow what this method should be doing?

Also what should this method do, what is the return type? From what I can understand we just set a plain text with the external URL from the SSOHandler. Why not just have a return method that would return that if the SSOHandler is there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method indeed returns the url to the SSO in a response. The login page has to know where to redirect to and this was the only way I found to work.
If the frontend could get the url by just using a plain method, then that would indeed be a better approach.

}

@RequestMapping(value = "/sso/return", method = RequestMethod.GET)
public void onSsoReturnGet(HttpServletRequest request, HttpServletResponse response) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a GET and a POST is this some keycloak requirement, or we are making it flexible for different SSOs? From where are this app/sso/return methods invoked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed to provide a flexible implementation. Some SSO's return with a POST, some with a GET.
app/sso/return is the redirectUri that you pass to the SSO; when you successfully log in the SSO returns to this redirectUri


} catch (NotFoundException e) {
// userInfo not found: create new account
createSsoAccount(request, response, ssoUserInfo);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need put this creation of an account under some flag, otherwise this might blow up the system if users just start creating accounts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow your here. This catch block only gets triggered when the SSO says OK - valid user but flowable IDM does not recognize it yet.

createSsoAccount(request, response, ssoUserInfo);

} catch (IOException e) {
e.printStackTrace();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a proper logging here?

private static Logger LOGGER = LoggerFactory.getLogger(IdmSSOResource.class);

private void loginWithSso(HttpServletRequest request, HttpServletResponse response, User user) throws IOException {
String redirectTo = request.getRequestURL().toString().replace("/app/sso/return", "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't understand what this redirection should do? Why is the redirectUrl coming from a cookie? Why do we need to do the replace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you get redirected to the login page, the page saves the query parameter redirectUri (the one that tells the idm where to go after successful login). Because you would lose this parameter when going to the SSO, I saved it in the cookies. This was again the only way I found to work to save the redirectPath.

The replace was done to get the baseURL. <fullurl>/app/sso/return.replace("/app/sso/return", "") = the full url.
This is indeed a dirty hack, if you know how to get it properly, please let me know.

try {
loginWithSso(request, response, userService.getUserInformation(userInfo.getId()).getUser());
} catch (IOException e) {
e.printStackTrace();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a proper logging here?

userInfo.getFirstName(),
userInfo.getLastName(),
userInfo.getEmail(),
UUID.randomUUID().toString()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to create a password in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The password is created so that no-one can just enter a username without password and get logged in. But the created user also gets forced to login through the SSO this way.

}
} else {

response.setContentType(MediaType.TEXT_HTML_VALUE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return some other status here or maybe a body whether the login was successful or not and then display this appropriately within the FE instead of hardcoding the HTML here?

Copy link
Contributor Author

@mattydebie mattydebie Jan 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoding the html was indeed a test case. Will change this!

[edit]: fixed typo

* @author mattydebie
*/
@Component
public class KeyCloakSSOHandler implements SSOHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this the Keycloak is a mandatory SSO. Shouldn't we make this optional through an opt in?

Something else, this class is not thread safe. Calling getExternalUrl sets some state which is then used within the handleSsoReturn

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be optional, I thought is was. Per default there should be a SSOHandler provided that returns as inactive. Will take a look into the OptionalBeans though.

getExternalUrl only merges some config, so it normally never changes.

@mattydebie
Copy link
Contributor Author

No worries about the delay, being with your family during the holidays is more important!

Have you had a look at the Keycloak Spring Boot Adapter which uses the Keycloak Spring Security adapter.

I have to admit that I did not look into the Keycloak Spring Boot Adapter, did not stumble upon it as KeyCloak was not the intended target for us. Just an easy example to test this on.

I think that in such case all other apps need to go through the 3rd party provider and not through IDM, as we are going to have an SSO over an SSO (IDM is a kind of an SSO as well).

In this PR you indeed only use the external SSO at login, to get basic information and to see if the user has permission to log in. All following requests needed for the Flowable UI apps go through the regular IDM (to not break the workings of flowable). I admit, one SSO would have been better. I will have to check this out.

In the mean time, I'll reply to your comments.

@mattydebie
Copy link
Contributor Author

Just to make things clear, is it useful for me to keep working on this PR and apply the suggested changes or are you more inclined to deny this to check out the spring security integration?

@mattydebie
Copy link
Contributor Author

Hi @filiphr
were you able to look at this? I know it have been busy times, but an answer to the above question would help me out a lot.

Thanks in advance
Matthias

@filiphr
Copy link
Contributor

filiphr commented Jan 21, 2019

Hey @mattydebie,

Sorry for the delay in replies.

Just to make things clear, is it useful for me to keep working on this PR and apply the suggested changes or are you more inclined to deny this to check out the spring security integration?

A contribution is always useful 😉. Are you not interested in the Spring Security approach?
If you ask me I think that the Spring Security Integration approach is a bit more flexible approach that would allow people to more easily add 3rd party integrations (not sure if you need to use KeyCloak or something else). Using SAML would also be a bit easier to implement (in my opinion).

The Flowable UI Apps rely mostly on the Spring Security integration, there might be something specific, but we can easily change that.

@mattydebie
Copy link
Contributor Author

Ofcourse Spring security would be nice too. It would just mean I'll have to start over again 😛 ..
IMO the nice thing about this approach is the fact that everything already works with the Flowable-IDM. So adding user data from an external SSO can go as far as you want. (only authentication , including authorization, ..)

I'll try and check the spring security again as well. But I think I ran into some kind of trouble when looking at it the first time.. 🤔

@VeridianDynamics
Copy link
Contributor

Hi, my name is Jan and I'm Matty's colleague. Since he will not have time for this anymore, I was asked to take over the task of making SSO work with Flowable. So how can we move forward from where we are? It seems that you prefer a Spring Security solution and from a bit of experimentation I did with Spring Security, it appears that this approach should be feasible. I am always in favour of a clean solution, although it may require some 'babysitting' from you or the other Flowable maintainers because my experience with Flowable architecture, Spring and Spring Security is very limited.

I could use some high-level help figuring out what would need to be done. So far I think the following actions are necessary:

  1. Add a configuration page which will allow the user to set clientId, clientSecret, accessTokenUri, userAuthorizationUri and userInfoUri for OAuth 2.0 configuration
  2. Add a dependency on spring-security-oauth2-autoconfigure package to the relevant POMs
  3. Add OAuth2SecurityConfigurationAdapter to the relevant SecurityConfiguration.java files
  4. Disable IDM (Or should we allow a hybrid approach where both SSO and IDM can be used? How? Also, there should be an option to revert accidental misconfiguration of SSO to prevent lockouts.)
  5. ?

Since maintaining a long-lived fork is rarely a good idea, we would like to have this feature properly integrated in Flowable. Can you let me know which path to take to make this happen?

@tijsrademakers
Copy link
Contributor

Hi Jan,

Exactly, a Spring Security focused solution definitely has our preference. For a first implementation we can use properties (from the application.properties) file, instead of having a UI for this (this could be a second version). If IDM is disable then you need to ensure that an authenticated user has the correct groups as well. In the IDM application you can define which groups a user is part of, so how will that part be replaced with the SSO feature?

Thanks,

Tijs

@VeridianDynamics
Copy link
Contributor

VeridianDynamics commented Mar 7, 2019

Hi @tijsrademakers ,
I'm not sure, is the group knowledge even necessary for Flowable? I'm not sure how exactly they are used and will have to do further reading of Flowable docs to find out. As I said, the whole SSO landscape is quite new to me, so take this with a large grain of salt, but I assume it would work something like this:
User groups and permissions will be configured in the OAuth provider itself and Flowable, when configured with the right settings, would perform the standard OAuth authentication flow.

Do you see any issues with this approach? I believe this problem has been solved many times in all sorts of software, so unless Flowable uses groups in some kind of non-standard way, there should be no problem with this.

Thanks,
Jan

@VeridianDynamics
Copy link
Contributor

@tijsrademakers
Hi Tijs, I checked out the groups in Flowable and it appears that they are only used as a tool to assign permissions for multiple users at once. If this is correct, it means that the groups are not even used as a concept outside of IDM, suggesting that I would not have to handle them in any way, because that would be handled in the OAuth 2.0 provider of choice.

@VeridianDynamics
Copy link
Contributor

@tijsrademakers @filiphr
Hi, any comments please?

@tstephen
Copy link
Contributor

@VeridianDynamics I think you are looking at the IDM groups for permissions (i.e. to control access different web applications). In addition, and this is the original use, groups define who user tasks should be directed to. So unless you have no user tasks you will need groups for this purpose at least.

Given you're using a Keycloak implementation you can leverage its Authorization functionality to give you a set of entitlements for the user. There's a lot to grok but it's described here: https://www.keycloak.org/docs/latest/authorization_services/index.html

@dereisele
Copy link

Is there any progress on this feature? I could help out if there is any work to do.

@tomvda
Copy link

tomvda commented Nov 25, 2019

@dereisele, we've tried to get in sync on this topic with the core flowable members, but decided to postpone this issue until after the 6.5 release and see then on how to continue on this topic. We'll forst merge everything in the upcoming 6.5 release and will pick this up again afterwards.

We (inuits/flowable-engine) have a working keycloak and wso2 idm integration based on OIDC, but there are still several issues that should be covered before we get this PR accepted. There are different solutions for those issues dough, so this would require feedback from the core team to continue on the approved solution.

@flowable flowable deleted a comment from Robinyo Dec 25, 2019
@hristo-dl
Copy link

Hi,

@tijsrademakers, @VeridianDynamics, @filiphr , @tstephen , since 6.5 is out now, is there any plan to get this finished?

Regards,
Hristo

@srinathganesh1
Copy link

Is this how to create tenant? is there a way to make tenant optional?

Screenshot from 2020-03-31 21-44-26

@tomvda
Copy link

tomvda commented Apr 6, 2020

All,

Sorry for the delay, but as promised we did pickup the Keycloak OIDC authentication again for 6.5.0. Functionally everything was ok immediately in 6.5.0, but it took some while to clean everything up.

Since there are quite some changes since we initiated this PR, we decided to put everything together in another branch for now which starts from the current flowable/flowable-engine master. We'd like to read your comments about it and whether or not to continue in another PR?

See oidc-auth branch of inuits/flowable-engine

Changes/improvements compared to this PR:

  • Starting from current master
  • Cleanup with more configurable properties and better error handling (if certain claims are not available for ex.)
  • Other OIDC handlers can be implemented in custom libraries like we did, extending this ones

The OIDC implementation uses an OpenID Connect implementation on Spring Security. This was previously also already the case.

Additional properties in application.properties:

  • flowable.sso.provider=keycloak -> enable using the KeycloakSsoHandler
  • flowable.sso.keycloak.url.auth -> authorization endpoint
  • flowable.sso.keycloak.url.token -> token endpoint
  • flowable.sso.keycloak.url.info -> user info endpoint
  • flowable.sso.keycloak.cient-id
  • flowable.sso.keycloak.cient-secret
  • flowable.sso.keycloak.scope -> the oauth scope needed to receive all necessary claims about a user, no scope will be requested if this prperty is missing
  • flowable.sso.keycloak.userinfo.tenant -> the tenant attribute or claim for the user coming from Keycloak
  • flowable.sso.keycloak.userinfo.client_roles -> the privileges claim for the user coming from Keycloak

In your realm on Keycloak you should configure an additional client using following settings

  • access type -> confidential
  • standard flow enabled
  • implicit flow disabled
  • enter the valid redirect url -> the path for the KeycloakSsoHandler is (ex. https://your.flowable.local/flowable-idm/sso/return)
  • add client roles which can be assigned to users
    • access-idm
    • access-admin
    • access-modeler
    • access-task
    • access-rest
  • add extra mappers
    • tenant -> an optional claim of JSON type string, available in userinfo and access token
    • privileges -> an optional multi-valued claim of JSON type string, containing the assigned client roles listed above, also avilable in userinfo and access token

The KeycloakSsoHandler will register new users in flowable-idm if they don't exist yet and update existing ones. Currently the handler gets all info from the userinfo endpoint, also tenant and privileges, but this should also be read from the JWT access token if available there; we'll improve this soon.

Tenant and privileges claims are optional now, but:

  • if no privileges, you'll need to set the privileges manually for each user or they wont have any access
  • if no tenant, the user will be able to access all tenants

We have also a branch ready with support for multiple tenants on a single user, but this has much more impact on existing code although it works nice.
It also falls back to the current implementation if not enabled in properties, but if enabled an extra tab will be added to flowable-idm which allows you to manage your tenants and map them to users. Then also a multi-valued claim from oidc will be supported (not ready yet) to map multiple tenants to a single user via Keycloak. Switching between tenants happens with an extra session cookie which can be set via an extra component (dropdown) in the existing UI's or an additional application in front of the UI's. The backends of the UI's will validate if a user is allowed to use a tenant before adding the tenant as a header to the flowable-rest requests.

See tenant-mapping branch of inuits/flowable-engine

Please give your advice whether to continue in this PR or create a new one

@tijsrademakers, @hristo-dl, @srinathganesh1

@tijsrademakers
Copy link
Contributor

@tomvda I think a new PR would be the cleanest approach, thanks.

@srinathganesh1
Copy link

All,

Sorry for the delay, but as promised we did pickup the Keycloak OIDC authentication again for 6.5.0. Functionally everything was ok immediately in 6.5.0, but it took some while to clean everything up.

Since there are quite some changes since we initiated this PR, we decided to put everything together in another branch for now which starts from the current flowable/flowable-engine master. We'd like to read your comments about it and whether or not to continue in another PR?

See oidc-auth branch of inuits/flowable-engine

Changes/improvements compared to this PR:

  • Starting from current master
  • Cleanup with more configurable properties and better error handling (if certain claims are not available for ex.)
  • Other OIDC handlers can be implemented in custom libraries like we did, extending this ones

The OIDC implementation uses an OpenID Connect implementation on Spring Security. This was previously also already the case.

Additional properties in application.properties:

  • flowable.sso.provider=keycloak -> enable using the KeycloakSsoHandler
  • flowable.sso.keycloak.url.auth -> authorization endpoint
  • flowable.sso.keycloak.url.token -> token endpoint
  • flowable.sso.keycloak.url.info -> user info endpoint
  • flowable.sso.keycloak.cient-id
  • flowable.sso.keycloak.cient-secret
  • flowable.sso.keycloak.scope -> the oauth scope needed to receive all necessary claims about a user, no scope will be requested if this prperty is missing
  • flowable.sso.keycloak.userinfo.tenant -> the tenant attribute or claim for the user coming from Keycloak
  • flowable.sso.keycloak.userinfo.client_roles -> the privileges claim for the user coming from Keycloak

In your realm on Keycloak you should configure an additional client using following settings

  • access type -> confidential

  • standard flow enabled

  • implicit flow disabled

  • enter the valid redirect url -> the path for the KeycloakSsoHandler is (ex. https://your.flowable.local/flowable-idm/sso/return)

  • add client roles which can be assigned to users

    • access-idm
    • access-admin
    • access-modeler
    • access-task
    • access-rest
  • add extra mappers

    • tenant -> an optional claim of JSON type string, available in userinfo and access token
    • privileges -> an optional multi-valued claim of JSON type string, containing the assigned client roles listed above, also avilable in userinfo and access token

The KeycloakSsoHandler will register new users in flowable-idm if they don't exist yet and update existing ones. Currently the handler gets all info from the userinfo endpoint, also tenant and privileges, but this should also be read from the JWT access token if available there; we'll improve this soon.

Tenant and privileges claims are optional now, but:

  • if no privileges, you'll need to set the privileges manually for each user or they wont have any access
  • if no tenant, the user will be able to access all tenants

We have also a branch ready with support for multiple tenants on a single user, but this has much more impact on existing code although it works nice.
It also falls back to the current implementation if not enabled in properties, but if enabled an extra tab will be added to flowable-idm which allows you to manage your tenants and map them to users. Then also a multi-valued claim from oidc will be supported (not ready yet) to map multiple tenants to a single user via Keycloak. Switching between tenants happens with an extra session cookie which can be set via an extra component (dropdown) in the existing UI's or an additional application in front of the UI's. The backends of the UI's will validate if a user is allowed to use a tenant before adding the tenant as a header to the flowable-rest requests.

See tenant-mapping branch of inuits/flowable-engine

Please give your advice whether to continue in this PR or create a new one

@tijsrademakers, @hristo-dl, @srinathganesh1

thanks will look at it

@BozhidarKuzmanov
Copy link

Hello,

Sorry if I am pushing, but my company is very interested in this feature.
Do you have any timeline for it or any realistic date on mind? I understand that you cannot commit to specific schedule and I am not asking you to.

Regards

@srinathganesh1
Copy link

Hello,

Sorry if I am pushing, but my company is very interested in this feature.
Do you have any timeline for it or any realistic date on mind? I understand that you cannot commit to specific schedule and I am not asking you to.

Regards

I did a check-out of the previous commit and was working using the source code. maybe you could do similar till they review, merge, release this

@BozhidarKuzmanov
Copy link

Hello,
Sorry if I am pushing, but my company is very interested in this feature.
Do you have any timeline for it or any realistic date on mind? I understand that you cannot commit to specific schedule and I am not asking you to.
Regards

I did a check-out of the previous commit and was working using the source code. maybe you could do similar till they review, merge, release this

Hi,
Already doing that.
I just have some concerns that we might end up with additional work to adopt any further changes.

@srinathganesh1
Copy link

I just have some concerns that we might end up with additional work to adopt any further changes.
Thats always a head-ache. But this thread has first comment dated 11 Dec 2018

You could try this too premium-minds/flowable-keycloak#1 (comment)

@tomvda tomvda mentioned this pull request Apr 16, 2020
@tomvda
Copy link

tomvda commented Apr 16, 2020

This PR should be closed now and will continue in PR #2281 as advised above

@tijsrademakers
Copy link
Contributor

Thanks for creating the new PR, we will review it and provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants