Announcement Announcement Module
Collapse
No announcement yet.
Login in 2 steps Page Title Module
Move Remove Collapse
This topic is closed
X
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Login in 2 steps

    Hi all,
    I would like to receive some ideas for implementing our 2 steps login.

    The process works as follow:
    1. User insert username and password
    2. If they are correct, an email with an unique token code are sent to the user mailbox. (double check for higher security)
    3. User is forwarded to a "insert token code" page
    4. If the token inserted by the user is correct, authentication is successfull and the user is forwarded to the required page.

    I'm wondering which classes should I extend/rewrite to have this functionality integrated in Acegi and to maintain the Acegi flexible design.

    My idea:
    I was thinking to extend AbstractProcessingFilter to manage a different form page and to handle my filter with a specific getDefaultFilterProcessesUrl().
    But I don't know if it is a good idea to implement the attemptAuthentication() method throwing a new InsertTokenCodeAuthenticationException (class to be created) and where to manage this exception

    thanks in advance,
    Yanke

  • #2
    Could you clarify, is this for email validation on initial sign-up, or for each and every login attempt? I've only seen it done for the former.

    Comment


    • #3
      Login in 2 steps

      It is for every single login attempt... it's an e-banking system...

      Comment


      • #4
        I'll assume everything is happening over HTTPS then. I'd recommend you never create an Authentication until you have all three tokens: username, password, emailCode. This means you'll be making a standard Spring MVC controller to collect the username and password, checking it's correct, generating an emailCode and emailing it. Then display a login form to ask for the emailCode, keeping the username and password as hidden form inputs. That form then posts to an AbstractProcessingFilter subclass.

        The AbstractProcessingFilter subclass is like AuthenticationProcessingFilter: it generates a "request" Authentication, but containing all three tokens. Something like:

        Code:
            public Authentication attemptAuthentication(HttpServletRequest request)
                throws AuthenticationException {
                String username = request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
                String password = request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
                String emailCode = request.getParameter(ACEGI_SECURITY_FORM_EMAIL_CODE_KEY);
        
                if (username == null) {
                    username = "";
                }
        
                if (password == null) {
                    password = "";
                }
        
                if (emailCode == null) {
                    emailCode = "";
                }
        
                UsernamePasswordEmailAuthenticationToken authRequest = new UsernamePasswordEmailAuthenticationToken(username,
                        password, emailCode);
                authRequest.setDetails(request.getRemoteAddr());
        
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        The delegation to the AuthenticationManager will eventually delegate to a subclass of DaoAuthenticationProvider. The subclass will have overridden these two methods:

        Code:
            protected boolean isPasswordCorrect(Authentication authentication,
                UserDetails user);
            protected Authentication createSuccessAuthentication(Object principal,
                Authentication authentication, UserDetails user);
        You need both because the "success" Authentication must contain the emailCode so subsequent requests during the session are accepted. I'd suggest the emailCode remain valid until an explicit logout action, when you can invalidate the HttpSession as well as the database-persisted emailCode. Or, setup a javax.servlet.http.HttpSessionListener which invalidates the code when the HttpSession ends.

        Comment


        • #5
          Thank you very much Ben. I understand your idea that maintain a clear separation between my login logic and Acegi Authentication.

          I will soon implement your suggestion.

          thanks again,
          Yanke

          Comment


          • #6
            Need another piece of information to authenticate user

            Hello,

            I've tried what's listed in topic http://forum.springframework.org/viewtopic.php?t=899, but I'm not having any success. Here is my problem:

            A user in the application may be assigned many different roles such as ROLE_PROJECTA_ADMIN, ROLE_PROJECTB_ADMIN, ROLE_PROJECTB_WRITER, etc. I am using the Authorize Taglib to display content based on roles. So one could have the following scenerio. A user logs into the system and selects PROJECTA (User should only see PROJECTA related information). If the user is assigned roles ROLE_PROJECTA_ADMIN and ROLE_PROJECTB_WRITER, the user will see the link below regardless of the project she chooses:

            Code:
            <authz&#58;authorize ifAllGranted="ROLE_PROJECTA_ADMIN, ROLE_PROJECTB_ADMIN">
               <html&#58;link action="/page""></html&#58;link>
            </authz&#58;authorize>
            The solution I attempted is as follows

            1) User logs into the system. This is handled by a Struts controller that simply verifies the username and password and gathers the projects the user has access to.

            2) The user is presented with a screen to choose the project she wants to work with. The username and passwords are stored as hidden fields.

            3) Once the user selects the project, she is re-authenticated using the Acegi AuthenticationProcessingFilter. The AUTHORITIES table will has the columns (AUTHORITY,USERNAME,PROJECT_ID). I should be able to only retrieve authorities for that user based on the project and username. Something like the following:

            Code:
            SELECT USERNAME,AUTHORITY FROM AUTHORITIES A, WHERE USERNAME = ? AND PROJECT_ID = ?
            Is there a better way to do this????

            Following the advice from topic 899, I've extended the AuthenticationProcessingFilter and DaoAuthenticationProvider and added UsernamePasswordProjectAuthenticationToken. Is there something that

            My AuthenticationProcessingFilter
            Code:
            
            
            package gmbpcm.security;
            
            import net.sf.acegisecurity.Authentication;
            import net.sf.acegisecurity.AuthenticationException;
            import gmbpcm.security.UsernamePasswordProjectAuthenticationToken;
            import net.sf.acegisecurity.providers.*;
            import net.sf.acegisecurity.ui.*;
            
            import javax.servlet.FilterConfig;
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServletRequest;
            
            
            
            public class AuthenticationProcessingFilter extends net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter &#123;
                //~ Static fields/initializers =============================================
            
                public static final String ACEGI_SECURITY_FORM_USERNAME_KEY = "j_username";
                public static final String ACEGI_SECURITY_FORM_PASSWORD_KEY = "j_password";
                public static final String BOB_PROJECT_TYPE_KEY = "j_projecttype";
                public static final String ACEGI_SECURITY_LAST_USERNAME_KEY = "ACEGI_SECURITY_LAST_USERNAME";
            
                //~ Methods ================================================================
            
                /**
                 * This filter by default responds to <code>/j_acegi_security_check</code>.
                 *
                 * @return the default
                 */
                public String getDefaultFilterProcessesUrl&#40;&#41; &#123;
                    return "/j_acegi_security_check";
                &#125;
            
                public Authentication attemptAuthentication&#40;HttpServletRequest request&#41;
                    throws AuthenticationException &#123;
                    String username = request.getParameter&#40;ACEGI_SECURITY_FORM_USERNAME_KEY&#41;;
                    String password = request.getParameter&#40;ACEGI_SECURITY_FORM_PASSWORD_KEY&#41;;
            		String projecttype = request.getParameter&#40;BOB_PROJECT_TYPE_KEY&#41;;
            
                    if &#40;username == null&#41; &#123;
                        username = "";
                    &#125;
            
                    if &#40;password == null&#41; &#123;
                        password = "";
                    &#125;
            		
            		if &#40;projecttype == null&#41; &#123;
            			projecttype = "";
            		&#125;
            
            		UsernamePasswordProjectAuthenticationToken authRequest = new UsernamePasswordProjectAuthenticationToken&#40;username,
                            password, projecttype&#41;;
            
                    // Allow subclasses to set the "details" property
                    setDetails&#40;request, authRequest&#41;;
            
                    // Place the last username attempted into HttpSession for views
                    request.getSession&#40;&#41;.setAttribute&#40;ACEGI_SECURITY_LAST_USERNAME_KEY,
                        username&#41;;
            
                    return this.getAuthenticationManager&#40;&#41;.authenticate&#40;authRequest&#41;;
                &#125;
            
                public void init&#40;FilterConfig filterConfig&#41; throws ServletException &#123;&#125;
            
            
                protected void setDetails&#40;HttpServletRequest request,
                        UsernamePasswordProjectAuthenticationToken authRequest&#41; &#123;
                    authRequest.setDetails&#40;request.getRemoteAddr&#40;&#41;&#41;;
                &#125;
            &#125;
            My UsernamePasswordProjectAuthenticationToken
            Code:
            
            package gmbpcm.security;
            
            import net.sf.acegisecurity.GrantedAuthority;
            import net.sf.acegisecurity.providers.*;
            
            
            public class UsernamePasswordProjectAuthenticationToken
                extends AbstractAuthenticationToken &#123;
                //~ Instance fields ========================================================
            
                private Object credentials;
                private Object details = null;
                private Object principal;
            	private Object project;
                private GrantedAuthority&#91;&#93; authorities;
                private boolean authenticated = false;
            
                //~ Constructors ===========================================================
            
                public UsernamePasswordProjectAuthenticationToken&#40;Object principal,
                    Object credentials, Object project&#41; &#123;
                    this.principal = principal;
                    this.credentials = credentials;
            		this.project = project;
                &#125;
            
                public UsernamePasswordProjectAuthenticationToken&#40;Object principal,
                    Object credentials, Object project, GrantedAuthority&#91;&#93; authorities&#41; &#123;
                    this.principal = principal;
                    this.credentials = credentials;
            		this.project = project;
                    this.authorities = authorities;
                &#125;
            
                protected UsernamePasswordProjectAuthenticationToken&#40;&#41; &#123;
                    throw new IllegalArgumentException&#40;"Cannot use default constructor"&#41;;
                &#125;
            
                //~ Methods ================================================================
            
                public void setAuthenticated&#40;boolean isAuthenticated&#41; &#123;
                    this.authenticated = isAuthenticated;
                &#125;
            
                public boolean isAuthenticated&#40;&#41; &#123;
                    return this.authenticated;
                &#125;
            
                public void setAuthorities&#40;GrantedAuthority&#91;&#93; authorities&#41; &#123;
                    this.authorities = authorities;
                &#125;
            
                public GrantedAuthority&#91;&#93; getAuthorities&#40;&#41; &#123;
                    return this.authorities;
                &#125;
            
                public Object getCredentials&#40;&#41; &#123;
                    return this.credentials;
                &#125;
            
                public void setDetails&#40;Object details&#41; &#123;
                    this.details = details;
                &#125;
            
                public Object getDetails&#40;&#41; &#123;
                    return details;
                &#125;
            
                public Object getPrincipal&#40;&#41; &#123;
                    return this.principal;
                &#125;
            	
            	public Object getProject&#40;&#41; &#123;
                    return this.project;
                &#125;
            &#125;

            You stated that I should subclass DaoAuthenticationProvider. This where I'm lost. What needs to happen in the isPasswordCorrect and createSuccessAuthentication methods. Do I need to subclass any other classes? Any help you can provide will be greatly appreciated.

            Thanks,

            Damon Henry

            Comment


            • #7
              Hi Damon

              First up, Acegi Security 0.8.0 recommends a simplified way of handling your email + password + username situation. I wish I had recommended this to people before, but anyway....

              Simply use subclass AuthenticationProcessingFilter and override its new obtainPassword method:

              Code:
                  protected String obtainPassword&#40;HttpServletRequest request&#41; &#123;
                      return request.getParameter&#40;ACEGI_SECURITY_FORM_PASSWORD_KEY&#41; + "&#58;" + request.getParameter&#40;MY_CUSTOM_EMAIL_KEY&#41;;
                  &#125;
              Your AuthenticationDao will also need to be modified to concatenate your password column to your email column. As such, you've isolated changes to Acegi Security to handle the authentication side to only two locations, and they're two locations which are reasonable and common to extend.

              Regarding your project separation, I'd really encourage you to use the ACL services for this. You're trying to make the authentication infrastructure reflect a concept of effectiveRights = authentication + domainObject. This is exactly what the ACL model does, but more elegantly and comprehensively. In the ACL model you'd simply login, then you can obtain a list of all your projects from a public Collection projectManager.getAll(). Acegi Secuity would filter the projects the user doesn't have access to (ie delete them from the returned Collection). When you read, write or delete specific Project objects, Acegi Security will block the method invocation if the user doesn't have permission to the instance. The Contacts Sample application uses ACLs in this manner - I'd encourage you to take a look at it for inspiration.

              Comment

              Working...
              X