Advanced

Implementing security

Problem

You would like to protect some of web application pages from unauthorised access. For example, if user types the following URI in the browser you want to redirect them to login screen first.

http://localhost/troika.asp?cmd=secretInfo

Solution

Implement declarative security and protect the resources from access.

  1. Define security constraint in config.xml

    
    <security-constraints>
      <constraint>
        <login-form-path>/troika.asp?cmd=login</login-form-path>
        <url-patterns>
          <url-pattern request-ctx-xpath="//action">
            <match-pattern>secretAction</match-pattern>
            <return-path>/troika.asp?cmd=secretAction</return-path>
          </url-pattern>
        </url-patterns>
        <authenticate>
          <role>admin</role>
          <role>member</role>
        </authenticate>
      </constraint>
    </security-constraints>
    
  2. List user roles (admin, member) that are allowed to access the resource(cmd=secretAction) in authenticate element
  3. Define Command mapping for login screen:

    
    <cmd-map action="login">
      <name>loginForm</name>
      <type>LoginAction</type>
      <forwards>
        <forward name="success" redirect="true" path="/" />
        <forward name="login-form" redirect="false"
          path="/views/login.xsl" />
      </forwards>
    </cmd-map>
    
  4. Define loginForm mapping:
    
    <req-ctx-map name="loginForm" type="LoginForm"/>
    
  5. Implement LoginForm:

    
    
    LoginForm.prototype =  new RequestContext();
    LoginForm.prototype.constructor = LoginForm;
    
    function LoginForm(action) {
    
        if (arguments.length) {
    
            this.init(action);
        }
    }
    
    LoginForm.prototype.init = function (action) {
    
        RequestContext.prototype.init.call(this, action);
    
        this.userName = null;
        this.password = null;
    
        return this;
    };
    
    LoginForm.prototype.validate = function () {
    
        var result =  new ArrayList();
    
        this.flatten();
    
        result.addAll(this.checkRequired("Username", this.userName));
        result.addAll(this.checkRequired("Password", this.password));
    
        return result;
    };
    

    Note: The call this.flatten() converts each property (username, password) into a simple string. When the form (RequestContext) is created all properties are of ListArray type and can contain multiple values. For example, you have URI like this:

    /troika.asp?cmd=login&userName=user1&userName=user2
    

    After calling flatten() on login form userName property will contain a string with comma separated values:

    loginFirm.userName: user1,user2

  6. Implement LoginAction command:

    
    LoginAction.prototype =  new Command();
    LoginAction.prototype.constructor = LoginAction;
    
    LoginAction.prototype.init = function (config) {
    
        Command.prototype.init.call(this, config);
        return this;
    };
    
    function LoginAction(config) {
    
        if (arguments.length) {
    
            this.init(config);
        }
    
        this.execute = function (environment, loginForm) {
    
            var result =  new ResponseContext();
    
            if (environment.request.get("submit")) {
    
                var errors = loginForm.validate();
                if (errors.size()) {
    
                    result.errors.addAll(errors);
                    result.forward = this.findForward(loginForm, "login-form");
                }
                else {
    
                    var dao =  new LoginDAO(this.getConnectionString("helloworld-db"));
                    var vo = dao.findByUsernameAndPassword(loginForm.userName, loginForm.password);
    
                    if (vo) {
    
                        environment.session.put("security.role", vo.securityRole);
                        environment.session.put("loginId", vo.loginId);
                        environment.session.put("memberId", vo.memberId);
    
                        result.forward = this.getNextPageForward(environment, loginForm, vo);
                    }
                    else {
    
                        this.error("Could not log you in please try again.", loginForm, result, environment);
                    }
                }
            }
            else {
    
                result.forward = this.findForward(loginForm, "login-form");
            }
    
            return result;
        };
    
        this.getNextPageForward = function (environment, loginForm, userVO) {
    
            var session = environment.session;
            var nextPage = session.get("nextPageForward");
    
            var result = undefined;
    
            if (nextPage) {
    
                session.remove("nextPageForward");
                result =  new Forward(true, nextPage);
            }
            else {
    
                result = this.findForward(loginForm, "success");
            }
    
            return result;
        };
    
        this.error = function (msg, loginForm, responseCxt) {
    
            responseCxt.errors.add(new Error(msg));
            responseCxt.forward = this.findForward(loginForm, "login-form");
        };
    }
    
    • First, we check to see if Login form has been submitted with submit button. If not we simply show the login form.
    • If submit button has been clicked we check to see if userName and password fields are set and not empty:
      
      var errors = loginForm.validate();
      
    • If there are no errors, then we are creating new LoginDAO instance and search for userName/password in the database
    • If we find the user then we set session variable (security.role) to indicate that we have successfully logged in

      
      
      environment.session.put("security.role", vo.securityRole);
      
    • We also save the current user Id so that we can retrieve it later from the other commands

      
      
      environment.session.put("loginId", vo.loginId);
      environment.session.put("memberId", vo.memberId);
      
    • Then, we check for session variable nextPageForward and redirect the response to the URI. If it is not set we forward to login command mapping success view. In this case it set to

      /troika.asp?cmd=secretAction
      

    • The variable nextPageForward is set by framework’s AppController class when the authentication fails
    • If the userName or password cannot be found in the database we bounce user back to the login form with error message
  7. I have created the login table in helloworld.mdb for convenience. It contains the following fields:

    • LoginId
    • MemberId
    • Username
    • Pwd
    • SecurityRole
    • SecretInfo
  8. Let’s generate the persistence code- LoginDAO/VO classes

    • Create new entry in build/generator/dao-definitions.xml

      
      
      <entity name="Login" table="login">
        <field name="loginId" type="Identity" size="1" />
        <field name="memberId" type="Integer" size="1" />
        <field name="username" size="25" />
        <field name="password" column="Pwd" size="20" />
        <field name="securityRole" size="10" />
        <field name="secretInfo" size="80" />
        <finder fields="loginId" />
        <finder fields="username password" />
        <update />
      </entity>
      
    • Run this from command line:
      >build generate
      
    • This will generate two classes LoginDAO and LoginVO and copies them to WEB-INF/classes/models directory
  9. Test it in browser:

    • http://localhost/troika.asp?cmd=secretAction
      
      You will be redirected to login screen
    • Enter johns/password and click submit button
    • You will be bounced back to the original URI -
      /troika.asp?cmd=secretAction
      
  10. That is the end of the tutorial

Search

Advanced

FAQ

Bookmarks

AddThis Social Bookmark Button

Support This Project