6 Replies Latest reply on Apr 3, 2012 11:22 PM by hurzeler

    Seam, SAML, JAAS: How do I map IDP to an application user?

    jonananas
      I've been trying to add SAML authentication to my seam application.
      I wan't to add SAML authentication to regular username,password authentication in one seam application.
      I would also like to "map" the SAML-token to a user in the application userstore.
      The application is a bit different from seam-sp in that we use JAAS instead of authenticator, from components.xml:
         <security:identity remember-me="true" jaas-config-name="appSecurityPolicy" />
      I'm having trouble understanding how to do this. My first approach was "hardcoding" the mapping in my own InternalAuthenticator:
      @Name("fsokAuthenticator")
      @Scope(ScopeType.STATELESS)
      @Stateless
      @Local(FsokInternalAuthenticatorLocal.class)
      public class FsokInternalAuthenticator implements FsokInternalAuthenticatorLocal {
      @In
      Identity identity;
      @Logger
      Log log;
      public boolean internalAuthenticate(Principal principal, List<String> roles) {
      if (principal instanceof SamlPrincipal) {
      SamlPrincipal samlPrincipal = (SamlPrincipal) principal;
      log.debug("Identity provider: " + samlPrincipal.getIdentityProvider().getEntityId());
      // Should only continue if IDP matches the one I trust...
      Credentials credentials = identity.getCredentials();
      credentials.setUsername("username");
      credentials.setPassword("password");
      try {
      String login = identity.login();
      log.debug("After identity.login(): " + login);
      } catch (Exception ex) {
      log.debug("During identity.login(): ", ex);
      return false;
      }
      return true;
      }
      return false;
      }
      }
      However I get an exception during login, but the user gets logged in even though I return false!
      This is weird, but never mind for now...
      My question is this: I can't help thinking that I'm doing this all wrong. I should for example be able to use a LoginModule to do the mapping between IDP and username,
      I can't really figure out how though (my understanding is that JAAS get invoked from Identity.login()?. Can you help?
      Kind regards Jonas

      I've been trying to add SAML authentication to my seam application.

      I wan't to add SAML authentication to regular username,password authentication in one seam application.

      I would also like to "map" the SAML-token to a user in the application userstore.

       

      The application is a bit different from seam-sp in that we use JAAS instead of authenticator, from components.xml:

       

       

      {code:xml}

      <security:identity remember-me="true" jaas-config-name="appSecurityPolicy" />

      {code:xml}


      I'm having trouble understanding how to do this. My first approach was "hardcoding" the mapping in my own InternalAuthenticator:

       

      from external-authentication-config.xml:

      {code:xml}

      <ServiceProvider protocol="http" hostname="localhost" internalAuthenticationMethod="#{fsokAuthenticator.internalAuthenticate}"...

      {code:xml}

       

      {code}

      @Name("fsokAuthenticator")

      @Scope(ScopeType.STATELESS)

      @Stateless

      @Local(FsokInternalAuthenticatorLocal.class)

      public class FsokInternalAuthenticator implements FsokInternalAuthenticatorLocal {

       

      @In

      Identity identity;

       

      @Logger

      Log log;

       

      public boolean internalAuthenticate(Principal principal, List<String> roles) {

      if (principal instanceof SamlPrincipal) {

      SamlPrincipal samlPrincipal = (SamlPrincipal) principal;

      log.debug("Identity provider: " + samlPrincipal.getIdentityProvider().getEntityId());

      // Should only continue if IDP matches the one I trust...

       

      Credentials credentials = identity.getCredentials();

      credentials.setUsername("username");

      credentials.setPassword("password");

      try {

      String login = identity.login();

      log.debug("After identity.login(): " + login);

      } catch (Exception ex) {

      log.debug("During identity.login(): ", ex);

      return false;

      }

      return true;

      }

      return false;

      }

      }

      {code}

       

      However I get an exception during login, but the user gets logged in when I return false, and not logged in when I return true!

      That's weird, but never mind for now...

       

      My question is this: I can't help thinking that I'm doing this all wrong. I should for example be able to stack another LoginModule to take care of the mapping between IDP and username,

      I can't really figure out how though (my understanding is that JAAS get invoked from Identity.login()?. Can you help?

       

      Kind regards Jonas

        • 1. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
          marcelkolsteren

          Interesting use case. Before we dive into the technical implementation, I'd like to ensure that I understand your intention. You're talking about combining local authentication of users (using their username and password) with external authentication (using a SAML IDP). And you'd like to link the external identity and internal identity of a user, right?

           

          Some questions that need to be answered first:

           

          1. How many IDP's do you use? Only one or multiple?
          2. What will be the flow for linking internal to external accounts? Maybe it's like this. The user first logs in locally, just in the traditional way. Then she has a menu option to link her account to an external account. When activating this option, she is redirected to the IDP. She enters here credentials there, and is redirected back to the application. The application then saves her external identity (SAML NameID) to her local account, so that next time she'll be able to login with her external account directly. Is it like this? Or something else?
          3. Should it be possible for users that have never logged in locally, to login directly with their external account?
          4. If the answer to (3) is yes, is there a requirement that an administrator creates a local account for them first? That is, an account that doesn't contain local credentials (username/password) but only a link to an external SAML NameID?
          5. Where do you store the local user information? In LDAP? In a database?
          6. Should a user with a linked account have two ways to login? So should she have the choice of logging in either locally or with her external account?
          • 2. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
            jonananas

            Basically we have an application with several "regular" users that use username/password for login. We also have some organisations that want to provide their "employees" with SSO to our application. They want to do this using SAML.

            We do not want to know about their users, we basically just want to map their SAML-token to one of our "internal" users. So you assume correctly .

             

            Answers:

            1. Multiple, one per organisation.

            2. The flow, preferably would be: The user is logged in at the remote organisation. When accessing our application they are redirected to the remote IDP and then back to our application (since they are already logged in at the remote IDP). The application then checks the organisation (I'm thinking IDP-name) and maps that to a local account/username. In addition to this we might want to map a few attributes from the SAML token to local session variables.

            3. Yes.

            4. Not mandatory, but it would be nice

            5. Database, using JPA.

            6. No.

             

            We do not have our own IDP today, but I've been playing around with OpenSSO in order to try out two IDP:s (SSOCircle being the other one). I assume that the problem can be solved satisfactory inside our application, but I'm not sure how to get JAAS into the equation without using identity.login().

             

            Thanks for your reply!

            • 3. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
              marcelkolsteren

              Thanks for the clear answer. This interlude on the more functional and high-level aspects of your environment really helped me better understand your intentions. And it also provides an interesting real-world use case for SAML.

               

              So now on to the technical stuff. First I want to give you some insight about what's under the hood of the PicketLink Seam module, and how it interacts with the standard security mechanism in Seam.

               

              The standard security mechanism

              A central component in Seam security is the identity component. That's a session scoped component that stores the logged in principal (or null of no-one is logged in). You can use the identity.login() method for logging in someone based on local credentials. Before calling identity.login() you need to set the local credentials by calling credentials.setUsername() and credentials.setPassword(). The identity.login() method delegates the verification of the credentials (i.e. the authentication of the user) to an authenticate method that is supplied by the application, or to a JAAS login module. After successful authentication, the identity component will contain the principal that describes the currently logged in user. You can fetch it with identity.getPrincipal().

               

              What the PicketLink Seam module does

              Seam's identity component provides one method that can be used to poke in a principal that has been authenticated externally. It's called identity.acceptExternallyAuthenticatedPrincipal(Principal principal). When you want to delegate the user authentication to an external SAML identity provider, you'd call externalAuthenticator.samlSignOn(). The externalAuthenticator component, which is part of the PicketLink Seam module, will use SAML to verify the user, and then, when the user is back at the application's site, it will just poke the principal into the identity component in the way that I just described. But just before doing so, it will call the application-provided internal authenticate method, passing the (SAML-based) principal as a parameter, and passing an empty role list. In the internal authentication method, the application developer can verify that the user has an internal account as well, or maybe even create this internal account if it doesn't exist yet. In addition, some roles can be added to the list, based on the SAML properties of the principal, and optionally based on the local account that matches the external account.

               

              This background information probably makes you feel comfortable, because it shows that you were on the right track, implementing your own internal authenticate method. The problem is that you try to authenticate the user locally at that point, which is not the way to go, because externally authenticated users don't have local credentials in the environment that you described. So I would suggest a slightly different solution. If you don't require local accounts to be pre-registered by administrators (see question 4), I would do it like this. In your internalAuthenticate method, use JPA to find a user that is linked to the combination of the name of the principal (principal.getName()) and the IDP that verified it (principal.getIdentityProvider().getEntityId()). And if you don't find it, you can create it. You can just inject the entityManager into your component so that you can do the lookups and insertions. But you should add a @Transactional annotation to the internalAuthenticate method in order to be able to access the database. Your internalAuthenticate method will probably always return true, because there is no reason why a principal, that has already authenticated externally, would not be able to login. Maybe you would return false if the local user has a blocked flag which turns out to be false, but I don't know if you have such functionality. If you need to have some local session variables based on the SAML attributes, you could use the internalAuthenticate method to populate them.

               

              You'll use JAAS only for the local authentication flow, which just uses the regular Seam security mechanism, and which is separate from the external authentication flow. In your login page, you'll probably have two ways of logging in: logging in locally (which is backed by a call to identity.login()) and logging in with an external IDP (which is backed by a call to externalAuthenticator.samlLogin()). A locally authenticated user will have a session with identity.getPrincipal() containing the JAAS-supplied principal, and an externally authenticated user will have a session with identity.getPrincipal() containing a SeamSamlPrincipal object.

               

              Your answer to question 2, about the external login flow, implicitly asks for a feature called "identity provider discovery". You're the first one asking for this feature, and you might have already noticed that all current examples require the user to choose her IDP. Automatic discovery would mean that the service provider finds out what identity provider the user is currently using. Interesting use case. Maybe SAMLv2 has support for it. Please start a new thread for identity provider discovery, because this thread is more about linking external and internal accounts.

              • 4. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
                hurzeler

                This is a rather interesting post. Thank you for all the information.

                 

                I just have an additional questions though. Does the externalAuthenticator for SAML exist in Seam 3? I looked through the documentation and the seam-security source as well as the seam-security-external source and can't find it.

                Could you perhaps enlighten us how to set-up SAML authentication in Seam 3 where the user gets authenticated externally by an idp (ours) then calls the internal authenticator to set roles and groups. The Seam 3 Security documentation is not helpful here.

                 

                Your help is greatly appreciated.

                • 5. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
                  tonykaska
                  • 6. Re: Seam, SAML, JAAS: How do I map IDP to an application user?
                    hurzeler

                    Unfortunatelly that only explains how to deal with OpenId not SAML IDP.