上一级 首页 下一级

5  Web层的实现

J2EE的登录功能主要是在Web层实现。在前面架构设计中分析到,通过定制LoginModule登录进入Web层,同时获得了登录用户的角色,而且也将以相应的角色访问EJB层。Web层的实现也分两个步骤:一个是用户资料的管理;另外一个是访问权限的配置。

5.1  用户资料管理

用户资料管理主要是实现新用户注册;用户资料查询修改;丢失密码查询等功能。这些功能都是通过上面章节介绍的接口框架对后台EJB进行操作,建立UserService作为ServiceProxy的实现,如下:

/**

 *  接口框架的ServiceProxy的具体应用实现者

 * <p>Copyright: Jdon.com Copyright (c) 2003</p>

 * <p>Company: 上海汲道计算机技术有限公司</p>

 * @author banq

 * @version 1.0

 */

public class UserService extends ServiceSupport {

  private final static String module = UserService.class.getName();

  public void perform(Event e) {

    Debug.logVerbose("-->enter UserService ", module);

    UserManagerLocal userManagerLocal = getUserManagerLocal();

    UserEvent userEvent = (UserEvent) e;

    switch (userEvent.getActionType()) {

      case UserEvent.QUERY:

        performQuery(userEvent);

        break;

      case UserEvent.CREATE:

        Debug.logVerbose("-->enter create userId: ", module);

        userManagerLocal.createUser(userEvent);

        break;

      case UserEvent.EDIT:

        Debug.logVerbose("-->enter edit  ", module);

        if (validateUser(userEvent))

          userManagerLocal.updateUser(userEvent);

        else {

          Debug.logVerbose(

              " the user in UserForm is not the user in session. ", module);

          userEvent.setErrors(Constants.SYSTEM_ERROR);

        }

        break;

      default:

        Debug.logVerbose("no this actionType " + userEvent.getActionType(),

                         module);

        break;

    }

  }

  //使用内存状态中的User与用户输入的User资料进行比较,确认合法性

  private boolean validateUser(UserEvent userEvent) {

    boolean success = false;

    User user = userEvent.getUser();

    SecurityFacadeLocal sf = (SecurityFacadeLocal) eJBControllerLocal;

    if (sf.getUser() != null) {

      String userId = sf.getUser().getUserId();

      user.setUserId(userId);

      success = true;

    }

    return success;

  }

 

  private UserManagerLocal getUserManagerLocal() {

    UserManagerLocal userManagerLocal = null;

    try {

      SecurityFacadeLocal securityFacadeLocal = (SecurityFacadeLocal)

          eJBControllerLocal;

      Debug.logVerbose("-->get SecurityFacadeLocal ", module);

      userManagerLocal = securityFacadeLocal.getUserManager();

    } catch (Exception ex) {

      Debug.logError(" getUserManagerLocal() error: " + ex, module);

    }

    return userManagerLocal;

  }

  //查询用户

  private void performQuery(UserEvent userEvent) {

    User user = userEvent.getUser();

    if (user == null) {

      Debug.logVerbose("-->enter query the user in session ", module);

      SecurityFacadeLocal sf = (SecurityFacadeLocal) eJBControllerLocal;

      user = sf.getUser();

    } else {

      String email = user.getEmail();

      if (email != null) {

        Debug.logVerbose("-->enter query by email " + email, module);

        user = getUserManagerLocal().getUserByEmail(email);

      }

    }

    userEvent.setUser(user);

  }

}

UserService中将前台界面提交的关于用户资料新增、修改或查询命令传递到后台EJB中处理,其中:

SecurityFacadeLocal sf = (SecurityFacade Local) eJBControllerLocal;

user = sf.getUser();

6-7  用户注册界面

是从有状态Session Bean SecurityFacade中获得User实例。由于SecurityFacade在第一次被调用后将一直保存着User实例的数据因此只要该用户没有退出系统其他的数据将一直保存在内存中供其他程序反复调用。

以用户注册为例,用户注册实际就是用户资料的新增,其signup.jsp页面外观如下:

signup.jsp中,将图6-7中表单提交到Web层后由StrutsAction来实现,建立SaveSignUpAction如下:

public class SaveSignUpAction extends Action {

  public final static String module = SaveSignUpAction.class.getName();

