一、Shiro简介
Apache Shiro 是 Java 的一个安全框架。我们经常看到它被拿来和 Spring 的 Security 来对比。大部分人认为 Shiro 比 Security 要简单。我的观点赞成一半一半吧。
首先 Shiro 确实和 Security 是同类型的框架,主要用来做安全,也就是我们俗称的权限校验(控制)。居多人对 Shrio 的定义为好入门。
我选型为 Shiro ,主要的原因扩展太easy了,而且我要的功能它都有。
二、概述
前段时间出了一个基于SSM(SpringMVC + Spring + Mybatis)的Shiro 教程Demo,Cache(Nosql)是基于Ehcache的,但是很多同学卡在了Redis上,经常运行起来。所以现在出一版本基于 Cache 为Ehcache版本的。这样减少新入门的同学的难度,不用依赖第三方中间件。
后续会陆陆续续添加N多相关的功能。以不同版本的方式发布。
基于Redis版本地址:http://www.sojson.com/shiro
三、需要你的赞助
如果帮助到了您,请你在下载代码后,运行后,跑起来后,加群帮你解决问题后,兴奋、喜悦、愤怒、丧气、不知所措... ... 的时候,请赞助我,钱多少不重要,学生请不要赞助(富二代请忽略)。
赞助链接:http://www.sojson.com/subsidize.html
四、请遵循三要素
- 建议你看完本篇文档所有内容,再进行运行项目。
- 在没熟练之前,除了必要的配置修改,请勿改动任何配置和包路径。
- 有疑问先看文档,交流群:
259217951
即可,群里没人理你@群主即可。
五、环境准备
5.1 开发工具
Eclipse 、 MyEclipes 、Idea
等 Java 开发工具,推荐使用MyEclipes8.5以上。因为这个项目是在MyEclipes8.5和MyEclipes10.7
开发的。
如果使用Eclipse的同学,请安装好Maven环境,如果没有Maven环境,又不想安装,那么请在附件中下载依赖包,自己把项目转成Java Web项目然后进行运行。
如果使用Idea
作为开发工具的同学,注意配置resources
,还有一些其他的配置需要自己处理,不像导入 MyEclipes 直接能跑起来。
5.2 依赖第三方
因为是基于 Ehcache ,所以也没有其他第三方。
主要就是一个 Mysql 数据库。数据库的版本为 Mysql5.6
,估计Mysql5.5
,Mysql5.6
都没问题,Mysql5.7
有小小问题,因为一些默认配置导致有些语法可能不支持,这个慎用。
下面有最新,我测试可用的Mysql
安装教程:Mysql5.6下载安装,Mysql5.7下载安装 ,Windows64位,绿色安装(解压缩安装)图文安装教程 。
如果安装出现Mysql
权限问题:ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES) 解决。
六、环境配置及要求,JDK版本,初始化配置
6.1 JDK版本要求
JDK 版本要求为JDK1.7+
,我开发的版本是1.7.0_80
,JDK1.6
有些jar
包会报错,推荐使用JDK1.7
以上。
如果出现以下类似错误,那就是JDK版本不相符。
Unsupported major.minor version 51.0
其中各个版本对应的提示如下:
JDK1.5对应为Unsupported major.minor version 49.0
JDK1.6对应为Unsupported major.minor version 50.0
JDK1.7对应为Unsupported major.minor version 51.0
JDK1.8对应为Unsupported major.minor version 52.0
6.2 初始化配置
6.2.1 Mysql数据库初始化
本教程不支持自动创建表和插入数据,在项目的init/sql
下有三个sql 文件,分别为:tables.sql
(插入表)、init.data.sql
(插入初始化数据)、init_shiro_demo.sql
(插入初始化存储过程)。
执行的过程为: tables.sql
(插入表)===> init.data.sql
(插入初始化数据)就可以了。
存储过程可以是定时任务 com.sojson.common.timer.ToTimer
中定时任务调用的存储过程。每20分钟一次。想看看效果的同学,可以把spring.xml
配置文件中的spring-timer.xml
注释打开就可以。
数据库配置:jdbc.properties
配置你的数据库链接。
jdbc.url=jdbc:mysql://localhost:3306/shiro.demo
jdbc.username=root
jdbc.password=123456
具体 druid 配置:Druid数据库配置详细介绍 。
其他配置默认的即可,先跑起来,跑起来没问题后,看看配置文件,有问题和疑问在群里交流。
PS:如果你确实要用Mysql5.7,那么在Mysql的安装目录下找到。my.ini
或者my_default.ini
里面配置sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
即可。
6.3 Maven环境说明
本教程是用 Maven 管理Jar包及运行打包,如果你发现 Maven 一直在下载jar
包,时间过久的话,建议你换成阿里的数据源。打开你的 Maven 目录的setting.xml
文件,如果没有直接添加即可,官方群里有setting.xml
文件作为参考。
主要改个本地Maven
目录,改成你自己的目录即可:
<localRepository>E:\maven\repository</localRepository>
再配置一个mirror,找到标签为mirrors,然后在里面添加或者修改为阿里的Maven库,如下图:
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
Maven的Mirror和Repository 的详细讲解 ,可以详细了解下。
异常解决
常见的异常有3种。
- 如果有部分包打红
×
,可以删除这个包路径,再次项目右键Maven选项(每个工具不一样)
==>Update Maven Dependencies
更新即可。 - 如果下载好
Jar
包,也不报错,但是在运行项目的时候,报错zip
相关异常,那么删除Maven
目录下所有下好的jar
包,然后再来下载一次。因为是下载的包是损坏的。 - 还有 Maven 的 JDK 版本需要和你项目一致,有的工具默认配置是
JDK1.5
。
6.4 其他说明
编码格式:UTF-8
,换成其他编码格式可能会有瑕疵。
Spring相关Jar版本为:Spring 4.2.5
前端页面采用:Bootstarp 3.2
。
其他依赖:jQuery1.8.3
、layer
控件。
6.5 View层说明
http://www.sojson.com/shiro之前的Shiro Demo 是 JSP 和 Freemarker 混合模版,从这个Shiro Demo开始,我只用 Freemarker ,后期也会出 Freemarker 视频教程。当然为了习惯(只会)JSP的同学,我也双模版配置不修改,您想改成JSP可以直接支持,JSP是以WEB-INF下的view目录为根目录。
七、教程功能详细说明
下面各点是针对(SSM)SpringMvc + Spring + Mybatis框架说明,以及一些使用方式和基本功能介绍。
7.1.1 框架基本介绍
本教程是SSM( SpringMVC +Spring + Mybatis + Freemarker ) + Ehcache 做的整体Shiro Demo
,其他框架需要自己自行解决,所以不做框架 其他 的讲解,其实是大同小异。
7.1.2 分页介绍
本框架里的分页比较Low,分页的ServiceImpl
要继承 BaseMybatisDao<T>
,这里泛型的<T>
为当前实体对象对应的Mapper.xml
文件,其实就是Mapper.xml
的namespace
。调用父类的findPage
相关。
Service Impl Java 代码:
publicPagination<UserRoleAllocationBo> findUserAndRole(ModelMap modelMap,
Integer pageNo,Integer pageSize){
//findUserAndRole : 为查询数据(sqlID)
//findCount : 为查询符合数据的总条数(sqlID)
returnsuper.findPage("findUserAndRole","findCount", modelMap, pageNo, pageSize);
}
分页查询使用缺省的sqlId
。
publicPagination<UPermission> findPage(Map<String,Object> resultMap,Integer pageNo,
Integer pageSize){
/**
* 调用父类的分页查询,默认数据查询sqlId = findAll , count 查询sqlId = findCount
*/
returnsuper.findPage(resultMap, pageNo, pageSize);
}
Mapper.xml 文件 Sql 代码,和上面一 一对应:
<selectid="findCount"resultMap="BaseResultMap">
select count(id) from u_user
<includerefid="where_all"/>
</select>
<!-- 用户权限分配的分页查询 -->
<selectid="findUserAndRole"resultType="com.sojson.permission.bo.UserRoleAllocationBo">
select u.id,u.nickname,u.email,u.create_time,u.last_login_time,u.status ,group_concat(ur.name) roleNames,group_concat(ur.id)roleIds from
u_user u
left join u_user_role uur on uur.uid = u.id
left join u_role ur on ur.id = uur.rid
<where>
<iftest="findContent != null and findContent !='' ">
and (
LOWER(u.nickname) like LOWER(CONCAT("%",#{findContent,jdbcType=VARCHAR},"%")) or
LOWER(u.email) like LOWER(CONCAT("%",#{findContent,jdbcType=VARCHAR},"%"))
)
</if>
</where>
group by u.id
</select>
<selectid="findAll"resultMap="BaseResultMap">
select
<includerefid="Base_Column_List"/>
from u_permission
<includerefid="where_all"/>
<includerefid="limit_sql"/>
</select>
<selectid="findCount"resultMap="BaseResultMap">
select count(id) from u_permission
<includerefid="where_all"/>
</select>
具体请结合代码,Debug
断点看看。
前端HTML页面输出:
<#if page?exists>
<divclass="pagination pull-right">
${page.pageHtml}
</div>
</#if>
Java 分页处理代码:
publicString getPageHtml(){
Map<String,Object> map =newHashMap<String,Object>();
map.put("pageNo",this.getPageNo());
map.put("totalPage",this.getTotalPage());
//从Freemarker 获取分页html
returnnewFreemarker().getTemplate("common/page/html_page.ftl", map);
}
具体分页输出,请看项目中的Freemarker.getTemplate(...)
方法,上个项目是直接拼串输出,这次这里作为一个Demo
处理了下,采用Freemarker
模版输出。
7.1.3 基本结构,以及事务控制
Controller
==> Service
(事务控制层) ==> Dao
==> SqlMapper
==> Mysql 数据库。
对于事务AOP
配置和之前的版本是一致的。事务配置在spring-mybatis.xml
配置文件中。
<tx:adviceid="txAdvice"transaction-manager="transactionManager">
<tx:attributes>
<tx:methodname="publish*"/>
<tx:methodname="save*"/>
<tx:methodname="add*"/>
<tx:methodname="update*"/>
<tx:methodname="insert*"/>
<tx:methodname="create*"/>
<tx:methodname="del*"/>
<tx:methodname="load*"/>
<tx:methodname="init*"/>
<tx:methodname="*"read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- AOP配置-->
<aop:config>
<aop:pointcutid="myPointcut"
expression="execution(public * com.sojson.*.service.impl.*.*(..))"/>
<aop:advisoradvice-ref="txAdvice"pointcut-ref="myPointcut"/>
</aop:config>
事务控制层在ServiceImpl
层。public * com.sojson.*.service.impl.*.*(..)
的意思是, com.sojson.*.service.impl
下所有类的所有方法,而且方法参数不限。符合这些条件的加入事务。
事务的规则是通过方法名前缀来定义的。上面定义了常用的publish
、save
、add
....诸如此类,是走默认事务级别。匹配不中的走readOnly。
所以此处事务配置要注意,如果你修改包路径,这里也要修改,并且要符合规则,要不然会出现找不到数据库的Connection而报错。并且会造成事务无效。
7.1.4 View层Freemarker 配置
View层配置为通用配置,支持 Freemarker 和 JSP 配置,具体配置参考 SpringMvc 配置spring-mvc.xml
。
<!--===============通用视图解析器 begin===============-->
<beanid="viewResolverCommon"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="prefix"value="/WEB-INF/views/"/>
<propertyname="suffix"value=".jsp"/>
<!-- 可为空,方便实现自已的依据扩展名来选择视图解释类的逻辑-->
<propertyname="viewClass">
<value>org.springframework.web.servlet.view.InternalResourceView
</value>
</property>
<propertyname="order"value="1"/>
</bean>
<!-- 视图解析器 -->
<!-- 配置freeMarker视图解析器 -->
<beanid="viewResolverFtl"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<propertyname="viewClass"value="com.sojson.core.freemarker.extend.FreeMarkerViewExtend"/>
<!-- 把Freemarker 扩展一下,把相关属性加入进去。。。 -->
<propertyname="contentType"value="text/html; charset=utf-8"/>
<propertyname="cache"value="true"/>
<propertyname="suffix"value=".ftl"/>
<propertyname="order"value="0"/>
</bean>
<beanid="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<propertyname="order"value="2"></property>
<propertyname="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<propertyname="prefix"value="/WEB-INF/views/"/>
<propertyname="suffix"value=".jsp"></property>
</bean>
<!-- 配置freeMarker 拓展-->
<beanid="freemarkerConfig"
class="com.sojson.core.freemarker.extend.FreeMarkerConfigExtend">
<propertyname="templateLoaderPath">
<value>/WEB-INF/ftl/</value>
</property>
<propertyname="freemarkerVariables">
<map>
<entrykey="xml_escape"value-ref="fmXmlEscape"/>
<entrykey="api"value-ref="api"/>
</map>
</property>
<propertyname="defaultEncoding">
<value>utf-8</value>
</property>
<propertyname="freemarkerSettings">
<props><!-- 315360000 -->
<propkey="template_update_delay">0</prop><!-- Freemarker 模版缓存时间。开发环境不用开启,单位毫秒 -->
<propkey="defaultEncoding">UTF-8</prop>
<propkey="url_escaping_charset">UTF-8</prop>
<propkey="locale">zh_CN</prop>
<propkey="boolean_format">true,false</prop>
<propkey="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<propkey="date_format">yyyy-MM-dd</prop>
<propkey="time_format">HH:mm:ss</prop>
<!-- <prop key="number_format">0.######</prop>-->
<propkey="number_format">#</prop>
<propkey="whitespace_stripping">true</prop>
<propkey="auto_import">
<!-- Freemarker macro 文件应用 -->
/common/config/top.ftl as _top,
/common/config/left.ftl as _left,
/common/config/menu.ftl as _menu
</prop>
</props>
</property>
</bean>
<beanid="fmXmlEscape"class="freemarker.template.utility.XmlEscape"/>
7.1.5 Freemarker API标签封装
采用 Freemarker 的自定义标签功能封装得到使用起来更为简单的方式,下面来一下实列代码。这样的优点,它不是 HTTP 、 TCP 之类的请求,它是Freemarker
在后台组装的时候调用方法而已。这样有利于业务模块抽取和区分。
在spring-mvc.xml
里有如下自动扫描tag
标签类代码:
<!-- 自动扫描 标签 -->
<context:component-scanbase-package="com.sojson.*.*.tag;com.sojson.*.tag"/>
<!-- api 标签功能主类 -->
<beanname="api"class="com.sojson.core.tags.APITemplateModel"></bean>
Java 后台代码:
/**
* 测试封装的 Freemarker 标签代码 。
*/
@Component
publicclassUserInfoTagextendsSuperCustomTag{
//支持注入
@Autowired
UUserService userService;
/**
* 继承父类的方法,必须实现,返回类型为Object,方便使用,这样就可以返回Map、List、以及自己的JavaBean
*/
@Override
protectedObject result(Mapparams){
Long userId = getLong(params,"userId");//从页面取得userId
return userService.selectByPrimaryKey(userId);
}
}
Freemarker 前端使用:
<#--
参数说明:
target:目标tag类,就是上述Java代码中的 com.sojson.user.tag.UserInfoTag 类名首字母小写得到的Tag。
userId:自定义,这样可以定义多个,后台直接从Map获取即可。你可以 a="1",b="2",c="true",诸如此类。
返回值:{outTagName:你的返回值}
-->
<@api target="userInfoTag" userId="1">
<#--
后台返回值接收的Key为:outTagName
Key名称修改的话在 com.sojson.core.statics.Constant.OUT_TAG_NAME
-->
<#if outTagName?exists>
nickname:${outTagName.nickname}
email:${outTagName.email}
最后登录时间:${outTagName.lastLoginTime?string('yyyy-MM-dd HH:mm:ss')}
</#if>
</@api>
7.1.6 多种验证码集成
当我们在注册的时候,需要输入验证码,现在默认配置的动态(gif
)格式验证码。Shiro Demo
的注册页面即可查看。
项目中package:com.sojson.common.utils.vcode
包是验证码的封装包。
并且提供了一个VerifyCodeUtils.java
的验证码工具类。
使用方法参见:CommonController.java
类中的getVCode()
方法和getGifCode()
方法。
八、Shiro Demo中Shro功能相关详细说明
8.1 Shiro + Ehcache 集成
上一个ShiroDemo 采用的Redis,篇前也说了,部分同学对Redis还是比较陌生,所以才有这一版本的出现。
首先Ehcache采用的是2.5版本。Maven引入jar。
<!-- shiro + ehcache jar -->
<dependency>
<artifactId>ehcache-core</artifactId>
<groupId>net.sf.ehcache</groupId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<!-- shiro end -->
Shiro-ehcache
,其实我偏向自己实现比较好管理。 Ehcache 配置文件在resources
目录下发现有2
个Ehcache
相关的config
文件。及ehcache-config.xml
(本项目中的Vcache所使用)和ehcache-shiro.xml
(Shiro Auth相关权限,User Info使用)。
如果你仔细看发现Vcache
缓存工具类,使用的是 Ehcache 的路线,而 Shiro 使用的是Shiro-ehcache
路线,而他们又是同一种缓存,在结合的时候,我碰了几次墙。主要是 cache 的name
不能一样或是没有。
8.2 Shiro 系统级别权限初始加载
为了更详细的介绍,我写了一篇专门介绍这个权限配置加载的相关问题==>
Shiro教程,Shiro 配置文件详细解释,Shiro自定义Filter配置,建议现行看完。
Shiro 权限配置一般使用的有两种,一种是采用注解的方式,在我们的 Controller 方法上,或者Action
方法上写入一些权限判断注解,具体怎么使用,我不做介绍,我主要推荐使用配置的方式。这也是我们现在要讲到的配置方式加载系统基础权限控制,采用对Url
进行控制。
<beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<propertyname="securityManager"ref="securityManager"/>
<propertyname="loginUrl"value="/u/login.shtml"/>
<propertyname="successUrl"value="/"/>
<propertyname="unauthorizedUrl"value="/?login"/>
<!-- 基本系统级别权限配置-->
<propertyname="filterChainDefinitions">
<value>
/page/login.jsp = anon <!-- 登录相关不拦截 -->
/page/register/* = anon
/page/index.jsp = authc
/page/addItem* = authc,roles[数据管理员]
/page/file* = authc,roleOR[普通用户,数据管理员]
/page/listItems* = authc,roleOR[数据管理员,普通用户]
/page/showItem* = authc,roleOR[数据管理员,普通用户]
/page/updateItem*=authc,roles[数据管理员]
/** = anon <!-- 其他不拦截 -->
</value>
</property>
<!-- 自定义shiro 的 Filter -->
<propertyname="filters">
<util:map>
<entrykey="login"value-ref="login"></entry>
</util:map>
</property>
</bean>
通常采用以上配置,不过本教程采用动态加载方式,就是从数据库获取,从配置文件获取等方式,也就是你可以把权限存储一部分放在数据库,一部分存储到配置文件,然后修改更新。本教程是如下方式加载(粘贴代码去掉“\”
)。
<propertyname="filterChainDefinitions"value="#\{shiroManager.loadFilterChainDefinitions()\}"/>
配置文件加载相关看这里:
1. Shiro教程(十)Shiro 权限动态加载与配置精细讲解。
2. Shiro教程,Shiro 配置文件详细解释,Shiro自定义Filter配置
3. Java有序读取配置文件,有序读取ini配置文件 (这个我为什么放在这里说呢,因为好多人都不知道我们平常用的*.properties
配置是无序的,而我们现在加载要有序)
8.3 会话Session 相关
8.3.1 会话Session ID生成器
这个Session ID
生成器,在当前Demo
里采用的是 UUID 去掉“-”
,其实我们平常用的也是 UUID 。如果你想重写继承org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator
然后重写即可,我也重写了,只是做了一下小改动,就是把 UUID 的“-”
去掉了。
publicclassSOSJONSessionIdGeneratorextendsJavaUuidSessionIdGenerator{
publicfinalstaticString SID_KEY ="uid:%s:%s";
@Override
publicSerializable generateId(Session session){
//这里只是做一个小小的演示,具体功能你可以自己丰富。
String sid =super.generateId(session).toString();
return sid.replaceAll("-","");
}
}
8.3.2 会话Cookie模板
这里重要的一点是domain属性,它是配置Session的Cookie存储的域,如果不配置默认存储当前域,如果你是域名方式,最好是配置。掌握了这一点,在相同一级域名下做单点登录就好做多了。原理其实很简单。
如下配置,我把domain
配置为.sojson.com
,意思为不管是sojson.com
下的几级域名都对一级域名下有读写权限,换种说法不管是www.sojson.com
,admin.sojson.com
、 user.admin.sojson.com
都可以获取到这个 Cookie 值,懂了吗?
<!-- 会话Cookie模板 -->
<beanid="sessionIdCookie"class="org.apache.shiro.web.servlet.SimpleCookie">
<!--cookie的name,我故意取名叫xxxxbaidu -->
<constructor-argvalue="sid_demo"/>
<propertyname="httpOnly"value="true"/>
<!--cookie的有效时间 -->
<propertyname="maxAge"value="-1"/>
<!-- 配置存储Session Cookie的domain为 一级域名
<property name="domain" value=".sojson.com"/>
-->
</bean>
如果对一级域名、二级域名等不了解,请看这篇介绍:单个项目多个二级域名简单实现思路 。
Shiro简单解决多个二级域名的单点登录,请看这里详细介绍:Shiro 通过配置Cookie 解决多个二级域名的单点登录问题。
自主实现CAS(单点登录)思路设计:N多系统单点登录,实现、解决方案。四种解决方案 。
8.4 Shiro Remember Me(记住我)
当我们经常遇到一些网站,在登录的时候有一个勾选(checkbox
)记住我选项,当你勾选后一段时间再次访问,你还是登录状态,感觉有点神奇,当然在你技术一定的积累上,你自己也可以实现,我也曾经实现过,还是总是不那么完美。Shiro Remember Me
就是这个功能。
Remember Me Cookie配置:
<!-- 用户信息记住我功能的相关配置 -->
<beanid="rememberMeCookie"class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-argvalue="rememberMe"/>
<propertyname="httpOnly"value="true"/>
<!-- 配置存储rememberMe Cookie的domain为 一级域名 这里如果配置需要和Session回话一致更好。
<property name="domain" value=".sojson.com"/>
-->
<propertyname="maxAge"value="2592000"/><!-- 30天时间,记住我30天 -->
</bean>
记住上面的一个注释:配置存储rememberMe Cookie
的domain
为 一级域名, 这里如果配置需要和Session回话一致更好。
如此配置后,在Java Login
的时候,设置Remember Me
为true
就会有此效果。
ShiroToken token =newShiroToken(user.getEmail(), user.getPswd());
//这里如果是true,下次会记住登录,有效时间是在spring-shiro.xml中的rememberMeCookie配置中的有效期。
token.setRememberMe(rememberMe);
SecurityUtils.getSubject().login(token);
Remember Me 加密Key配置:
<!-- rememberMe管理器 -->
<beanid="rememberMeManager"class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<propertyname="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('3AvVhmFLUs0KTA3Kprsdag==')}"/>
<propertyname="cookie"ref="rememberMeCookie"/>
</bean>
8.5 Shiro Session 配置
我们看过Shiro Session(org.apache.shiro.session)源码的同学返现一个情况,Shiro的Session和HttpSession没有关系。实际Shiro 是用Session 代理了HttpSession。
而对Session管理配置文件中配置较多,请看源代码比较妥当,基本都有注释:
<!-- custom shiro session listener -->
<beanid="customShiroSessionDAO"class="com.sojson.core.shiro.CustomShiroSessionDAO">
<propertyname="shiroSessionRepository"ref="sessionShiroCacheManager"/>
<propertyname="sessionIdGenerator"ref="sessionIdGenerator"/>
</bean>
<!-- 手动操作Session,管理Session -->
<beanid="customSessionManager"class="com.sojson.core.shiro.session.CustomSessionManager">
<propertyname="shiroSessionRepository"ref="sessionShiroCacheManager"/>
<propertyname="customShiroSessionDAO"ref="customShiroSessionDAO"/>
</bean>
<!-- Session Manager -->
<beanid="sessionManager"class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 相隔多久检查一次session的有效性 -->
<propertyname="sessionValidationInterval"value="1800000"/>
<!-- session 有效时间为半小时 (毫秒单位)-->
<propertyname="globalSessionTimeout"value="1800000"/>
<propertyname="sessionDAO"ref="customShiroSessionDAO"/>
<!-- session 监听,可以多个。 -->
<propertyname="sessionListeners">
<list>
<refbean="customSessionListener"/>
</list>
</property>
<!-- 间隔多少时间检查,不配置是60分钟 -->
<propertyname="sessionValidationScheduler"ref="sessionValidationScheduler"/>
<!-- 是否开启 检测,默认开启 -->
<propertyname="sessionValidationSchedulerEnabled"value="true"/>
<!-- 是否删除无效的,默认也是开启 -->
<propertyname="deleteInvalidSessions"value="true"/>
<propertyname="sessionIdCookie"ref="sessionIdCookie"/>
</bean>
<!-- session 创建、删除、查询 -->
<beanid="sessionShiroCacheManager"class="com.sojson.core.shiro.cache.impl.SessionShiroCacheManager">
<propertyname="ehCacheManager"ref="ehCacheManager"/>
</bean>
<!-- 会话验证调度器 -->
<beanid="sessionValidationScheduler"class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
<!-- 间隔多少时间检查,不配置是60分钟 -->
<propertyname="interval"value="${session.validate.timespan}"/>
<propertyname="sessionManager"ref="sessionManager"/>
</bean>
8.6 小插曲——静态注入
这里还是讲一下Spring提供的静态注入,要不然怕你看到这里不知道这是什么意思,其实当你了解Spring的静态注入后,你会发现你很多地方用得着,就不用去getBean("name")了,直接静态注入即可。spring-shiro.xml中用到了,如下:
<!-- 静态注入,相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<beanclass="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<propertyname="staticMethod"value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<propertyname="arguments"ref="securityManager"/>
</bean>
静态注入详细讲解:Spring 静态注入讲解(MethodInvokingFactoryBean)
九、Shiro Ajax 请求拦截、跳转方案
我们知道Ajax不能做页面redirect
和forward
跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出或是 Session 超时了。这个时候如何解决?
@Override
protectedboolean isAccessAllowed(ServletRequest request,
ServletResponse response,Object mappedValue)throwsException{
UUser token =TokenManager.getToken();
if(null!= token || isLoginRequest(request, response)){// &&// isEnabled()
returnBoolean.TRUE;
}
if(ShiroFilterUtils.isAjax(request)){// ajax请求
Map<String,String> resultMap =newHashMap<String,String>();
LoggerUtils.debug(getClass(),"当前用户没有登录,并且是Ajax请求!");
resultMap.put("login_status","300");
resultMap.put("message",
"\u5F53\u524D\u7528\u6237\u6CA1\u6709\u767B\u5F55\uFF01");// 当前用户没有登录!
ShiroFilterUtils.out(response, resultMap);
}
returnBoolean.FALSE;
}
以上代码和教程代码一致,类路径com.sojson.core.shiro.filter.LoginFilter
详细说明,请看这篇:Shiro 教程,Ajax请求拦截跳转页面方案。
十、Freemarker for Shiro 标签使用
首先Jar包的引入,可能有点偏门,如果你对 Shiro 稍微熟练,又对 Shiro 自定义标签熟悉的话,可以自己写一套。 Maven 引入:
<!-- freemarker + shiro(标签) begin -->
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>0.1</version>
<scope>private</scope>
</dependency>
<!-- freemarker + shiro(标签) begin -->
具体用法请参考这:Shiro教程(八)Shiro Freemarker标签的使用,这里就不做过多叙述了。具体看项目Freemarker
中的使用。
十一、JSP for Shiro 标签使用
虽然当前Shiro Demo没有使用JSP,但是鉴于用JSP的同学较多,而当前项目又支持JSP,那么在这里还是提一下。
首先在JSP顶部像引入JSTL相关库一样的这么引入:
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
如果报错,或者是飘红,那么请你首先把这个链接复制到浏览器,看是否能打开,如果能打开,那么就剪切、粘贴、剪切、粘贴重复保存几次看看。
JSP Shiro标签详细讲解:Shiro教程(九)Shiro JSP标签的使用。
十二、Shiro 登录后获取登录之前的最后一个访问地址
这个功能乍一看,不懂什么意思,先说明这个功能。下面来举栗子:
场景1:当我访问http://www.sojson.com/admin.shtml
的时候,它是一个要登录的页面,那么 Shiro 控制它跳转到登录页,那么登录完毕了呢?应该怎么跳转,可能很多人会想跳转到项目的首页,那要是我继续跳转到原来访问的页面http://www.sojson.com/admin.shtml
呢?是不是用户体验瞬间上升了?
场景2:当当前页面Session失效的时候,跳转到登录,然后应该跳转到哪里?应该是最后操作的页面吧,其实和上面差不多。
其实我们平时非Shiro的项目也可以获取,但是可能用起来不是那么好用,方法如下:
//上一个浏览的非Ajax的地址,在登录后,取得地址,如果不为null,那么就跳转过去。
String url =(String) request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE);
上面的WebUtils
是 Shiro 里面的工具类,但是这个变量却不是Shiro的,它是:javax.servlet.forward.request_uri
,如果不是 Shiro 项目,你直接用这个“javax.servlet.forward.request_uri”
即可。
Shiro 获取上一个请求的信息方式:
/**
当Shiro 的Filter拦截请求后,如果不满足条件(return false),就会走这个方法
*/
@Override
protectedboolean onAccessDenied(ServletRequest request,ServletResponse response)
throwsException{
//保存Request和Response,登录后可以取到
saveRequestAndRedirectToLogin(request, response);
returnBoolean.FALSE ;
}
//登录后,取到之前的Request中的一些信息。
SavedRequest saveRequest =WebUtils.getSavedRequest(request);
saveRequest.getMethod();//之前的请求方法
saveRequest.getQueryString();//之前请求的条件
saveRequest.getRequestURI();//之前请求的路径
saveRequest.getRequestUrl();//之前请求的全路径
十三、禁止用户登录
这个功能其实是一个改变用户数据库表里的一个字段,本Demo中:1:有效,0:禁止登录
然后踢出用户登录状态。代码详细请查看CustomSessionManager.java
类的forbidUserById(Long id, Long status)
方法。
而再次登录的话,需要再登录,而登录的地方限制了用户状态为(0:禁止登录)的用户登录。
Java具体实现代码:
/**
* 查询要禁用的用户是否在线。
* @param id 用户ID
* @param status 用户状态
*/
publicvoid 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());
//标记用户Session
SessionStatus sessionStatus =(SessionStatus) session.getAttribute(SESSION_STATUS);
//是否踢出 true:有效,false:踢出。
sessionStatus.setOnlineStatus(status.intValue()==1);
//更新Session
customShiroSessionDAO.update(session);
}
}
}
十四、在线有效Session显示,在线Session踢出
从 Ehcache 获取所有的 Session ,然后获取有效Session
,也就是包含用户信息的Session
,然后进行页面展示,这里没做分页,支持踢出、激活功能,踢出后当对方再次操作的时候,会提示“你已经被踢出,请重新登录”,下图展示功能。
点击上方“踢出”功能后,不管你点击哪个链接,或者是刷新当前页面,都会提示如下页面。
踢出后,不能直接退出,要不然用户感觉莫名其妙。所有增加了一个Filter。SimpleAuthFilter.java
如果标记为踢出,会提示用户。具体查看源码以及配合项目的使用。
这个看情况,如果你需要用户直接跳转到登录页面去,那么你直接执行获取用户然后 logout
即可。
十五、用户密码修改
用户密码修改功能很简单,但是有同学转不过弯来,首先密码修改常规做法基本就这2种 :
- 先输入原来的密码,然后输入新密码,先校验原来的密码是正确的才更新新密码。
- 直接输入新密码或者根据密码保护填写正确直接更新密码。
但是有人转不过弯来,我怎么校验他原来的密码是否正确,我们校验密码和登录是一样的,按你注册的时候加密的规则加密他输入的老密码,然后进行对比,正确方可修改,和登录功能是一样样的。
Java代码实现:
/**
* 个人资料修改
* @return
*/
@RequestMapping(value="updateSelf",method=RequestMethod.POST)
@ResponseBody
publicMap<String,Object> updateSelf(UUser entity){
try{
userService.updateByPrimaryKeySelective(entity);
resultMap.put("status",200);
resultMap.put("message","修改成功!");
/**
* 重新帮用户登录一次,可以显示最新的资料
*/
UUser token =TokenManager.getToken();
Boolean rememberMe = token.getRememberMe();
token = userService.selectByPrimaryKey(token.getId());
//修改之后再Login一下,覆盖现有登录信息
TokenManager.login(token, rememberMe);
}catch(Exception e){
resultMap.put("status",500);
resultMap.put("message","修改失败!");
LoggerUtils.fmtError(getClass(), e,"修改个人资料出错。[%s]",JSONObject.fromObject(entity).toString());
}
return resultMap;
}
十六、权限管理
16.1 权限结构设计(RBAC3)
权限设计基于RBAC3的结构,即权限赋予角色,角色赋予用户的结构。如下图:
对RBAC的理解:RBAC 介绍,结合案例讲解。
16.2 权限增删改查
当前Demo 依赖于URL控制权限,如下图:
十七、角色的增删改查,以及赋予权限
17.1 角色的增删改查
17.2 权限分配给角色
十八、对用户赋予角色
当我们把相关权限赋予给角色哦,那每个用户对应到哪些权限?其实这里就是 RBAC3 的宗旨,一切基于角色,也就是所有的用户只能赋予角色,这个和生活中是一样的,下面举个栗子。
比如:老王是某公司的技术总监,而且同时是规划部的部长,那老王就是有2个角色,或者说是3个角色,都有公司员工、技术总监、规划部长,那老王就拥有这3个角色的权限。这样懂了吧。
十九、管理员权限自动添加
为什么有这个需求?管理员的权限控制,无非有2种,一是对管理员不做控制,二是对管理员拥有所有权限。而我们系统就是第二种。
每当系统添加了一个新的权限,管理员的角色自动补充一条记录,这样促使管理员角色拥有所有权限。
publicUPermission insertSelective(UPermission record){
//添加一条权限记录
permissionMapper.insertSelective(record);
//每添加一个权限,都往【系统管理员 888888】里添加一次。保证系统管理员有最大的权限
executePermission(newLong(1),String.valueOf(record.getId()));
return record;
}
二十、Shiro Demo 项目下载
Shiro Demo 线上展示地址:http://demo.www.sojson.com/shiro.ehcache/
如果你不是Maven项目,请在此处下载Jar包:http://pan.baidu.com/s/1geLHXS3 密码: puym
项目源码下载:还没提交。
相关推荐
本节主要介绍如何基于SSM(Spring、SpringMVC、MyBatis)框架搭建一个简单的Web应用程序,并实现一个HelloWorld示例。 **1. 导入必要的依赖** - **SpringMVC**: MVC(Model-View-Controller)设计模式的实现之一,...
# SSM-WMS基于SSM(Spring+SpringMVC+MyBatis)框架开发的仓库管理系统 开发中 数据库 Mysql <br>后端 Spring SpringMVC MyBatis Slf4j Log4j Shiro Ehcache Fastjson <br>...
基于SpringMVC+Spring+MyBatis (SSM) 架构的高效率便捷开发框架源码+项目说明.zip 本项目是一个整合 **SpringMVC+Spring+MyBatis(SSM)** 框架的 **Demo**。 拥有高效率便捷开发模式,使开发人员更专注于业务,...
Java基于SSM(Spring+SpringMVC+MyBatis)在线考试系统是一个综合性的Web应用程序,它利用了Java技术栈中的三大核心框架:Spring、SpringMVC和MyBatis,来构建一个高效、灵活且可扩展的考试平台。这个系统的实现涉及...
SpringMVC、MyBatis、Shiro 和 EasyUI 是四个在Web开发中广泛使用的开源框架。这个项目结合了它们的功能,构建了一个强大的后端服务和前端界面系统。 **SpringMVC** 是Spring框架的一部分,是一个模型-视图-控制器...
`SpringMVC`是基于`Spring Framework`的模型视图控制器,用于构建Web应用程序。它提供了一个灵活的请求处理机制,支持多种视图技术,并且能够很好地与其他Spring组件集成,如`AOP`(面向切面编程)和`IoC`(依赖注入)。...
Spring MVC+Mybatis+Ehcache+Apache Shiro+Bootstrap整合开发java仓库管理系统源码 开发环境:Eclipse ,JDK 1.8 ,Tomcat7 技术选型 后端技术 SpringMVC MVC框架 Spring Framework 容器 Apache Shiro 安全框架...
【项目介绍】:基于SSM框架的在线英语学习 注意点: 技术框架:Spring +SpringMVC+MyBatis+Shiro+BootStrap jdk:1.81 Tomcat 8 Maven github #chenyinghui 修改记录: 1.将user表的username属性字段改成unique #...
在Java开发中,Shiro常与Spring(SSM:Spring、SpringMVC、MyBatis)框架进行整合,以实现更高效的安全管理。本文将详细介绍如何整合Shiro与SSM,并探讨涉及的相关技术。 首先,我们来看`ehcache-core`这个标签。...
SSM框架,全称Spring、SpringMVC和MyBatis,是Java开发中常见的Web应用程序框架组合。这个框架集合提供了模型-视图-控制器(MVC)架构,并且集成了强大的持久层解决方案MyBatis和Spring的依赖注入、事务管理等功能。...
SSM(Spring、SpringMVC、MyBatis)与Shiro、Redis、Ecache整合是构建大型企业级Java应用的常见技术栈。这个压缩包文件的内容可能是为了演示或教学如何在项目中集成这些组件,实现用户认证和权限管理,并利用缓存...
【SSM+ehcache-Jar包】整合了四个核心组件:Spring、SpringMVC、Mybatis和Ehcache,这是一套常见的Java企业级开发框架集合。这些组件各自扮演着不同的角色,共同构建了一个高效、灵活的Web应用程序。 1. **Spring**...
该系统在技术上使用Spring+SpringMVC+MyBatis整合框架作为后台开发框架,AmazeUI作为前端开发框架。 并使用Ehcache作为项目的缓存,druid作为项目的数据库连接池,使用FreeMarker实现word的导出,使用Shiro完成项目...
该系统在技术上使用 Spring+SpringMVC+MyBatis 整合框架作为后台开发框架,AmazeUI 作为前端开发框架。 并使用 Ehcache 作为项目的缓存,druid 作为项目的数据库连接池,使用 FreeMarker 实现 word 的导出,使用 ...
本系统基于流行的Java开发框架SSM(Spring、SpringMVC、MyBatis)和关系型数据库MySQL,旨在提供一个高效、稳定且易用的知识产权管理系统。以下将详细解析该系统的架构设计、核心功能以及技术实现。 1. **系统架构*...
SSM(Spring、SpringMVC、MyBatis)是一个经典的Java web开发框架组合,它将Spring的核心容器、Spring的Web MVC模块以及MyBatis持久层框架整合在一起,为开发者提供了强大的服务。EasyUI则是一个基于jQuery的UI组件...
- **简介**:SSM框架是Java Web开发中常用的轻量级框架组合,分别代表SpringMVC作为控制器层、MyBatis作为持久层框架以及Spring作为业务逻辑层容器。 - **优势**:该框架组合具有高度解耦、易于测试、灵活配置等优点...
SSM+MySQL在线读书与分享论坛是一个典型的Java Web项目,主要使用了Spring、SpringMVC和MyBatis三个框架,以及MySQL作为后端数据库。这个项目对于初学者来说,是理解企业级应用开发流程和实践Java Web技术的好案例。...
SSM(Spring、SpringMVC、MyBatis)与Apache Shiro的整合是Java Web开发中常见的权限管理框架。在“ssm-shiro权限管理(二)”中,我们将深入探讨如何在Shiro的基础上进一步优化登录流程、实现缓存管理和动态权限...
该系统在技术上使用Spring+SpringMVC+MyBatis整合框架作为后台开发框架,AmazeUI作为前端开发框架。 并使用Ehcache作为项目的缓存,druid作为项目的数据库连接池,使用FreeMarker实现word的导出,使用Shiro完成项目...