jeesite模块解析,功能实现

作为十分优秀的开源框架,JeeSite拥有着不少实用性的东西。css

 

 

 

默认根路径跳转

 

定义了无Controller的path<->view直接映射前端

<mvc:view-controller  path=”/”  view-name=”redirect:${web.ex}” />java

 

登录login模块

定义了1.sysLogin.jspweb

整个jsp能够看作一个表单。主要目的就是接收用户输入的用户名和密码字段信息,而后交给后台处理。Action变量指定了该表达式的提交方式:/a/login所对应的函数来处理。ajax

sysLogin.jsp算法

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

帐号和密码的属性sql

<div class="input-row">数据库

    <label for="username">帐号</label>apache

    <input type="text" name="username" id="username" placeholder="请填写登陆帐号">

</div>

<div class="input-row">

    <label for="password">密码</label>

    <input type="password" name="password" id="password" placeholder="请填写登陆密码">

</div>

 

一个username一个password,表单会借由request属性传到函数种,到时候能够经过getUsername和getPassword两个函数从request中取出。可是简单之处必有难点出现。如何对shiro应用确实不易。

 

LoginController.java控制层的方法

/**

 * 管理登陆

 */

@RequestMapping(value = "${adminPath}/login", method = RequestMethod.GET)

public String login(HttpServletRequest request, HttpServletResponse response, Model model) {

Principal principal = UserUtils.getPrincipal();

 

if (logger.isDebugEnabled()){

logger.debug("login, active session size: {}", sessionDAO.getActiveSessions(false).size());

}

 

// 若是已登陆,再次访问主页,则退出原帐号。

if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){

CookieUtils.setCookie(response, "LOGINED", "false");

}

 

// 若是已经登陆,则跳转到管理首页

if(principal != null && !principal.isMobileLogin()){

return "redirect:" + adminPath;

}

return "modules/sys/sysLogin";

}

/**

 * 登陆失败,真正登陆的POST请求由Filter完成

 */

@RequestMapping(value = "${adminPath}/login", method = RequestMethod.POST)

public String loginFail(HttpServletRequest request, HttpServletResponse response, Model model) {

Principal principal = UserUtils.getPrincipal();

 

// 若是已经登陆,则跳转到管理首页

if(principal != null){

return "redirect:" + adminPath;

}

 

String username = WebUtils.getCleanParam(request, FormAuthenticationFilter.DEFAULT_USERNAME_PARAM);

boolean rememberMe = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM);

boolean mobile = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_MOBILE_PARAM);

String exception = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);

String message = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM);

 

if (StringUtils.isBlank(message) || StringUtils.equals(message, "null")){

message = "用户或密码错误, 请重试.";

}

 

model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);

model.addAttribute(FormAuthenticationFilter.DEFAULT_REMEMBER_ME_PARAM, rememberMe);

model.addAttribute(FormAuthenticationFilter.DEFAULT_MOBILE_PARAM, mobile);

model.addAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, exception);

model.addAttribute(FormAuthenticationFilter.DEFAULT_MESSAGE_PARAM, message);

 

if (logger.isDebugEnabled()){

logger.debug("login fail, active session size: {}, message: {}, exception: {}",

sessionDAO.getActiveSessions(false).size(), message, exception);

}

 

// 非受权异常,登陆失败,验证码加1。

if (!UnauthorizedException.class.getName().equals(exception)){

model.addAttribute("isValidateCodeLogin", isValidateCodeLogin(username, true, false));

}

 

// 验证失败清空验证码

request.getSession().setAttribute(ValidateCodeServlet.VALIDATE_CODE, IdGen.uuid());

 

// 若是是手机登陆,则返回JSON字符串

if (mobile){

        return renderString(response, model);

}

 

return "modules/sys/sysLogin";

}

 

咱们看到controller是负责接收前台数据,前台from中指定的是/a/login因此定位到相应的controller。细看这俩,只是简单的检查与跳转。这是由于shiro的登录功能在controller以前加入了一个filter.这个filter被配置在文件Spring-context-shiro.xml文件中。

<!-- Shiro权限过滤过滤器定义 -->

<bean name="shiroFilterChainDefinitions" class="java.lang.String">

<constructor-arg>

<value>