  public ActionForward execute(ActionMapping actionMapping,

                               ActionForm actionForm,

                               HttpServletRequest request,

                               HttpServletResponse response) throws

      Exception {

    FormBeanUtil.remove(actionMapping, request);

    UserForm userForm = (UserForm) actionForm;

    //检查提交的表单中有无逻辑错误

    if (!checkErrors(userForm, request).isEmpty())

      return (actionMapping.getInputForward());

    //创建一个UserEvent

    UserEvent userEvent = createUserEvent(userForm, request);

    //递交给接口框架处理

    ServiceProxyHandler.perform(userEvent, request);

    //处理完成后,如果没有错误,显示注册成功信息

    if (userEvent.getErrors() == null) {

      if (userEvent.getActionType() == userEvent.CREATE)

         return actionMapping.findForward("createOk");

      else

         return actionMapping.findForward("editOk");

    } else {

      ActionErrors errors = new ActionErrors();

      errors.add(ActionErrors.GLOBAL_ERROR,

                 new ActionError(userEvent.getErrors()));

      saveErrors(request, errors);

      return actionMapping.getInputForward();

    }

  }

//create a UserEvent

  private UserEvent createUserEvent(UserForm userForm, HttpServletRequest request)

    throws      Exception {

    Debug.logVerbose(" --> getUserEvent  ", module);

    String action = userForm.getAction();

    UserEvent userEvent = new UserEvent(module);

    User user = new UserModel();

    try {

      PropertyUtils.copyProperties(user, userForm);

      userEvent.setUser(user);

      userEvent.setActionType(FormBeanUtil.actionTransfer(action));

    } catch (Exception e) {

      Debug.logError("getUserEvent error" + e, module);

      throw new Exception(e);

    }

    return userEvent;

  }

}

在前面章节已经提到,Action的代码已经可以非常模板化,不同的只是基本对象的区别。在eventmappings.xml建立下列对应关系:

<event-mapping>

    <web-action-name>

     com.jdon.security.web.SaveSignUpAction

    </web-action-name>

    <service-proxy-class>

      com.jdon.security.service.UserService

    </service-proxy-class>

</event-mapping>

这样,SaveSignUpActionUserEvent通过UserService交由EJB处理。

再以用户资料修改为例创建EditSignUpAction.java。用户成功登录后,通过直接调用http://localhost:8080/AuthTest/auth/editSignUpAction.do,将显示如图6-7所示的包含用户注册数据的画面,用户的ID无需从网址URL中输入,因为该用户已经登录,用户资料保留在系统内存中,可以直接调用出来。

EditSignUpAction.java代码如下:

public class EditSignUpAction extends Action {

  public final static String module = EditSignUpAction.class.getName();

  public ActionForward execute(ActionMapping mapping,

                               ActionForm form,

                               HttpServletRequest request,

                               HttpServletResponse httpServletResponse) throws  Exception {

    String action = FormBeanUtil.EDIT_STR;

    int actionInt = FormBeanUtil.actionTransfer(action);

    if (form == null) {

      form = new UserForm();

      FormBeanUtil.save(form, mapping, request);

    }

    UserForm userForm = (UserForm) form;

    //从后台获得已经存在的表单数据

    getSignUpForm(actionInt, userForm, request);

    userForm.setAction(action);

    return (mapping.findForward("success"));

  }

 

  private void getSignUpForm(int actionInt, UserForm userForm,

                             HttpServletRequest request) {

    UserEvent userEvent = new UserEvent(module);

    userEvent.setActionType(userEvent.QUERY);

    try {

       //提交UserService实现Query查询操作

       ServiceProxyHandler.perform(userEvent, request);

       //userEvent获得结果

     User user = userEvent.getUser();

     Debug.logVerbose(" user name is" + user.getName() + " userId is" +

                      user.getUserId(), module);

      PropertyUtils.copyProperties(userForm, user);

      userForm.setPassword2(user.getPassword());

    } catch (Exception e) {

 

      Debug.logError("getSignUpForm error" + e, module);

    }

  }

}

5.2  Web容器安全配置

假设Web层目录结构如下:

SAMPLE

  |

  |--- WEB-INF

  |----admin  

  |--- account

     |

     |----- auth

account目录是对任何人都开放的,而auth是授权注册用户才能访问。

