1 2 Previous Next 18 Replies Latest reply on Aug 3, 2007 9:22 AM by yonia

    external authentication-any pointers for a beginner?

    mwkohout

      Hello all-

      I'm charged with developing an application that authenticates users using an external, already existing web-based sso authentication application and (being new to Seam) I'm not quite sure how to proceed.

      The way I *envision* my app working is that before every page render, an authentication method ( pointed to in components.xml) checks for the presence of a cookie(this represents an active user session). If the cookie isn't there (or is invalid), the authentication method redirects to the external web app (where the user provides their u/p). After the the user authenticates against the external sso system, that system redirects back to my app. Upon redirection, the authentication method is called again and this time passes and outjects an application "user" object.

      Is this a sound way to accomplish my goal? And how do I trigger this authentication method before every page render/page request?

        • 1. Re: external authentication-any pointers for a beginner?
          mwkohout

          also, I'm using Seam 2 beta.

          • 2. Re: external authentication-any pointers for a beginner?
            shane.bryzak

            Thankfully, the servlet spec provides us with session management for free so that we don't need to check each request for a session cookie ourselves. The best thing I can suggest is to read the security chapter of the Seam reference documentation to become familiar with how security works in Seam.

            • 3. Re: external authentication-any pointers for a beginner?
              mwkohout

              After reviewing the seam security documentation, I've written some code:

              1) A JAAS Module:

              public class CustomLoginModule extends SeamLoginModule {
              
               private static final LogProvider log = Logging.getLogProvider(SeamLoginModule.class);
              
               public CustomLoginModule() {
               }
              
               @Override
               public boolean login() throws LoginException {
              
               boolean isLoggedIn = false;
               javax.faces.context.FacesContext ctx = javax.faces.context.FacesContext.getCurrentInstance();
               javax.servlet.http.Cookie cookie = (javax.servlet.http.Cookie) ctx.getExternalContext().getRequestCookieMap().get("umnAuthV2");
              
               log.debug("in Module. cookie == "+cookie);
               try{
               if (cookie == null) //if we can't find it, redirect them to the auth server. the auth server will redirect them back, using the desturl param
               ctx.getExternalContext().redirect("https://authserver.somewhere?desurl=" + ((javax.servlet.http.HttpServletRequest) ctx.getExternalContext().getRequest()).getRequestURL());
               }
               catch (IOException ex) {
               Logger.getLogger("global").log(Level.SEVERE, null, ex);
               }
              
              
               org.jboss.seam.core.Expressions.MethodExpression mb = org.jboss.seam.security.Identity.instance().getAuthenticateMethod();
               if (mb == null) {
               throw new java.lang.IllegalStateException("No authentication method defined - please define <security:authenticate-method/> for <security:identity/> in components.xml");
               }
              
               try {
               isLoggedIn = (java.lang.Boolean) mb.invoke();
               } catch (java.lang.Exception ex) {
               log.error("Error invoking login method", ex);
               }
               return isLoggedIn;
               }
              }



              And a new security configuration factory(that exposes my JAAS module):
              @Name("org.jboss.seam.security.configurationFactory")
              @BypassInterceptors
              @Scope(ScopeType.STATELESS)
              public class JAASConfigFactory {
              
               @Logger
               private Log log;
               public JAASConfigFactory() {
               }
              
               static final String DEFAULT_JAAS_CONFIG_NAME = "custom";
              
               protected javax.security.auth.login.Configuration createConfiguration()
               {
               return new javax.security.auth.login.Configuration()
               {
               private AppConfigurationEntry[] aces = { createAppConfigurationEntry() };
              
               @Override
               public AppConfigurationEntry[] getAppConfigurationEntry(String name)
               {
               return DEFAULT_JAAS_CONFIG_NAME.equals(name) ? aces : null;
               }
              
               @Override
               public void refresh() {}
              
              
               };
               }
              
               protected AppConfigurationEntry createAppConfigurationEntry()
               {
               log.debug("in JAASConfigFactory..");
               return new AppConfigurationEntry(
               CustomLoginModule.class.getName(),
               LoginModuleControlFlag.REQUIRED,
               new HashMap<String,String>()
               );
               }
              
               @Factory(value="org.jboss.seam.security.configuration", autoCreate=true, scope=APPLICATION)
               public javax.security.auth.login.Configuration getConfiguration()
               {
               return createConfiguration();
               }
              
               public static javax.security.auth.login.Configuration instance()
               {
               if ( !Contexts.isApplicationContextActive() )
               {
               throw new IllegalStateException("No active application scope");
               }
               return (javax.security.auth.login.Configuration) Component.getInstance("org.jboss.seam.security.configuration");
               }
              
              }
              


              I've also altered the security:identity component:
               <security:identity authenticate-method="#{authBean.authenticate}"
               security-rules="#{securityRules}"
               authenticate-every-request="true"
               auto-create="true" jaas-config-name="custom"/>
              


              However, Seam does not seem to be installing my JAAS module(it fails to output any of my logs). Am I missing something?

              Any ideas would be helpful
              Thanks
              Mike Kohout



              • 4. Re: external authentication-any pointers for a beginner?
                shane.bryzak

                Does getConfiguration() get hit when you set a breakpoint there? You may need to extend Identity and override the getLoginContext() method.

                • 5. Re: external authentication-any pointers for a beginner?
                  mwkohout

                  No, it wasn't hit by a breakpoint. I'm going to try your suggestion because it sounds cleaner, but I ended up overriding the getDefaultCallbackHander method of Identity to return my preferred callbackhandler and setting up my login module via jboss confuse-igation files....it's still not working, but now it's failing in my code(I think because of some error in my jboss configs) :-).

                  • 6. Re: external authentication-any pointers for a beginner?
                    mwkohout

                    Thanks for your continuing suggestions and patience, Shane.

                    I reimplemented by overriding getLoginContext() and it's still going boom.

                    here's my JAAS config factory method:

                    @Factory(value="org.jboss.seam.security.configuration", autoCreate=true, scope=APPLICATION)
                     public javax.security.auth.login.Configuration getConfiguration()
                     {
                     log.error("in my getConfiguration()");
                     return new javax.security.auth.login.Configuration()
                     {
                    
                     private AppConfigurationEntry[] aces = { new AppConfigurationEntry(
                     X500LoginModule.class.getName(),
                     LoginModuleControlFlag.REQUIRED,
                     new HashMap<String,String>()
                     ) };
                    
                     @Override
                     public AppConfigurationEntry[] getAppConfigurationEntry(String name)
                     {
                     List<AppConfigurationEntry> entries = new ArrayList<AppConfigurationEntry>();
                     for( AppConfigurationEntry entry : aces)
                     {
                     if( entry.getLoginModuleName().equals(name))
                     entries.add(entry);
                    
                     }
                     return entries.toArray(new AppConfigurationEntry[0]);
                     }
                    
                    
                     public String toString()
                     {
                     return "appConfigurationEntries="+Arrays.asList(aces);
                     }
                    
                    
                     };
                     }
                    


                    And my subclass of identity
                    @Name(value = "org.jboss.seam.security.identity")
                    @Scope(value = SESSION)
                    //@BypassInterceptors
                    @Startup
                    public class X500Identity extends Identity {
                    
                     private static final LogProvider log = Logging.getLogProvider(X500Identity.class);
                    
                     @In(value="org.jboss.seam.security.configuration")
                     Configuration config;
                    
                     @In(value="org.jboss.seam.core.expressions")
                     Expressions expressionFactory;
                    
                     public X500Identity()
                     {
                     setJaasConfigName(X500LoginModule.class.getName());
                     setAuthenticateEveryRequest(true);
                     log.error("in X500Identity constructor. jaas config name = "+this.getJaasConfigName());
                     }
                     @Override
                     protected LoginContext getLoginContext() throws LoginException {
                     log.error("in my getLoginContext()");
                    
                     if (getJaasConfigName() == null) {
                     throw new RuntimeException("In X500Identity. JAAS config name not set. Please set it up.");
                     }
                     if( config == null )
                     throw new RuntimeException("In X500Identity. \"org.jboss.seam.security.configuration\" component not injected. Please set it up.");
                    
                     log.error( "new LoginContext(getJaasConfigName(), getSubject(), getDefaultCallbackHandler(), config)=+new LoginContext("+getJaasConfigName()+","+ getSubject()+","+ getDefaultCallbackHandler()+","+ config+")");
                     return new LoginContext(getJaasConfigName(), getSubject(), getDefaultCallbackHandler(), config);
                     }
                    
                     @Override
                     public CallbackHandler getDefaultCallbackHandler() {
                     log.error("in my getDefaultCallbackHandler()");
                     return new CookieCallbackHandler();
                     }
                    
                    
                    
                     @Override
                     public void checkRestriction(String expr) {
                     log.error("in my checkRestriction(String expr) expr=" + expr);
                     if (!evaluateExpression(expr)) {
                     if (!isLoggedIn()) {
                     this.login();
                     } else {
                     Events.instance().raiseEvent("org.jboss.seam.notAuthorized");
                     throw new AuthorizationException(String.format("Authorization check failed for expression [%s]", expr));
                     }
                     }
                     }
                    
                     @Override
                     public boolean isLoggedIn(boolean attemptLogin) {
                     log.error("in my isLoggedIn(boolean attemptLogin) attemptLogin = " + attemptLogin);
                     boolean isLoggedIn = super.isLoggedIn(attemptLogin);
                     log.error("exiting isLoggedIn(boolean attemptLogin). isLoggedIn = " + isLoggedIn);
                     return isLoggedIn;
                     }
                    
                     @Override
                     public Expressions.MethodExpression getAuthenticateMethod() {
                     log.error("in my getAuthenticateMethod():"+expressionFactory.createMethodExpression("#{authBean.authenticate()}"));
                     return expressionFactory.createMethodExpression("#{authBean.authenticate()}");
                     }
                    
                    
                     public static Identity instance() {
                     if (!Contexts.isSessionContextActive()) {
                     throw new IllegalStateException("No active session context");
                     }
                    
                     Identity instance = (Identity) Component.getInstance(X500Identity.class, ScopeType.SESSION);
                    
                     if (instance == null) {
                     throw new IllegalStateException("No Identity could be created");
                     }
                    
                     return instance;
                     }
                    }
                    


                    For some reason, it doesn't appear to be hitting my authenticateMethod anymore....ideas?



                    • 7. Re: external authentication-any pointers for a beginner?
                      smokingapipe

                      This is a very interesting thread, I will be following closely.

                      • 8. Re: external authentication-any pointers for a beginner?
                        mwkohout

                        I think core to my problem is that I'm not really understanding how Seam 2's default login module is deployed.

                        I've looked and found several jboss service xml deployment files but (perhaps due to my limited experience with those files) it doesn't seem that it's how the seam login module is activated.

                        Could someone with more insight or ability confirm this? And if so talk me through it?

                        thanks
                        Mike

                        • 9. Re: external authentication-any pointers for a beginner?
                          shane.bryzak

                          This is getting too complex. Try simply overriding the configuration factory class like this:

                          @Name("org.jboss.seam.security.configurationFactory")
                          @BypassInterceptors
                          @Scope(ScopeType.STATELESS)
                          @Install(precedence = DEPLOYMENT)
                          public class MyConfigFactory extends Configuration
                          {
                           protected AppConfigurationEntry createAppConfigurationEntry()
                           {
                           return new AppConfigurationEntry(
                           CustomLoginModule.class.getName(),
                           LoginModuleControlFlag.REQUIRED,
                           new HashMap<String,String>()
                           );
                           }
                          }


                          Unfortunately you need to set the install precedence to DEPLOYMENT because the configuration factory in Seam defaults to APPLICATION (I've fixed this in CVS now, so if you're using latest CVS you don't need the @Install line).


                          • 10. Re: external authentication-any pointers for a beginner?
                            mwkohout

                            After updating to the head of cvs, I'm able to get this stuff to work.

                            For the most part.

                            But, one problem remains-on the first view of a protected resource(like wildcarded restriction below), the user is not being forced to authenticate:-). On the second request, when the jsessionid cookie of the server is set, authentication occurs and the correct things seem to happen.

                            <page view-id="*">
                             <restrict>#{identity.isLoggedIn(true)}</restrict>
                             <navigation>
                             <rule if-outcome="home">
                             <redirect view-id="/home.xhtml"/>
                             </rule>
                             </navigation>
                             </page>
                            


                            if my description is vague, here's a list of actions and their results.
                            1)A user makes a request to the server(let's say it's http://localhost/JAASTest). The user doesn't have a jsessionid cookie.
                            2)the server, upon reciept of the user's request, creates a jesssionid cookie and sends it back on the response. The server also renders the protected resource and returns that back to the user.
                            *****at this step, the user should have been forced to authenticate****
                            3)the user then makes another request to the protected resource.
                            4)The server then forces authentication and good things seem to happen.

                            I'm thinking my error is occurring in my custom Identity class-maybe I'm missing a critial annotation or I'm misunderstanding about when Seam starts a session or something. Does anyone see what I'm doing wrong?
                            import static org.jboss.seam.ScopeType.SESSION;
                            import edu.umn.ictr.mentor.action.CookieCallbackHandler;
                            import edu.umn.ictr.mentor.action.X500LoginModule;
                            import javax.security.auth.callback.CallbackHandler;
                            import javax.security.auth.login.Configuration;
                            import javax.security.auth.login.LoginContext;
                            import javax.security.auth.login.LoginException;
                            import javax.servlet.http.Cookie;
                            import org.jboss.seam.Component;
                            import org.jboss.seam.ScopeType;
                            import org.jboss.seam.annotations.In;
                            import org.jboss.seam.annotations.Name;
                            import org.jboss.seam.annotations.Scope;
                            import org.jboss.seam.annotations.Startup;
                            import org.jboss.seam.annotations.intercept.BypassInterceptors;
                            import org.jboss.seam.contexts.Contexts;
                            import org.jboss.seam.core.Events;
                            import org.jboss.seam.core.Expressions;
                            import org.jboss.seam.security.AuthorizationException;
                            import org.jboss.seam.security.Identity;
                            import org.jboss.seam.log.LogProvider;
                            import org.jboss.seam.log.Logging;
                            import org.jboss.seam.security.RuleBasedIdentity;
                            
                            
                            /**
                             *
                             * @author mwkohout
                             */
                            @Name(value = "org.jboss.seam.security.identity")
                            @Scope(value = SESSION)
                            @Startup
                            public class X500Identity extends Identity {
                            
                             private static final LogProvider log = Logging.getLogProvider(X500Identity.class);
                            
                             @In("org.jboss.seam.security.configuration")
                             Configuration config;
                            
                            
                            
                             private Cookie X500Cookie;
                            
                            
                            
                             public X500Identity()
                             {
                             setJaasConfigName("default");
                             setAuthenticateEveryRequest(true);
                             log.error("in X500Identity constructor. jaas config name = "+this.getJaasConfigName());
                             }
                             @Override
                             public void create()
                             {
                             super.create();
                             }
                            
                             public Cookie getX500Cookie() {
                             return X500Cookie;
                             }
                            
                             public void setX500Cookie(Cookie X500Cookie) {
                             this.X500Cookie = X500Cookie;
                             }
                            
                            
                             public Configuration getConfig() {
                             return config;
                             }
                            
                             public void setConfig(Configuration config) {
                             log.error("in setConfig. config = "+config);
                             this.config = config;
                             }
                            
                             @Override
                             protected LoginContext getLoginContext() throws LoginException {
                             log.error("in my getLoginContext()");
                            
                             if (getJaasConfigName() == null) {
                             throw new RuntimeException("In X500Identity. JAAS config name not set. Please set it up.");
                             }
                             if( config == null )
                             throw new RuntimeException("In X500Identity. \"org.jboss.seam.security.configuration\" component not injected. Please set it up.");
                            
                             log.error( "new LoginContext(getJaasConfigName(), getSubject(), getDefaultCallbackHandler(), config)=+new LoginContext("+getJaasConfigName()+","+ getSubject()+","+ getDefaultCallbackHandler()+","+ config+")");
                             log.error("config's # of app configurationEntry's entries= "+config.getAppConfigurationEntry("default").length);
                             log.error("config's app configurationEntry's LoginModuleName= "+config.getAppConfigurationEntry("default")[0].getLoginModuleName());
                             return new LoginContext(getJaasConfigName(), getSubject(), getDefaultCallbackHandler(), config);
                             }
                            
                             @Override
                             public CallbackHandler getDefaultCallbackHandler() {
                             log.error("in my getDefaultCallbackHandler()");
                             return new CookieCallbackHandler();
                             }
                            
                            
                            
                             @Override
                             public void checkRestriction(String expr) {
                             log.error("in my checkRestriction(String expr) expr=" + expr);
                             if (!evaluateExpression(expr)) {
                             if (!isLoggedIn()) {
                             this.login();
                             } else {
                             Events.instance().raiseEvent("org.jboss.seam.notAuthorized");
                             throw new AuthorizationException(String.format("Authorization check failed for expression [%s]", expr));
                             }
                             }
                             }
                            
                            
                            
                            
                             public static X500Identity instance() {
                             if (!Contexts.isSessionContextActive()) {
                             throw new IllegalStateException("No active session context");
                             }
                            
                             X500Identity instance = (X500Identity) Component.getInstance(X500Identity.class, ScopeType.SESSION);
                            
                             if (instance == null) {
                             throw new IllegalStateException("No Identity could be created");
                             }
                            
                             return instance;
                             }
                            }
                            


                            thanks
                            Mike Kohout

                            • 11. Re: external authentication-any pointers for a beginner?
                              shane.bryzak

                              You currently can't specify a restriction on the "*" view like that (in fact you should be using the login-required attribute anyway). There's an outstanding JIRA issue to that effect:

                              http://jira.jboss.org/jira/browse/JBSEAM-1009

                              What happens when you move your restriction to a more specific view? Do you get the same behaviour that you described?

                              • 12. Re: external authentication-any pointers for a beginner?
                                mwkohout

                                It's still not working. Setting the pages.xml entries(and updating to HEAD of cvs) like so:

                                 <navigation>
                                 <rule if-outcome="home">
                                 <redirect view-id="/home.xhtml"/>
                                 </rule>
                                 </navigation>
                                 </page>
                                
                                 <page view-id="/home.xhtml" scheme="https">
                                 <restrict>#{identity.isLoggedIn(true)}</restrict>
                                 </page>
                                


                                resulted in identical behavior.

                                Setting the login-requred parameter to true resulted in me being forwarded to seam-gen's login.xhtml view. I understand why that's happening-because of the exception handler listed below...but I'm not sure what I should do to make it hit my authentication code. Subclass Pages?
                                <page view-id="*">
                                 <navigation>
                                 <rule if-outcome="home">
                                 <redirect view-id="/home.xhtml"/>
                                 </rule>
                                 </navigation>
                                 </page>
                                
                                 <page view-id="/home.xhtml" login-required="true" scheme="https">
                                 </page>
                                 <exception class="org.jboss.seam.security.NotLoggedInException">
                                 <redirect view-id="/login.xhtml"/>
                                 </exception>
                                


                                any ideas? where in the seam code itself should I be looking so I can debug this behavior?

                                thanks again
                                Mike Kohout

                                • 13. Re: external authentication-any pointers for a beginner?
                                  shane.bryzak

                                   

                                  Setting the login-requred parameter to true resulted in me being forwarded to seam-gen's login.xhtml view.


                                  Isn't that the result that you wanted? BTW the exception handler for NotLoggedInException is unrelated to the login-required attribute.

                                  • 14. Re: external authentication-any pointers for a beginner?
                                    mwkohout

                                    Thanks for your help so far, Shane.

                                    No....My users are authenticating against a web application on a different server developed by a different group...so I've got to forward them to a login page that isn't in my Seam app.

                                    This is the simplified sequence of events:
                                    1) an unauthenticated user tried to access my seam app.
                                    2) my app(via my JAAS Handler) looks for a "secure" cookie for the domain. It doesn't see it. So, it forwards them on to http://authenticate.institution.edu where they are presented with a form and authenticate to that app. That app then sets the domain-wide cookie and then forwards them back to my seam app.
                                    3) my app sees the cookie and from the cookie knows who they are and they are then authenticated. Then they are then assigned roles(assigning roles is trivial and is not something I'm having trouble with) and they use my seam app.
                                    4) After authentication, for every request I check(through a WS) and make sure the cookie is still valid.

                                    I do believe(because I'm not at work and don't have my app in front of me) the NotLoggedInException is thrown from Pages.redirectToLoginView() if the login view isn't set. Why didn't I set my login view? Because I don't want to redirect to a view in my app-I wanna force the JAAS Handler I wrote to run instead of redirect them. My JAAS Handler will force a redirect if necessary.

                                    Instead of forwarding to a view from the exception handler in pages.xml, is there any way I can force the authentication stuff to happen?

                                    1 2 Previous Next