/static/** = anon

/userfiles/** = anon

${adminPath}/cas = cas

${adminPath}/login = authc

${adminPath}/logout = logout

${adminPath}/** = user

/act/editor/** = user

/ReportServer/** = user

</value>

</constructor-arg>

</bean>

 

<!-- 安全认证过滤器 -->

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

<property name="securityManager" ref="securityManager" />

<!--

<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->

<property name="loginUrl" value="${adminPath}/login" />

<property name="successUrl" value="${adminPath}?login" />

<property name="filters">

            <map>

                <entry key="cas" value-ref="casFilter"/>

                <entry key="authc" value-ref="formAuthenticationFilter"/>

            </map>

        </property>

<property name="filterChainDefinitions">

<ref bean="shiroFilterChainDefinitions"/>

</property>

</bean>

最关键的部分。loginUrl属性所指定的url表示的是全部未经过验证的url所访问的位置。此处就是登录界面了。successUrl表示成功登录访问的url位置,也就是主页。Filters是配置具体验证方法的位置。在此处,${adminPath}/login = authc指定了/a/login,登录页面所须要的验证权限名为authc.而且authc的filter也设置了,在map中:

<entry key="authc" value-ref="formAuthenticationFilter"/>

 再来看formAuthenticationFilter中的处理,须要关注的类主要在com.thinkgem.jeesite.modules.sys.security这个包里。一般FormAuthenticationFilter是主要逻辑管理类,SystemAuthorizingRealm这个类则是数据处理类,至关于DAO。

可是并未发现其功能,是由于这俩类都继承于shiro的类。

总得讲,首先request被formAuthenticationFilter接收到,而后传给createToken函数,该函数从request中取出name and  password,而后生成自定义的一个token传给了SystemAuthorizingRealm中的doGetAuthenticationInfo验证。其中SystemAuthorizingRealm内有systemService的实例,该实例含有userDAO能取出数据库中的name and password 接着由这俩密码生成SimpleAuthenticationInfo,再由info中的逻辑来验证。

 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;

    }

}

 

以后就是service+dao+entity.

 

 

首先说下他的一个流程

 

Jeesite流程

流程

主要是jsp,entity,dao,dao.xml,service,controller)

(1) .MyBatisRegisterDao.xml

这里作的工做即是对数据库语句的撰写。

 

 

(2) .MyBatisRegisterDao.java

 

 

 

(3) .Register.java实体

通常公共的字段放在相应的实体工具类中,如createBycreateDateupdateByupdateDateremarksdel_flag都放在dateEntity.java中。用时只需extends便可

 

 

(4).RegisterService.java

其中建议requestMapping注解放在首位,全局注解为好。

 

 

(5).RegisterService.java

 

 

 

(6).Register.jsp

 

 

 

 

Mybatis的整体流程是

1.加载配置并初始化,其触发条件是加载配置文件将SQL 的配置信息加载成为一个个MappingStatement对象(包括传入参数映射配置,执行的sql语句,结果映射配置) 存储在内存中

2.接收调用请求,其触发条件是调用mybatis中的api,将请求传递给下层的请求处理层处理

3.处理操做请求api接口层传递传递请求过来,传入sqlid和传入参数,根据sqlid查找对应的MappingStatement对象,和传入参数对象解析MappingStatement对象,获得最终要执行的sql和执行传入参数,后获取数据库链接,根据最终获得的sql语句和传入参数到数据库执行,获得最终的处理结果,最后释放资源

4.将最终处理结果返回

 

关于shiro受权

1.Shiro受权的三要素是:权限,角色,用户

2.三要素的关联:由于经过声明权限咱们仅仅能了解这个权限在项目中能作什么,而不能肯定谁有这个权限,因此,咱们须要在应用程序中对用户和权限创建关系。

3.在项目上: 咱们通常将权限分配给某个角色,而后将这个角色分配给一个或多个用户,例如:修改的权限是只有管理员才拥护的,那么,在这个时候,管理员就至关于被设于拥有修改权限的用户,

4.shiro支持三种受权方式:编码实现,注解实现,jsp Tag实现

 

 

咱们看下关于用户权限的几个表:

 

 

 

Orm对象关系映射

1.用于实现面向对象编程语言里不一样类型系统的数据之间的转换

2.jeesite框架中用到的就是mybatis

安全模块

 

用户密码加密存储

用户密码加密算法:对用户的密码进行sha-1算法加密。迭代1024次,并将salt放在前16位中。

/**

 * 生成安全的密码,生成随机的16位salt并通过1024次 sha-1 hash

 */

