作爲十分優秀的開源框架,JeeSite擁有着不少實用性的東西。css
首先說下他的一個流程前端
流程java
主要是jsp,entity,dao,dao.xml,service,controller)web
(1) .MyBatisRegisterDao.xmlajax
這裏作的工做即是對數據庫語句的撰寫。算法
(2) .MyBatisRegisterDao.javaspring
(3) .Register.java實體sql
通常公共的字段放在相應的實體工具類中,如createBy,createDate,updateBy,updateDate,remarks,del_flag都放在dateEntity.java中。用時只需extends便可數據庫
(4).RegisterService.javaapache
(4) .RegisterController.java
其中建議requestMapping註解放在首位,全局註解爲好。
(6).Register.jsp
Mybatis的整體流程是
1.加載配置並初始化,其觸發條件是加載配置文件將SQL 的配置信息加載成爲一個個MappingStatement對象(包括傳入參數映射配置,執行的sql語句,結果映射配置) 存儲在內存中
2.接收調用請求,其觸發條件是調用mybatis中的api,將請求傳遞給下層的請求處理層處理
3.處理操做請求,api接口層傳遞傳遞請求過來,傳入sql的id和傳入參數,根據sql的id查找對應的MappingStatement對象,和傳入參數對象解析MappingStatement對象,獲得最終要執行的sql和執行傳入參數,後獲取數據庫鏈接,根據最終獲得的sql語句和傳入參數到數據庫執行,獲得最終的處理結果,最後釋放資源
4.將最終處理結果返回
1.Shiro受權的三要素是:權限,角色,用戶
2.三要素的關聯:由於經過聲明權限咱們僅僅能瞭解這個權限在項目中能作什麼,而不能肯定誰有這個權限,因此,咱們須要在應用程序中對用戶和權限創建關係。
3.在項目上: 咱們通常將權限分配給某個角色,而後將這個角色分配給一個或多個用戶,例如:修改的權限是隻有管理員才擁護的,那麼,在這個時候,管理員就至關於被設於擁有修改權限的用戶,
4.shiro支持三種受權方式:編碼實現,註解實現,jsp Tag實現
咱們看下關於用戶權限的幾個表:
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 中
定義了無Controller的path<->view直接映射
<mvc:view-controller path=」/」 view-name=」redirect:${web.ex}」 />
定義了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.
引入連接:http://www.nohup.cc/article/23/
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> </property> </bean> |
shiroFilter是shiro的安全認證過濾器,其中,
securityManager:指定一個負責管理的bean,這個新的bean在接下來會定義,其中包含了認證的主要邏輯。
而屬性中的filterChainDefinitions則詳細規定了不一樣的url的對應權限
<!-- 定義 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" /> </bean> |
這部分代碼定義了securitymanager的主要屬性的實體,systemAuthorizingRealm和shiroCacheManager都是本身實現的,分別用於進行驗證管理和cache管理。從配置文件能看出,配置文件指定了做爲安全邏輯的幾個結構,除了這兩部分,還包括formAuthenticationFilter。其中realm和filter在com.thinkgem.jeesite.modules.sys.security包裏實現。
從可見都邏輯上講,FormAuthenticationFilter類是用戶驗證時所接觸的第一個類。
當用戶登陸任意界面時,shiro會對當前狀態進行檢查。若是發現須要登陸,則會自動跳轉到配置文件裏loginUrl屬性所指定的url中。
而這一url又被指定爲authc權限,即須要驗證。接着,authc的filter被指定爲formAuthenticationFilter,所以login頁面所提交的信息被改filter截獲進行處理。
其中的核心邏輯是createToken函數:
protected AuthenticationToken createToken(S2ervletRequest 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); } |
UsernamePasswordToken是security包裏的第四個類,它繼承自shiro的同名類,用於shiro處理中的參數傳遞。createtoken函數接受到login網頁所接受的表單,生成一個token傳給下一個類處理。
函數中的rememberme以及captcha分別表示的是記住用戶功能和驗證碼功能,這部分也是shiro自身攜帶,咱們並不須要修改。filter是經過aop的方式結合到系統裏的,所以並無具體的接口實現。
shiro的最終處理都將交給Real進行處理。由於在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的。一般狀況下,在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO.
Realm中有個參數是systemService,這個即是spring的具體業務邏輯,其中也包含了具體的DAO,正是在這個部分,shiro與spring的接口具體的結合了起來。
realm當中有兩個函數特別重要,分別是用戶認證函數和受權函數。
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; } |
以上即是用戶認證函數,中間關於驗證碼檢測的部分咱們暫且不表,其最核心的語句是
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的形式指派的,它負責驗證用戶是否有權限訪問。詳細的細節容我以後添加。
LoginController是本該實現登陸認證的部分。因爲shiro的引入和AOP的使用,jeesite中的LoginController只處理驗證以後的部分。
若是經過驗證,系統中存在user實例,則返回對應的主頁。不然從新定位於login頁面。
經常使用的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)
}
});