1.sysLogin.jsp

        本文采用从前端到后台的形式,整体的阐述一下jeesite中login模块的交互流程,整个jeesite的重要功能我拟将他分为login,list,权限三个部分,应该包括了我们需要实现的大部分功能,计划将这三个部分逐一写出。
        jsp这部分我并不太熟,详见嘉炜的jsp讲解,我这里只挑出重要部分介绍:

<form id="loginForm"  class="form login-form" action="${ctx}/login" method="post">

        整个jsp页面其实就是一个表单,它的主要目的就是接受用户输入的用户名和密码字段,然后交给后台处理。action变量指定了该表达的提交方式,既是交由/a/login所对应的函数来处理。

    <input type="text" id="username" name="username" class="required" value="${username}" placeholder="登录名">
    <input type="password" id="password" name="password" class="required" placeholder="密码"/>

        以上就是表单里的两个属性,一个属性名为username,一个名为password,表单会借由request属性传到函数当中,届时就能通过getUsername和getPassword两个函数从request中取出。这部分是在FormAuthenticationFilter中的createToken函数中实现,下文中会详细介绍。
        Login模块中的jsp非常简单,难点主要在于shiro的应用上,这也是该模块与普通list模块的区别之处。

2.shiro

        之前已经写过一篇shiro相关的文章,只是之前的shiro文章是作为简单的shiro教程来写的,内容大而全,所以知识点之间的串联难免会差一些,这次只做为jeesite应用来写,更注重在逻辑方面,若有问题可以参考前一篇文章。
        其实这里也设计到一些spring-mvc的内容,不过之前也已经写过了,而且也很简单,就不作为单独的一章了。

        上回合说到,jsp将username和password打包扔给后台,那么后台是由什么接受呢?在spring-mvc中,负责接受前台数据的是controller部分,而form中所指定的action是/a/login。

@RequestMapping(value = "${adminPath}/login", method = RequestMethod.GET)
public String login(HttpServletRequest request, HttpServletResponse response, Model model) {
    User user = UserUtils.getUser();
    // 如果已经登录,则跳转到管理首页
    if(user.getId() != null){
        return "redirect:"+Global.getAdminPath();
    }
    return "modules/sys/sysLogin";
}

@RequestMapping(value = "${adminPath}/login", method = RequestMethod.POST)
public String login(@RequestParam(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM) String username, HttpServletRequest request, HttpServletResponse response, Model model) {
    User user = UserUtils.getUser();
    // 如果已经登录,则跳转到管理首页
    if(user.getId() != null){
        return "redirect:"+Global.getAdminPath();
    }
    model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);
    model.addAttribute("isValidateCodeLogin", isValidateCodeLogin(username, true, false));
    return "modules/sys/sysLogin";
}

        很容易就能定位到,以上两个函数就是处理form的函数,或者说正常情况下是由这两个函数来处理。但是仔细看这两个函数,并没有进行逻辑处理,只是简单的检查和跳转。这是因为shiro的登陆功能在controller之前加入了一个filter。这个filter被配置在文件spring-context-shiro.xml文件里。

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <property name="loginUrl" value="${adminPath}/login" />
    <property name="successUrl" value="${adminPath}" />
    <property name="filters">
        <map>
            <entry key="authc" value-ref="formAuthenticationFilter"/>
        </map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /static/** = anon
            /userfiles/** = anon
            ${adminPath}/login = authc
            ${adminPath}/logout = logout
            ${adminPath}/** = user
        </value>
    </property>
</bean>

        以上就是配置过程最关键的部分。loginuUrl属性所指定的url表示的是所有未通过验证的url所访问的位置,此处就是登录界面;successUrl表示的是成功登陆的url访问的位置,此处就是主页。filters则是配置具体验证方法的位置。在此处,${adminPath}/login = authc指定了/a/login,既登陆页面,所需要的验证权限名为authc,又配置了authc所用的filter为formAuthenticationFilter。
        因此整个逻辑是:如果任何地方未登陆,则访问/a/login页面,而/a/login页面的验证权限中又指定了formAuthenticationFilter做为过滤,如果过滤中验证成功,则访问/a这个主页。所以,login.jsp中的表单信息则首先交由formAuthenticationFilter首先处理。

        再来看formAuthenticationFilter中的处理,需要关注的类主要在com.thinkgem.jeesite.modules.sys.security这个包里。通常FormAuthenticationFilter是主要逻辑管理类,SystemAuthorizingRealm这个类则是数据处理类,相当于DAO。但是直接从代码里没发看出功能,这是因为jeesite中的这两个类都是继承于shiro的类,有很多逻辑并没有在jeesite中实现,所以我并不能贴出全部代码,某些地方只用语言来描述。
        大致来讲,首先表单的request被formAuthenticationFilter接收到,然后传给createToken函数,该函数从request中取出name和password,然后生成自定义的一个token传给SystemAuthorizingRealm中的doGetAuthenticationInfo验证。SystemAuthorizingRealm中有systemService的实例,该实例含有userDAO能取出数据库中的name和password。接着由这两个密码生成SimpleAuthenticationInfo,再由info中的逻辑来验证。以上过程中jeesite实现的代码分别如下:

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    String username = getUsername(request);
    String password = getPassword(request);
    if (password==null){
        password = "";
    }
    boolean rememberMe = isRememberMe(request);
    String host = getHost(request);
    String captcha = getCaptcha(request);
    return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha);
}

 

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
    
    if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){
        // 判断验证码
        Session session = SecurityUtils.getSubject().getSession();
        String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
        if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){
            throw new CaptchaException("验证码错误.");
        }
    }

    User user = getSystemService().getUserByLoginName(token.getUsername());
    if (user != null) {
        byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
        return new SimpleAuthenticationInfo(new Principal(user), 
                user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
    } else {
        return null;
    }
}

3.service+dao+entity

        还剩下一些传统SSH的内容,具体就是在doGetAuthenticationInfo中systemService是如何取出数据库里的数据的。
        这部分感觉大家都知道,先空在这里,有必要再补充。