public static String entryptPassword(String plainPassword) {

String plain = Encodes.unescapeHtml(plainPassword);

byte[] salt = Digests.generateSalt(SALT_SIZE);

byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);

return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword);

}

咱们能够看到,在SystemService中,加密方式通过1024次迭代,并将salt放在前16位。Return的首先是salt而后+hashPasswordd.

而后看下解密:

/**

 * 设定密码校验的Hash算法与迭代次数

 */

@PostConstruct

public void initCredentialsMatcher() {

HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SystemService.HASH_ALGORITHM);

matcher.setHashIterations(SystemService.HASH_INTERATIONS);

setCredentialsMatcher(matcher);

}

jeesite/src/main/java/com/thinkgem/jeesite/modules/sys/security/SystemAuthorizingRealm.java

解密的过程与加密的过程是一致的。

 

安全验证码

验证码通常不会出现。可是当用户请求超过三次,此时sysLogin.jsp会向ValidateCodeServlet请求验证图片,而ValidateCodeServlet生成的图片则存入session中。而后进行code的一个验证。

String validateCode = request.getParameter(VALIDATE_CODE);

if (StringUtils.isNotBlank(validateCode)){

response.getOutputStream().print(validate(request, validateCode)?"true":"false");

 

缓存处理

 

系统对每一个用户所须要用到的资源都用map作了缓存处理。

若是用户不存在则建立一个新的Map<String,Object>对象,若是存在的话则取principal中的Map<String,Object>对象作为缓存,由于principle会随着用户的logout自动释放,每一个用户都有了本身的缓存,能够再日志中查询到。而且每一个用户的缓存是相互独立的。

UserUtils.java中,

public static Map<String, Object> getCacheMap()

public static Object getCache(String key, Object defaultValue)

public static void putCache(String key, Object value)

public static void removeCache(String key)

 

 

主题更换

head.jsp中经过查询cookie.theme.value的值来替换bootstrap的css文件,从而达到主题更换的效果。咱们先看下head.jsp:

 

<link href="${ctxStatic}/bootstrap/2.3.1/css_${not empty cookie.theme.value ? cookie.theme.value : 'cerulean'}/bootstrap.min.css" type="text/css" rel="stylesheet" />

 

LoginController中,主题替换的接口以下:

/**

 * 获取主题方案

 */

@RequestMapping(value = "/theme/{theme}")

public String getThemeInCookie(@PathVariable String theme, HttpServletRequest request, HttpServletResponse response){

if (StringUtils.isNotBlank(theme)){

CookieUtils.setCookie(response, "theme", theme);

}else{

theme = CookieUtils.getCookie(request, "theme");

}

return "redirect:"+request.getParameter("url");

}

 

翻页处理

/jeesite/src/main/java/com/thinkgem/jeesite/common/persistence/Page.java

 

其中page<T>的toString()方法实现了BootStrap的显示细节,其中数据都放于Page中。

而在前端jsp页面只须要引用便可。

<div class="pagination">${page}</div>

树形目录选择

先说下office的弹出对话框式树形选择。

使用tags:treeselect标签将页面操做逻辑封装。在tags:treeselect中,用JBox来调用/tag/treeselect转向treeselect.jsp页面,并传入相关的参数,其中url,展现的json格式数据来源。当选择的为v时,即肯定,这时,id和name就hi传出来。

ajaxData:{selectIds: $("#${id}Id").val()},buttons:{"肯定":"ok", ${allowClear?"\"清除\":\"clear\", ":""}"关闭":true}, submit:function(v, h, f){

$("#${id}Id").val(ids.join(",").replace(/u_/ig,""));

$("#${id}Name").val(names.join(","));

其中tagTreeselect.jsp负责数据展现。

zNodetree负责选择等操做。

 

角色受权树型选择操做

先经过后台传过来的数据构建zNodetree

zNodetree 来管理数据的选择

在表单提交时(submitHandler )获取选择数据并添加到相应的 input中。而后提交。以下图

 

 

这里用了一个小技巧。 SpringMVC  进行先后台数据绑定的时候实际上是调用Model 的 set 与 get方法。( 因此只要有这两个方法便可,不用有成员变员也行)

Role 模型添加了两个方法,并用 Transient 来标记不写入数据库。以下

 

 

这样就能够自动把数据写回到Role 中。

 

邹邹 邹伟帅

相关文章
相关标签/搜索