jeesite模塊解析,功能實現


做為十分優秀的開源框架,JeeSite擁有着很多實用性的東西。

 

 

 

默認根路徑跳轉

 

定義了無Controller的path<->view直接映射

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

 

登陸login模塊

定義了1.sysLogin.jsp

整個jsp可以看做一個表單。主要目的就是接收用戶輸入的用戶名和密碼字段信息,然后交給后台處理。Action變量指定了該表達式的提交方式:/a/login所對應的函數來處理。

sysLogin.jsp

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

賬號和密碼的屬性

<div class="input-row">

    <label for="username">賬號</label>

    <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 中。

 

鄒鄒 鄒偉帥

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM