Shiro Demo結(jié)合SSM框架講解Shiro案例
本教程結(jié)合SSM(SpringMVC + Mybatis)框架講解Shiro(Shiro是 Java 的一個(gè)安全框架。我們經(jīng)常看到它被拿來和 Spring 的 Security 來對(duì)比。),講解的內(nèi)容有自定義Shiro攔截器,Shiro Freemarker標(biāo)簽,Shiro JSP標(biāo)簽,權(quán)限控制講解,并提供Shiro Demo下載。
Shiro Demo環(huán)境準(zhǔn)備
開發(fā)工具:Eclipse、MyEclipse、Idea等等。
依賴第三方:Mysql 5.0以上、Redis。
需要的配置:jdbc.properties中配置Mysql的信息、spring-cache.xml配置Redis 配置,如果是默認(rèn)配置,就不用換,Redis Windows安裝:http://www.sojson.com/blog/110.html。
注意:官方網(wǎng)站已經(jīng)更新了第二個(gè)版本:http://www.sojson.com/shiro

申明:請(qǐng)看完本頁面的所有細(xì)節(jié),對(duì)你掌握這個(gè)項(xiàng)目來說很重要,別一上來就搞,你不爽,我也不爽。
本項(xiàng)目理論上,只需要一個(gè)Redis,然后一個(gè)Mysql和一個(gè)有Maven環(huán)境的開發(fā)工具即可運(yùn)行起來。
Demo已經(jīng)部署到線上,地址是http://shiro.itboy.net,
管理員帳號(hào):admin,密碼:sojson.com 如果密碼錯(cuò)誤,請(qǐng)用sojson。
PS:你可以注冊(cè)自己的帳號(hào),然后用管理員賦權(quán)限給你自己的帳號(hào),但是,每20分鐘會(huì)把數(shù)據(jù)初始化一次。建議自己下載源碼,讓Demo跑起來,然后跑的更快,有問題加群解決。
Shiro Demo 源碼下載
Shiro Demo 非Maven項(xiàng)目依賴包下載:點(diǎn)我下載
Shiro Demo 源碼下載下載:點(diǎn)我(云端下載)
Github 0.1版本下載:https://github.com/baichengzhou/SpringMVC-Mybatis-shiro
Github 0.2版本下載:https://github.com/baichengzhou/SpringMVC-Mybatis-Shiro-redis-0.2
Shiro Demo 0.2版本介紹:http://www.sojson.com/blog/165.html
Shiro Demo 0.2版本主要解決的問題為0.1版本出現(xiàn)的問題和BUG,然后添加了一個(gè)功能--單個(gè)帳號(hào)登錄限制問題,具體查看上面的介紹。
Shiro Demo環(huán)境準(zhǔn)備,建議使用0.2版本,這樣你會(huì)遇到較少問題。
開發(fā)工具:Eclipse、MyEclipse、Idea等等。
依賴第三方:Mysql 5.0以上、Redis。
需要的配置:jdbc.properties中配置Mysql的信息、spring-cache.xml配置Redis 配置,如果是默認(rèn)配置,就不用換,Redis Windows安裝:http://www.sojson.com/blog/110.html。
注意:0.1版本訪問不要帶項(xiàng)目路徑訪問。比如用:http://localhost:8080訪問,別帶設(shè)置帶項(xiàng)目名稱 ,如:http://localhost:8080/shiro.demo/這樣是不對(duì)的。 。也就是要把項(xiàng)目部署到Root下,也就是根目錄下。0.2版本已經(jīng)解決該問題了。
主流的工具部署到Root下的方法:
Shiro 簡(jiǎn)介
Apache Shiro 是 Java 的一個(gè)安全框架。我們經(jīng)常看到它被拿來和 Spring 的 Security 來對(duì)比。大部分人認(rèn)為 Shiro 比 Security 要簡(jiǎn)單。我的觀點(diǎn)贊成一半一半吧。
首先 Shiro 確實(shí)和 Security 是同類型的框架,主要用來做安全,也就是我們俗稱的權(quán)限校驗(yàn)(控制)。居多人對(duì) Shrio 的定義為好入門。
我選型為 Shiro ,主要的原因擴(kuò)展太easy了,而且我要的功能它都有。
本教程環(huán)境。
本教程Jar包管理是 Maven ,所以如果是 Maven 項(xiàng)目下載Demo后可以直接使用,如果是普通的Java Web項(xiàng)目,那么請(qǐng)?jiān)谙旅嫦螺d所有依賴包。
本教程開發(fā)工具是Myecilpse8.5。
本教程編碼格式為UTF-8。
本教程JDK為1.7。
本教程Spring版本為4.2.5。
前端頁面采用Bootstarp 3.2。
本教程包含的內(nèi)容。
SSM(SpringMVC + Spring +Mybatis)框架的增刪改查(含分頁),所以如果框架小白也是可以看看的。
View層主要是Freemarker,但是為了考慮到好多人還使用的是JSP,也有一個(gè)頁面是用JSP實(shí)現(xiàn)的,并且框架支持Freemarker 和JSP雙View展示(優(yōu)先找Freemarker)。
Shiro初始權(quán)限動(dòng)態(tài)加載。
Shiro自定義權(quán)限校驗(yàn)Filter定義,及功能實(shí)現(xiàn)。
ShiroAjax請(qǐng)求權(quán)限不滿足,攔截后解決方案。
ShiroFreemarker標(biāo)簽使用。
ShiroJSP標(biāo)簽使用。
Shiro登錄后跳轉(zhuǎn)到最后一個(gè)訪問的頁面。
用戶禁止登錄Demo。
在線顯示,在線用戶管理(踢出登錄)。
登錄注冊(cè)密碼加密傳輸Demo(詳細(xì)請(qǐng)見下面講解)。
密碼修改。
用戶個(gè)人中心。
權(quán)限的增刪改查。
角色的增刪改查。
權(quán)限->角色->用戶之間的關(guān)系維護(hù)。
管理員權(quán)限的自動(dòng)添加(當(dāng)有一個(gè)權(quán)限創(chuàng)建,自動(dòng)添加到管理員角色下,保證管理員是最大權(quán)限)。
Spring定時(shí)任務(wù)數(shù)據(jù)化數(shù)據(jù)。
集成多種驗(yàn)證碼(包括動(dòng)態(tài)的gif驗(yàn)證碼哦)。
一個(gè)帳號(hào)多處登錄限制,踢出用戶。
后續(xù)會(huì)陸陸續(xù)續(xù)升級(jí)... ...
一、SSM(SpringMVC + Spring + Mybatis)框架的增刪改查(含分頁)
本教程是SSM(SpringMVC + Spring + Mybatis + Freemarker + JSP)+ Shiro + Redis 做的整體Demo,其他框架需要自己自行解決,所以不做其他框架的講解,其實(shí)是大同小異。
Controller==>Service(事務(wù)控制層) ==>Dao==>SqlMapper==>Mysql
二、View層 Freemarker,JSP
通用View層配置在spring-mvc.xml中的以【通用試圖解析器】注釋標(biāo)注的區(qū)間配置。
三、Shiro + Redis 的集成,也提供Ehcache的依賴Jar。
Redis緩存配置主要在spring-cache.xml中。對(duì)應(yīng)的所有Cache 相關(guān) Java 代碼在package:com.sojson.core.shiro.cache中
四、Shiro 初始權(quán)限動(dòng)態(tài)加載。
我們一般是這么加載的。在spring-shiro.xml中配置
/** = anon /page/login.jsp = anon /page/register/* = anon /page/index.jsp = authc /page/addItem* = authc,roles[數(shù)據(jù)管理員] /page/file* = authc,roleOR[普通用戶,數(shù)據(jù)管理員] /page/listItems* = authc,roleOR[數(shù)據(jù)管理員,普通用戶] /page/showItem* = authc,roleOR[數(shù)據(jù)管理員,普通用戶] /page/updateItem*=authc,roles[數(shù)據(jù)管理員]
本教程采用動(dòng)態(tài)加載,你可以從數(shù)據(jù)庫里讀取然后拼接成shiro要的數(shù)據(jù)。
<property name="filterChainDefinitions" value="#\{shiroManager.loadFilterChainDefinitions()\}"/>
配置文件方式加載詳細(xì)講解:http://www.sojson.com/blog/148.html
五、Shiro 自定義權(quán)限校驗(yàn)Filter定義,及功能實(shí)現(xiàn)。
Shrio Filter在package:com.sojson.core.shiro.filter,具體配置在spring-shiro.xml中。定義了5個(gè)攔截器,具體功能看代碼以及代碼注釋。
Shrio Filter在package:com.sojson.core.shiro.filter,具體配置在spring-shiro.xml中。定義了5個(gè)攔截器,具體功能看代碼以及代碼注釋
<bean id="shiroManager" class="com.sojson.core.shiro.service.impl.ShiroManagerImpl"/> <bean id="login" class="com.sojson.core.shiro.filter.LoginFilter"/> <bean id="role" class="com.sojson.core.shiro.filter.RoleFilter"/> <bean id="permission" class="com.sojson.core.shiro.filter.PermissionFilter"/> <bean id="simple" class="com.sojson.core.shiro.filter.SimpleAuthFilter"/>
<property name="filters"> <util:map> <entry key="login" value-ref="login"></entry> <entry key="role" value-ref="role"></entry> <entry key="simple" value-ref="simple"></entry> <entry key="permission" value-ref="permission"></entry> </util:map> </property>
六、Shiro Ajax請(qǐng)求權(quán)限不滿足,攔截后解決方案。
這里有一個(gè)前提,我們知道Ajax不能做頁面redirect和forward跳轉(zhuǎn),所以Ajax請(qǐng)求假如沒登錄,那么這個(gè)請(qǐng)求給用戶的感覺就是沒有任何反應(yīng),而用戶又不知道用戶已經(jīng)退出了。解決代碼如下:
//Java代碼,判斷如果是Ajax請(qǐng)求,然后并且沒登錄,那么就給予返回JSON,login_status = 300,message = 當(dāng)前用戶沒有登錄! if (ShiroFilterUtils.isAjax(request)) {// ajax請(qǐng)求 MapresultMap = new HashMap(); LoggerUtils.debug(getClass(), "當(dāng)前用戶沒有登錄,并且是Ajax請(qǐng)求!"); resultMap.put("login_status", "300"); resultMap.put("message", "\u5F53\u524D\u7528\u6237\u6CA1\u6709\u767B\u5F55\uFF01");//當(dāng)前用戶沒有登錄! ShiroFilterUtils.out(response, resultMap); }
//前端代碼 if(result.login_status == 300){ layer.msg(result.message);//當(dāng)前用戶沒有登錄! }
七、Shiro Freemarker標(biāo)簽使用。
Freemarker使用Shiro 標(biāo)簽的介紹:http://www.sojson.com/blog/143.html
八、Shiro JSP標(biāo)簽使用。
JSP使用Shiro 標(biāo)簽的介紹:http://www.sojson.com/blog/144.html
九、Shiro 登錄后跳轉(zhuǎn)到最后一個(gè)訪問的頁面。
在 Java 中就可以這樣獲取上一個(gè)地址:
//上一個(gè)瀏覽的非Ajax的地址,在登錄后,取得地址,如果不為null,那么就跳轉(zhuǎn)過去。 String url = (String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE); //shiro也有他的方法。詳細(xì)看下面。
如果需要保存登錄之前的Request信息,那么需要在Login攔截的Filter中先保存:
@Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //保存Request和Response,登錄后可以取到 saveRequestAndRedirectToLogin(request, response); return Boolean.FALSE ; } //登錄后,取到之前的Request中的一些信息。 SavedRequest saveRequest = WebUtils.getSavedRequest(request); saveRequest.getMethod();//之前的請(qǐng)求方法 saveRequest.getQueryString();//之前請(qǐng)求的條件 saveRequest.getRequestURI();//之前請(qǐng)求的路徑 saveRequest.getRequestUrl();//之前請(qǐng)求的全路徑
十、用戶禁止登錄Demo
這個(gè)功能其實(shí)是一個(gè)改變用戶數(shù)據(jù)庫表里的一個(gè)字段,本Demo中:1:有效,0:禁止登錄
然后踢出用戶登錄狀態(tài)。代碼詳細(xì)請(qǐng)查看CustomSessionManager.java類的forbidUserById(Long id, Long status)方法。
而再次登錄的話,需要再登錄,而登錄的地方限制了用戶狀態(tài)為(0:禁止登錄)的用戶登錄。
/** * 查詢要禁用的用戶是否在線。 * @param id用戶ID * @param status用戶狀態(tài) */ public void forbidUserById(Long id, Long status) { //獲取所有在線用戶 for(UserOnlineBo bo : getAllUser()){ Long userId = bo.getId(); //匹配用戶ID if(userId.equals(id)){ //獲取用戶Session Session session = shiroSessionRepository.getSession(bo.getSessionId()); //標(biāo)記用戶Session SessionStatus sessionStatus = (SessionStatus) session.getAttribute(SESSION_STATUS); //是否踢出 true:有效,false:踢出。 sessionStatus.setOnlineStatus(status.intValue() == 1); //更新Session customShiroSessionDAO.update(session); } } }
十一、在線顯示,在線用戶管理(踢出登錄)。
上面的功能依賴這個(gè)功能。從Redis中獲取所有有效的Session
/** * 獲取所有的有效Session用戶 * @return */ public List getAllUser() { //獲取所有session Collection sessions = customShiroSessionDAO.getActiveSessions(); List list = new ArrayList(); for (Session session : sessions) { UserOnlineBo bo = getSessionBo(session); if(null != bo){ list.add(bo); } } return list; }
踢出后,不能直接退出,要不然用戶感覺莫名其妙。所有增加了一個(gè)Filter。SimpleAuthFilter.java如果標(biāo)記為踢出,會(huì)提示用戶。具體查看源碼以及配合項(xiàng)目的使用。
十二、登錄注冊(cè)密碼加密傳輸。
這個(gè)地方好多人糾結(jié)的。比如密碼過于簡(jiǎn)單,即使加密后也能破解。如我們把密碼:123456,加密后就是:e10adc3949ba59abbe56e057f20f883e
這個(gè)很容易被識(shí)別,導(dǎo)致不安全,現(xiàn)在市面上的MD5破解其實(shí)就是把常用的數(shù)字,字母進(jìn)行先加密,然后對(duì)比,美其名曰破解MD5。
那怎么能讓用戶即使使用很簡(jiǎn)單的密碼,也發(fā)現(xiàn)不了?
本Demo的實(shí)現(xiàn)方式是:MD5(登錄帳號(hào) + “固定值” + 密碼),把這個(gè)作為密碼,這樣,基本是不可能猜的出來。
//Java代碼。UserManager.md5Pswd(UUser user); /** * 加工密碼,和登錄一致。 * @param user * @return */ public static UUser md5Pswd(UUser user){ //密碼為 email + '#' + pswd,然后MD5 user.setPswd(md5Pswd(user.getEmail(),user.getPswd())); return user; } /** * 字符串返回值 * @param email * @param pswd * @return */ public static String md5Pswd(String email ,String pswd){ pswd = String.format("%s#%s", email,pswd); pswd = MathUtil.getMD5(pswd); return pswd; }
//JS代碼 var pswd = MD5(username +"#" + password);
十三、密碼修改。
不講了,和上面原理一致,然后具體看Demo。
十四、用戶個(gè)人中心。
包含的功能有[個(gè)人資料,資料修改,密碼修改,我的權(quán)限],具體看Demo。
十五、權(quán)限的增刪改查。
本Demo的設(shè)計(jì)是遵循RBAC3的思想。http://www.sojson.com/blog/142.html
RBAC個(gè)人理解介紹:http://www.sojson.com/blog/141.html

具體實(shí)現(xiàn)看Demo。
十六、角色的增刪改查。

十七、權(quán)限->角色->用戶之間的關(guān)系維護(hù)。
把權(quán)限賦給角色。

把角色賦給用戶。

十八、管理員權(quán)限的自動(dòng)添加
這里每次添加一個(gè)權(quán)限,都會(huì)添加到“管理員”角色下,保證“管理員”角色擁有最大權(quán)限。
十九、Spring定時(shí)任務(wù)數(shù)據(jù)初始化。
這個(gè)Demo因?yàn)槭情_放的,所以創(chuàng)建了一個(gè)定時(shí)任務(wù)。每20分鐘執(zhí)行一次,用Mysql存儲(chǔ)過程重新創(chuàng)建表,重新插入初始化數(shù)據(jù)。
具體數(shù)據(jù)看項(xiàng)目中的init/sql下的tables.sql(初始化表),init.data.sql(初始化數(shù)據(jù))
//定時(shí)任務(wù)配置文件spring-timer.xml//Java 代碼 RoleServiceImpl.java /** * 每20分鐘執(zhí)行一次 */ @Override @Scheduled(cron = "0 0/20 * * * ? ") public void initData() { roleMapper.initData(); }
二十、集成驗(yàn)證碼。

項(xiàng)目中package:com.sojson.common.utils.vcode包是驗(yàn)證碼的封裝包。
并且提供了一個(gè)VerifyCodeUtils.java 的驗(yàn)證碼工具類。
使用方法參見:CommonController.java類中的getVCode()方法和getGifCode()方法。


驗(yàn)證碼詳細(xì)介紹、
Java生成驗(yàn)證碼合集(一)簡(jiǎn)單版 、
Java生成驗(yàn)證碼合集(二)GJF版 。
二十一、一個(gè)帳號(hào)多處登錄限制,踢出用戶。
項(xiàng)目中我們會(huì)用到單點(diǎn)登錄,還有用到單個(gè)賬號(hào)怎么限制同時(shí)只能一個(gè)人在線?
Shiro教程(十一)Shiro 控制并發(fā)登錄人數(shù)限制實(shí)現(xiàn),登錄踢出實(shí)現(xiàn):http://www.sojson.com/blog/158.html
