天天看點

JeeSite功能子產品解讀,功能介紹,功能實作

jeesite流程;關于shiro授權;orm對象關系映射;安全子產品;緩存處理;主題更換;翻頁處理;樹形目錄選擇;角色授權樹形選擇;預設路徑跳轉;登陸login子產品。

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

首先說下他的一個流程

Jeesite流程

流程

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

(1) .MyBatisRegisterDao.xml

這裡做的工作便是對資料庫語句的撰寫。

JeeSite功能子產品解讀,功能介紹,功能實作

(2) .MyBatisRegisterDao.java

JeeSite功能子產品解讀,功能介紹,功能實作

(3) .Register.java實體

一般公共的字段放在相應的實體工具類中,如createBy,createDate,updateBy,updateDate,remarks,del_flag都放在dateEntity.java中。用時隻需extends即可

JeeSite功能子產品解讀,功能介紹,功能實作

(4).RegisterService.java

JeeSite功能子產品解讀,功能介紹,功能實作

(4) .RegisterController.java

其中建議requestMapping注解放在首位,全局注解為好。

JeeSite功能子產品解讀,功能介紹,功能實作

(6).Register.jsp

JeeSite功能子產品解讀,功能介紹,功能實作

Mybatis的總體流程是

1.加載配置并初始化,其觸發條件是加載配置檔案将SQL 的配置資訊加載成為一個個MappingStatement對象(包括傳入參數映射配置,執行的sql語句,結果映射配置) 存儲在記憶體中

2.接收調用請求,其觸發條件是調用mybatis中的api,将請求傳遞給下層的請求處理層處理

3.處理操作請求,api接口層傳遞傳遞請求過來,傳入sql的id和傳入參數,根據sql的id查找對應的MappingStatement對象,和傳入參數對象解析MappingStatement對象,得到最終要執行的sql和執行傳入參數,後擷取資料庫連接配接,根據最終得到的sql語句和傳入參數到資料庫執行,得到最終的處理結果,最後釋放資源

4.将最終處理結果傳回

關于shiro授權

1.Shiro授權的三要素是:權限,角色,使用者

2.三要素的關聯:因為通過聲明權限我們僅僅能了解這個權限在項目中能做什麼,而不能确定誰有這個權限,是以,我們需要在應用程式中對使用者和權限建立關系。

3.在項目上: 我們一般将權限配置設定給某個角色,然後将這個角色配置設定給一個或多個使用者,例如:修改的權限是隻有管理者才擁護的,那麼,在這個時候,管理者就相當于被設于擁有修改權限的使用者,

4.shiro支援三種授權方式:編碼實作,注解實作,jsp Tag實作

JeeSite功能子產品解讀,功能介紹,功能實作

我們看下關于使用者權限的幾個表:

JeeSite功能子產品解讀,功能介紹,功能實作

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中。然後送出。如下圖

JeeSite功能子產品解讀,功能介紹,功能實作

這裡用了一個小技巧。 SpringMVC  進行前背景資料綁定的時候其實是調用Model 的 set 與 get方法。( 是以隻要有這兩個方法即可,不用有成員變員也行)

給 Role 模型添加了兩個方法,并用 Transient 來标記不寫入資料庫。如下

JeeSite功能子產品解讀,功能介紹,功能實作

這樣就可以自動把資料寫回到Role 中

預設根路徑跳轉

定義了無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>

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

    <input type="password" name="password" id="password" placeholder="請填寫登入密碼">

一個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) {

if(principal != null){

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);

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);

我們看到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>

最關鍵的部分。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中的shiro

引傳入連結接:http://www.nohup.cc/article/23/

2.1 spring-context-shiro.xml

    spring-context-shiro.xml是shiro的主配置檔案,配置資訊是重點主要是安全認證過濾器的配置。它規定哪些url需要進行哪些方面的認證和過濾

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

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

    <property name="p" 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>

  shiroFilter是shiro的安全認證過濾器,其中,

