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是如何取出数据库里的数据的。
这部分感觉大家都知道,先空在这里,有必要再补充。