本例中设定两个角色adminuser,角色admin代表系统的管理员,而user则代表注册用户。这两个角色需要在Web层定义,在web.xml加入如下语句:

<security-role>

    <role-name>Admin</role-name>

</security-role>

<security-role>

    <role-name>User</role-name>

</security-role>

定义了角色名称,下一步需要进行访问控制权限的分配,希望管理员角色可以访问Webadmin路径下的所有JSP文件或其他任何资源,那么在web.xml中配置:

<security-constraint>

    <display-name>admin</display-name>

    <Web-resource-collection>

      <Web-resource-name>Admin Area</Web-resource-name>

      <!--  admin路径下所有资源  -->

      <url-pattern>/admin/*</url-pattern>

      <!--  protected路径下所有资源  -->

      <url-pattern>/account/auth/*</url-pattern>

    </Web-resource-collection>

    <auth-constraint>

<!--  定义角色为admin -->

      <role-name>admin</role-name>

    </auth-constraint>

    <user-data-constraint>

      <transport-guarantee>NONE</transport-guarantee>

    </user-data-constraint>

  </security-constraint>

这样,如果用户以网址http://localhost:8080/admin/访问时,容器将检验通过登录验证用户的角色,如果是admin角色,将会正常访问,否则会被拒绝。

希望一般注册用户user能够访问路径account/auth下任何资源,同时也可以设定角色admin拥有该目录下的资源访问权限。

<security-constraint>

    <display-name>User protected</display-name>

    <Web-resource-collection>

      <Web-resource-name> Protected Area </Web-resource-name>

      <!--  protected路径下所有资源  -->

      <url-pattern>/account/auth/*</url-pattern>

      <!--  auth路径下所有资源  这并不是对应一个实际路径

            对应StrutssActionpath,说明以auth为路径的

            所有Action也只有角色user能够访问

        -->

      <url-pattern>/auth/*</url-pattern>

    </Web-resource-collection>

    <auth-constraint>

<!--  定义角色为user -->

      <role-name>user</role-name>

    </auth-constraint>

    <user-data-constraint>

      <transport-guarantee>NONE</transport-guarantee>

    </user-data-constraint>

  </security-constraint>

Strutss-config.xml中有如下配置:

<action path="/saveSignUpAction" name="userForm"

        type="com.jdon.security.web.SaveSignUpAction"

        validate="true"  input="/account/signup.jsp"  scope="request"

        >

      <forward name="createOk" path="/account/auth/success.jsp" />

      <forward name="editOk" path="/account/auth/success.jsp" />

</action>

<action path="/auth/editSignUpAction" attribute="userForm"

      type="com.jdon.security.web.EditSignUpAction"

      validate="false" scope="request" >

      <forward name="success" path="/account/signup.jsp" />

</action>

根据web.xml中的配置,路径为/auth/ editSignUpActionEditSignUpAction只有注册用户才可以访问,因为没有设置根路径“/*”的访问限制,所以SaveSignUpAction则是任何用户都可以访问。

完成了访问控制权限的设置,现在可以设置用户的登录方式了。用户的登录有两种方式,即基于HTTP的登录验证方式和表单登录验证方式。

基本登录方式:

<login-config>

    <auth-method>BASIC</auth-method>

    <realm-name>register user</realm-name>

</login-config>

那么,当用户访问受限制资源时,J2EE容器将自动提示界面如图6-2所示,要求用户输入用户名和密码。

表单登录验证方式:

<login-config>

    <auth-method>FORM</auth-method>

    <realm-name>SecurityRealm</realm-name>

    <form-login-config>

      <form-login-page>/account/login.jsp</form-login-page>

      <form-error-page>/account/login_error.jsp</form-error-page>

    </form-login-config>

<login-config>

当用户访问受限制资源时,J2EE容器自动将/account/login.jsp推向浏览器界面,要求用户输入用户名和密码,如图6-3所示。

最后,为了使Web容器的安全机制激活,需要指定特定的LoginModule,这和具体J2EE服务器相关,在JBoss中,需要配置jboss=web.xml如下:

  <security-domain>java:/jaas/ JdonSecurity </security-domain>

这表示web层的安全域将使用名为JdonSecurity JAAS配置。关于JAAS配置JdonSecurity将在6节介绍。


上一级 首页 下一级