securityManager:指定一個負責管理的bean,這個新的bean在接下來會定義,其中包含了認證的主要邏輯。

  • loginUrl:沒有登入的使用者請求需要登入的頁面時自動跳轉到登入頁面,不是必須的屬性,不輸入位址的話會自動尋找項目web項目的根目錄下的”/login.jsp”頁面。
  • successUrl:登入成功預設跳轉頁面,不配置則跳轉至”/”。如果登陸前點選的一個需要登入的頁面,則在登入自動跳轉到那個需要登入的頁面。不跳轉到此。
  • unauthorizedUrl:沒有權限預設跳轉的頁面。
  • map中的entry指定了authc權限所對應的過濾器實體

而屬性中的filterChainDefinitions則詳細規定了不同的url的對應權限

  • anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。
  • authc:例如/admins/user/**=authc表示需要認證(登入)才能使用,沒有參數
  • roles:例子/admins/user/=roles[admin],參數可以寫多個,多個時必須加上引号,并且參數之間用逗号分割,當有多個參數時,例如admins/user/=roles["admin,guest"],每個參數通過才算通過,相當于hasAllRoles()方法。
  • perms:例子/admins/user/**=perms[user:add:],參數可以寫多個,多個時必須加上引号,并且參數之間用逗号分割,例如/admins/user/=perms["user:add:,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當于isPermitedAll()方法。
  • rest:例子/admins/user/=rest[user],根據請求的方法,相當于/admins/user/=perms[user:method] ,其中method為post,get,delete等。
  • port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協定http或https等,serverName是你通路的host,8081是url配置裡port的端口,queryString

    是你通路的url裡的?後面的參數。

  • authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
  • ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協定為https
  • user:例如/admins/user/**=user沒有參數表示必須存在使用者,當登入操作時不做檢查
  • 注:anon,authcBasic,auchc,user是認證過濾器,perms,roles,ssl,rest,port是授權過濾器

<!-- 定義 Shiro 主要業務對象 -->

<bean id="securityManager"

class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

<!-- <property name="sessionManager" ref="sessionManager" /> -->

<property name="realm" ref="systemAuthorizingRealm" />

<property name="cacheManager" ref="shiroCacheManager" />

    這部分代碼定義了securitymanager的主要屬性的實體,systemAuthorizingRealm和shiroCacheManager都是自己實作的,分别用于進行驗證管理和cache管理。從配置檔案能看出,配置檔案指定了作為安全邏輯的幾個結構,除了這兩部分,還包括formAuthenticationFilter。其中realm和filter在com.thinkgem.jeesite.modules.sys.security包裡實作。

2.2 FormAuthenticationFilter

    從可見都邏輯上講,FormAuthenticationFilter類是使用者驗證時所接觸的第一個類。

    當使用者登入任意界面時,shiro會對目前狀态進行檢查。如果發現需要登入,則會自動跳轉到配置檔案裡loginUrl屬性所指定的url中。

    而這一url又被指定為authc權限,即需要驗證。接着,authc的filter被指定為formAuthenticationFilter,是以login頁面所送出的資訊被改filter截獲進行處理。

    其中的核心邏輯是createToken函數:

protected AuthenticationToken createToken(S2ervletRequest request, ServletResponse response) {

    UsernamePasswordToken是security包裡的第四個類,它繼承自shiro的同名類,用于shiro進行中的參數傳遞。createtoken函數接受到login網頁所接受的表單,生成一個token傳給下一個類處理。

    函數中的rememberme以及captcha分别表示的是記住使用者功能和驗證碼功能,這部分也是shiro自身攜帶,我們并不需要修改。filter是通過aop的方式結合到系統裡的,是以并沒有具體的接口實作。

2.3 SystemAuthorizingRealm

    shiro的最終處理都将交給Real進行處理。因為在Shiro中,最終是通過Realm來擷取應用程式中的使用者、角色及權限資訊的。通常情況下,在Realm中會直接從我們的資料源中擷取Shiro需要的驗證資訊。可以說,Realm是專用于安全架構的DAO.

Realm中有個參數是systemService,這個便是spring的具體業務邏輯,其中也包含了具體的DAO,正是在這個部分,shiro與spring的接口具體的結合了起來。

realm當中有兩個函數特别重要,分别是使用者認證函數和授權函數。

        return new SimpleAuthenticationInfo(new Principal(user),

        return null; }

    以上便是使用者認證函數,中間關于驗證碼檢測的部分我們暫且不表,其最核心的語句是

User user = getSystemService().getUserByLoginName(token.getUsername());

以及另一句語句

return new SimpleAuthenticationInfo(new Principal(user), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());

    函數的參數AuthenticationToken便是filter所截獲并生成的token執行個體,其内容是login表單裡的内容,通常是使用者名和密碼。在前一句話裡,getSystemService取到了systemService執行個體,該執行個體中又含有配置好的userDAO,能夠直接與資料庫互動。是以将使用者id傳入,取出資料庫裡所存有的user執行個體,接着在SimpleAuthenticationInfo生成過程中分别以user執行個體的使用者名密碼,以及token裡的使用者名密碼做為參數,驗證密碼是否相同,以達到驗證的目的。

    doGetAuthorizationInfo函數是授權的函數,其具體的權限是在實作類中以annotation的形式指派的,它負責驗證使用者是否有權限通路。詳細的細節容我之後添加。

2.3 LoginController

     LoginController是本該實作登入認證的部分。由于shiro的引入和AOP的使用,jeesite中的LoginController隻處理驗證之後的部分。

如果通過驗證,系統中存在user執行個體,則傳回對應的首頁。否則重新定位于login頁面。

2.4 annotation

    常用的annotation主要如下:

@RequiresAuthentication

    要求目前Subject 已經在目前的session 中被驗證通過才能被注解的類/執行個體/方法通路或調用。

    驗證使用者是否登入,等同于方法subject.isAuthenticated() 結果為true時。

@RequiresUser

    需要目前的Subject 是一個應用程式使用者才能被注解的類/執行個體/方法通路或調用。要麼是通過驗證被确認,或者在之前session 中的'RememberMe'服務被記住。

    驗證使用者是否被記憶,user有兩種含義:一種是成功登入的(subject.isAuthenticated() 結果為true);另外一種是被記憶的(subject.isRemembered()結果為true)。

@RequiresGuest

    要求目前的Subject 是一個“guest”,也就是他們必須是在之前的session中沒有被驗證或記住才能被注解的類/執行個體/方法通路或調用。

    驗證是否是一個guest的請求,與@RequiresUser完全相反。

    換言之,RequiresUser == !RequiresGuest。此時subject.getPrincipal() 結果為null.

@RequiresRoles

    要求目前的Subject 擁有所有指定的角色。如果他們沒有,則該方法将不會被執行,而且AuthorizationException 異常将會被抛出。例如:@RequiresRoles("administrator")

或者@RequiresRoles("aRoleName");

void someMethod();

    如果subject中有aRoleName角色才可以通路方法someMethod。如果沒有這個權限則會抛出異常AuthorizationException。

@RequiresPermissions

    要求目前的Subject 被允許一個或多個權限,以便執行注解的方法,比如:

    @RequiresPermissions("account:create")

    或者@RequiresPermissions({"file:read", "write:aFile.txt"} )

    void someMethod();

Alert(id)

$.ajax({

   type:”post”,

   url:”${pageContext.request.contextPage.}”,

   data:”cid=”+id,

   Success:function(data){

alert(data)

});

非常感謝各位檢視我的随筆。

這些随筆是在生活中遇到的一些問題和解決方案或者是相關的知識。

希望對大家會有一些幫助。

如果有幫到各位的地方,希望可以對作者進行一定的捐助,謝謝。

https://files.cnblogs.com/files/zz-blog/zhifubao.bmp

繼續閱讀