码云例子:https://gitee.com/luxins/shiroDemo1.git
1.简介
• Apache Shiro 是 Java 的一个安全(权限)框架。
• Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在
JavaSE 环境,也可以用在 JavaEE 环境。
• Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存
等。
• 下载:http://shiro.apache.org/
2.功能简介
• 基本功能点如下图所示:
• Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
• Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用
户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户
对某个资源是否具有某个权限;
• Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有
信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
• Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
• Web Support:Web 支持,可以非常容易的集成到Web 环境;
• Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可
以提高效率;
• Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能
• 把权限自动传播过去;
• Testing:提供测试支持;
• Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
• Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3.Shiro 架构 (Shiro外部来看)
• 从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成
工作:
• Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外
API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定
是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,
机器人等;与 Subject 的所有交互都会委托给 SecurityManager;
Subject 其实是一个门面,SecurityManager 才是实际的执行者;
• SecurityManager:安全管理器;即所有与安全有关的操作都会与
SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro
的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中
DispatcherServlet 的角色
• Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说
SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户
进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/
权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
4.Shiro 架构 (Shiro内部来看)
• Subject:任何可以与应用交互的“用户”;
• SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;
所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进
行认证、授权、会话及缓存的管理。
• Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证
策略(Authentication Strategy),即什么情况下算用户认证通过了;
• Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控
制着用户能访问应用中的哪些功能;
• Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体
的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要
实现自己的 Realm;
• SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web
环境,也可以用在如普通的 JavaSE 环境
• CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据
基本上很少改变,放到缓存中后可以提高访问的性能
• Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。
与Web 集成
• Shiro 提供了与 Web 集成的支持,其通过一个
ShiroFilter 入口来拦截需要安全控制的URL,然后
进行相应的控制
• ShiroFilter 类似于如 Strut2/SpringMVC 这种
web 框架的前端控制器,是安全控制的入口点,其
负责读取配置(如ini 配置文件),然后判断URL
是否需要登录/权限等工作。
ShiroFilter:
部分细节
• [urls] 部分的配置,其格式是: “url=拦截器[参数],拦截
器[参数]”;
• 如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会
执行其配置的拦截器。
• anon(anonymous) 拦截器表示匿名访问(即不需要登
录即可访问)
• authc (authentication)拦截器表示需要身份认证通过后
才能访问
shiro的默认过滤器
URL 匹配模式
• url 模式使用 Ant 风格模式
• Ant 路径通配符支持 ?、*、**,注意通配符匹配不
包括目录分隔符“/”:
– ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不
匹配 /admin 或 /admin/;
– *:匹配零个或多个字符串,如 /admin 将匹配 /admin、
/admin123,但不匹配 /admin/1;
– :匹配路径中的零个或多个路径,如 /admin/ 将匹
配 /admin/a 或 /admin/a/b
URL 匹配顺序
• URL 权限采取第一次匹配优先的 方式,即从头开始
使用第一个匹配的 url 模式对应的拦截器链。
• 如:
– /bb/=filter1
– /bb/aa=filter2
– /=filter3
– 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹
配,那么将使用 filter1 进行拦截。
认证/登录(先做整合后再测试)
为啥使用MD5盐值加密:
在doGetAuthenticationInfo的返回值创建SimpleAuthenticationInfo 对象的时候需要使用比较复杂的构造器 SimpleAuthenticationInfo Info=new SimpleAuthenticationInfo(username, credentials,salt,getName());盐值需要唯一:一般使用随机字符串或者userId;
使用
ByteSource salt=ByteSource.Util.bytes(username);//盐
Object simpleHash = new SimpleHash("MD5","123",salt,1024);进行计算加密后的值.
密码比对是通过AuthorizingRealm 的credentialsMatcher属性进行比对token中的和SimpleAuthenticationInfo
1.获取当前subject对象
Subject subject= SecurityUtils.getSubject();
2.测试当前用户是否认证,即是否登录了,调用subject的isAuthenticated();
boolean authenticated = subject.isAuthenticated();
3.若当前没有认证,则把用户名密码封装为UsernamePasswordToken对象
1).创建一个表单页面
2).把请求提交到SpringMVC的handler
3)获取用户名密码
4.代用subject的login(AuthenticationToken token)方法执行登录
@RequestMapping("/login")
public String add(User u){
Subject subject= SecurityUtils.getSubject();
if(!subject.isAuthenticated()){
UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(u.getName(),u.getAge());
usernamePasswordToken.setRememberMe(true);
try {
subject.login(usernamePasswordToken);
}catch (UnknownAccountException e){
System.out.println("没有该账户");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}catch (LockedAccountException e){
System.out.println("账户被锁定");
}catch (Exception e){
System.out.println("登录失败");
}
}
return "index";
}
5.自定义realm的方法,从数据库中获取相应的数据,返回给给shiro
1).extends AuthorizingRealm
2).实现方法 doGetAuthenticationInfo(AuthenticationToken authenticationToken)
6.由shiro进行密码比对
public class ShiroRealm extends AuthorizingRealm {
@Override
public String getName() {
return super.getName();
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("-----------"+authenticationToken);
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
System.out.println("------->"+username);
String s = token.getPassword().toString();
//加盐
if ("123".equals(username)){
throw new UnknownAccountException("用户不存在");
}if ("123456".equals(username)){
throw new IncorrectCredentialsException("密码错误");
}
//SimpleAuthenticationInfo 中传入的是数据库中查询的username和password
SimpleAuthenticationInfo Info=new SimpleAuthenticationInfo(username, "64c8b1e43d8ba3115ab40bcea57f010b", getName());
//如果加盐
String credentials="";
if ("123".equals(username)){
throw new UnknownAccountException("用户不存在");
}if ("123456".equals(username)){
throw new IncorrectCredentialsException("密码错误");
}if ("admin".equals(username)){
credentials="c41d7c66e1b8404545aa3a0ece2006ac";
}
//加盐
ByteSource salt=ByteSource.Util.bytes(username);//盐
//SimpleAuthenticationInfo中封装的是从数据库中查询的的username和password
SimpleAuthenticationInfo Info=new SimpleAuthenticationInfo(username, credentials,salt,getName());
return Info;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/*
* 1.继承AuthorizingRealm类,并且实现doGetAuthorizationInfo方法
* 2.AuthorizingRealm继承类AuthenticatingRealm,但是没有实现AuthenticatingRealm中的doGetAuthorizationInfo
* 方法,所以认证和授权只需要继承AuthorizingRealm,同时实现它的两个抽象方法.
*
* */
/*
* 1.从PrincipalCollection获取登录的信息
* 2.从登录信息中获取当前用户的角色和权限(可能会查询数据库)
* 3.创建SimpleAuthorizationInfo对象,并设置roles;
* 4.返回SimpleAuthorizationInfo对象
* */
Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
Set<String> set=new HashSet<>();
if ("user".equals(primaryPrincipal)){
set.add("user");
}
if("admin".equals(primaryPrincipal)){
set.add("admin");
}
SimpleAuthorizationInfo s=new SimpleAuthorizationInfo(set);
return s;
}
//MD5加密
//1.如何把字符串加密为md5
//2.替换realm的CredentialMatcher属性,直接使用HashCredentialMatcher对象,并设置加密方式即可.
将shiro配置文件的自己的realm的bean修改
<bean id="jdbcRealm" class="com.lx.shiro.realms.ShiroRealm">
<!--配置MD5加密-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
//加密方式
<property name="hashAlgorithmName" value="MD5"></property>
//加密次数
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
取得MD5加密后的字符串:null位置为盐;
public static void main(String[] args) {
//如果需要加盐
//加盐
ByteSource salt=ByteSource.Util.bytes(username);//盐
Object simpleHash = new SimpleHash("MD5","123",null,1024);
System.out.println(simpleHash);
}
}
页面
<%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %>
<html>
<body>
<h2>Hello World!</h2>
<form action="/user/login" method="post">
<input type="text" placeholder="name" name="name">
<input type="text" placeholder="pass" name="age">
<input type="submit" value="Login">
</form>
</body>
</html>
index.jsp
<%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8" %>
<%@taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<html>
<body>
<h2>index html!</h2>
welcome:<shiro:principal> </shiro:principal>
<shiro:hasRole name="admin">
<a href="/admin.jsp">Admin.jsp</a>
</shiro:hasRole>
<shiro:hasRole name="user">
<a href="/user.jsp">User.jsp</a>
</shiro:hasRole>
<a href="/user/logout">注 销</a>
</body>
</html>
unauthorized.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<html>
<body>
<h2>没有权限</h2>
</body>
</html>
其他页面都是空白
身份验证
• 身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身
份,如提供 email,用户名/密码来证明。
• 在 shiro 中,用户需要提供 principals (身份)和 credentials(证
明)给 shiro,从而应用能验证用户身份:
• principals:身份,即主体的标识属性,可以是任何属性,如用户名、
邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个
Primary principals,一般是用户名/邮箱/手机号。
• credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证
书等。
• 最常见的 principals 和 credentials 组合就是用户名/密码了
身份验证基本流程
• 1、收集用户身份/凭证,即如用户名/密码
• 2、调用 Subject.login 进行登录,如果失败将得到相应
的 AuthenticationException 异常,根据异常提示用户
错误信息;否则登录成功
• 3、创建自定义的 Realm 类,继承
org.apache.shiro.realm.AuthorizingRealm 类,实现
doGetAuthenticationInfo() 方法
身份验证示例
AuthenticationException
• 如果身份验证失败请捕获 AuthenticationException 或其子类
• 最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,
防止一些恶意用户非法扫描帐号库;
身份认证流程
• 1、首先调用 Subject.login(token) 进行登录,其会自动委托给
SecurityManager
• 2、SecurityManager 负责真正的身份验证逻辑;它会委托给
Authenticator 进行身份验证;
• 3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份
认证入口点,此处可以自定义插入自己的实现;
• 4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进
行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用
AuthenticationStrategy 进行多 Realm 身份验证;
• 5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取
身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处
可以配置多个Realm,将按照相应的顺序及策略进行访问。
Realm
• Realm:Shiro 从 Realm 获取安全数据(如用户、角色、
权限),即 SecurityManager 要验证用户身份,那么它需
要从 Realm 获取相应的用户进行比较以确定用户身份是否
合法;也需要从Realm得到用户相应的角色/权限进行验证
用户是否能进行操作
• Realm接口如下
• 一般继承 AuthorizingRealm(授权)即可;其继承了
AuthenticatingRealm(即身份验证),而且也间接继承了
CachingRealm(带有缓存实现)。
• Realm 的继承关系:
Authenticator
• Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验
证核心的入口点:如果验证成功,将返回AuthenticationInfo 验
证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应
的 AuthenticationException 异常
• SecurityManager 接口继承了 Authenticator,另外还有一个
ModularRealmAuthenticator实现,其委托给多个Realm 进行
验证,验证规则通过 AuthenticationStrategy 接口指定
AuthenticationStrategy
• AuthenticationStrategy 接口的默认实现:
• FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第
一个 Realm 身份验证成功的认证信息,其他的忽略;
• AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和
FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信
息;
• AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有
Realm身份验证成功的认证信息,如果有一个失败就失败了。
• ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy
策略
注销
配置shiro
Controller
页面
授权
• 授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作
等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限
(Permission)、角色(Role)。
• 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权
后才允许访问相应的资源。
• 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些
数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
• 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户
有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用
户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控
制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允
许。
• Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,
即实例级别的)
• 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有
一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等
都是角色,不同的角色拥有一组不同的权限。
Shiro 支持三种方式的授权:
– 编程式:通过写if/else 授权代码块完成
– 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相
应的异常
– JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成
默认拦截器
• Shiro 内置了很多默认的拦截器,比如身份验证、授权等
相关的。默认拦截器可以参考
org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举
拦截器:
Permissions
• 规则: 资源 标识符:操作:对象实例 例 ID 即对哪个资源的哪个
实例可以进行什么操作. 其 默认支持通配符权限字符串,: 表
示 资源/ / 操作/ / 实例的分割;, 表示 操作的 分割,* 表示 任意资
源/ / 操作/ / 实例。
• 多层次管理:
– 例如:user:query、user:edit
– 冒号 是一个特殊字符,它用来分隔权限字符串的下一 部件:第一部分
是权限被操作的领域(打印机),第二部分是被执行的操作。
– 多个值: 每个 部件能够保护多个值。因此,除了授予用户 user:query
和 user:edit 权限外,也 可以 简单地授予他们一 个: user: query , edit
– 还可以用 用 * 号 代替所有的值,如:user:* , 也可以写:*:query,表示
某个用户在所有的领域都有 query 的权限
Shiro 的 Permissions
• 实例 级访问控制
– 这种情况通常会使用三个部件: 域、操作、被付诸实
施的实例。如:user:edit:manager
– 也 可以使用通配符来定义,如:user:edit:、user::、
user::manager
– 部分 省略 通配符:缺少的部件意味着用户可以访问所
有与之匹配的值,比如:user:edit 等价于 user:edit :、
user 等价于 user:?
– 注意: 通配符只能 从字符串的结尾处省略部件,也就
是说 user:edit 并不等价于 user:*:edit
授权流程
流程如下:
• 1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给
SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
• 2、Authorizer是真正的授权者,如果调用如
isPermitted(“user:view”),其首先会通过
• PermissionResolver 把字符串转换成相应的 Permission 实例;
• 3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角
色/权限用于匹配传入的角色/权限;
• 4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果
有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,
如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示
授权失败。
ModularRealmAuthorizer
• ModularRealmAuthorizer 进行多 Realm 匹配流程:
– 1、首先检查相应的 Realm 是否实现了实现了Authorizer;
– 2、如果实现了 Authorizer,那么接着调用其相应的
isPermitted*/hasRole* 接口进行匹配;
– 3、如果有一个Realm匹配那么将返回 true,否则返回 false。
Shiro 标签
• Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如
根据登录用户显示相应的页面按钮。
• guest 标签:用户没有身份验证时显示相应信息,即游客
访问信息:
• user 标签:用户已经经过认证/记住我登录后显示相应的信
息。
• authenticated 标签:用户已经身份验证通过,即
Subject.login登录成功,不是记住我登录的
• notAuthenticated 标签:用户未进行身份验证,即没有调
用Subject.login进行登录,包括记住我自动登录的也属于
未进行身份验证。
• pincipal 标签:显示用户身份信息,默认调用
Subject.getPrincipal() 获取,即 Primary Principal。
• hasRole 标签:如果当前 Subject 有角色将显示 body 体内
容:
• hasAnyRoles 标签:如果当前Subject有任意一个
角色(或的关系)将显示body体内容。
• lacksRole:如果当前 Subject 没有角色将显
示 body 体内容
• hasPermission:如果当前 Subject 有权限
将显示 body 体内容
• lacksPermission:如果当前Subject没有权
限将显示body体内容
权限注解
• @RequiresAuthentication:表示当前Subject已经通过login
进行了身份验证;即 Subject. isAuthenticated() 返回 true
• @RequiresUser:表示当前 Subject 已经身份验证或者通过记
住我登录的。
• @RequiresGuest:表示当前Subject没有身份验证或通过记住
我登录过,即是游客身份。
• @RequiresRoles(value={“admin”, “user”}, logical=
Logical.AND):表示当前 Subject 需要角色 admin 和user
• @RequiresPermissions (value={“user:a”, “user:b”},
logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
user:b。
真实中进行授权
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/list.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
<!--配置哪些页面需要受保护.-->
<!--以及访问这些页面需要的权限.-->
<!--1). anon 可以被匿名访问-->
<!--2). authc 必须认证(即登录)后才可能访问的页面.-->
<!--<!-3). logout 登出.–>-->
<!--4). roles 角色过滤器-->
不要配置:
<!--<property name="filterChainDefinitions">-->
<!--<value>-->
<!--/login.jsp = anon-->
<!--/user/login = anon-->
<!--/user/logout = logout-->
<!--/user.jsp = roles[user]-->
<!--/admin.jsp =roles[admin]-->
<!--# everything else requires authentication:-->
<!--/** = authc-->
<!--</value>-->
<!--</property>-->
</bean>
<!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->
<bean id="filterChainDefinitionMap"
factory-bean="filterChainDefinitionMapBuilder" factory-method="buiStringStringLinkedHashMap"></bean>
<bean id="filterChainDefinitionMapBuilder"
class="com.lx.utils.FilterChainDefinitionMapBuilder"></bean>
FilterChainDefinitionMapBuilder.java
package com.lx.utils;
import java.util.LinkedHashMap;
import java.util.LinkedList;
public class FilterChainDefinitionMapBuilder {
public LinkedHashMap<String,String> buiStringStringLinkedHashMap(){
LinkedHashMap<String,String> linkedHashMap=new LinkedHashMap<String,String>();
linkedHashMap.put("/login.jsp","anon");
linkedHashMap.put("/user/login","anon");
linkedHashMap.put("/user.jsp","roles[user]");
linkedHashMap.put("/admin.jsp","roles[admin]");
linkedHashMap.put("/**","authc");
return linkedHashMap;
}
}
会话
概述
• Shiro 提供了完整的企业级会话管理功能,不依赖于底层容
器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境
都可以使用,提供了会话管理、会话事件监听、会话存储/
持久化、容器无关的集群、失效/过期支持、对Web 的透明
支持、SSO 单点登录的支持等特性
会话相关的 API
• Subject.getSession():即可获取会话;其等价于
Subject.getSession(true),即如果当前没有创建 Session 对象会创建
一个;Subject.getSession(false),如果当前没有创建 Session 则返回
null
• session.getId():获取当前会话的唯一标识
• session.getHost():获取当前Subject的主机地址
• session.getTimeout() & session.setTimeout(毫秒):获取/设置当
前Session的过期时间
• session.getStartTimestamp() & session.getLastAccessTime():
获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定
期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每
次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间
• session.touch() & session.stop():更新会话最后访问时
间及销毁会话;当Subject.logout()时会自动调用 stop 方法
来销毁会话。如果在web中,调用 HttpSession. invalidate()
也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会
话
• session.setAttribute(key, val) &
session.getAttribute(key) &
session.removeAttribute(key):设置/获取/删除会话属
性;在整个会话范围内都可以对这些属性进行操作
• 会话监听器用于监听会话创建、过期及停止事件
缓存
CacheManagerAware 接口
• Shiro 内部相应的组件(DefaultSecurityManager)会自
动检测相应的对象(如Realm)是否实现了
CacheManagerAware 并自动注入相应的
CacheManager。
• Shiro 提供了 CachingRealm,其实现了
CacheManagerAware 接口,提供了缓存的一些基础实现;
• AuthenticatingRealm 及 AuthorizingRealm 也分别提
供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓
存。
清除缓存
// 清除缓存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
Session缓存
• 如 SecurityManager 实现了 SessionSecurityManager,
其会判断 SessionManager 是否实现了
CacheManagerAware 接口,如果实现了会把
CacheManager 设置给它。
• SessionManager 也会判断相应的 SessionDAO(如继承
自CachingSessionDAO)是否实现了
CacheManagerAware,如果实现了会把 CacheManager
设置给它
• 设置了缓存的 SessionManager,查询时会先查缓存,如
果找不到才查数据库。
5.集成SSM架构
0.0MAVEN依赖
<properties>
<junit.version>4.12</junit.version>
<spring.version>4.3.8.RELEASE</spring.version>
<mybatis.version>3.4.5</mybatis.version>
<mybatis.spring.version>1.3.0</mybatis.spring.version>
<mapper.version>2.3.4</mapper.version>
<mysql.version>5.1.38</mysql.version>
<slf4j.version>1.6.4</slf4j.version>
<jackson.version>2.9.7</jackson.version>
<c3p0.version>0.9.5-pre10</c3p0.version>
<jstl.version>1.2</jstl.version>
<servlet-api.version>3.1.0</servlet-api.version>
<jsp-api.version>2.2.1</jsp-api.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<commons-io.version>1.3.2</commons-io.version>
</properties>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- 通用Mapper -->
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- c3p0数据源 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>${jsp-api.version}</version>
<scope>provided</scope>
</dependency>
<!-- Shrio框架依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- Shrio框架依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
1.配置SSM
配置applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!--使用spring自带的占位符替换功能 -->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- 忽略没有找到的资源文件 -->
<property name="ignoreResourceNotFound" value="true" />
<!-- 配置资源文件 -->
<property name="locations">
<list>
<value>classpath:db.properties</value>
</list>
</property>
</bean>
<!--1.扫描包 -->
<context:component-scan base-package="com.lx" />
<!--2. 配置数据源 c3p0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 请求超时时间 -->
<property name="checkoutTimeout" value="30000" />
<!-- 每60秒检查所有连接池中的空闲连接。默认值: 0,不检查 -->
<property name="idleConnectionTestPeriod" value="30" />
<!-- 连接数据库连接池最大空闲时间 -->
<property name="maxIdleTime" value="30" />
<!-- 连接池初始化连接数 -->
<property name="initialPoolSize" value="5" />
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 -->
<property name="acquireIncrement" value="5" />
</bean>
<!--3.定义SqlSessionFactory -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:mappers/*.xml"></property>
<property name="typeAliasesPackage" value="com.lx.bean" />
</bean>
<!--4.定义Mapper接口的扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lx.mapper" />
</bean>
<!--5.定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 6.开启事务注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
2.配置SpringMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫表包:控制层包 -->
<context:component-scan base-package="com.lx.controller" />
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- jsp所在的位置 -->
<property name="prefix" value="/" />
<!-- 文件后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 全部资源放行 -->
<mvc:annotation-driven/>
<!-- 对指定目录下的静态资源放行 -->
<mvc:resources location="statics/images/" mapping="/images/**"/>
<mvc:resources location="statics/css/" mapping="/css/**"/>
<mvc:resources location="statics/js/" mapping="/js/**"/>
<mvc:default-servlet-handler/>
</beans>
3.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
4.db.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
5.log4j.properties
log4j.rootLogger=INFO,Console,File
#定义日志输出目的地为控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以灵活地指定日志输出格式,下面一行是指定具体的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n
#文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.File = org.apache.log4j.RollingFileAppender
#指定输出目录
log4j.appender.File.File = logs/ssm.log
#定义文件最大大小
log4j.appender.File.MaxFileSize = 10MB
# 输出所以日志,如果换成DEBUG表示输出DEBUG以上级别日志
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n
6.userMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wb.mapper.LoginlogMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.wb.bean.Loginlog">
<id column="id" property="id" />
<result column="ip" property="ip" />
<result column="no" property="no" />
<result column="createtime" property="createtime" />
<result column="location" property="location" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, ip, no, createtime, location
</sql>
</mapper>
7.配web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<!--spring的配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<!-- 添加Spring的上下文环境监听 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置SpringMvc核心的控制器 DispatcherServlet -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet的初始化參數:设置文件的路径和文件名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--不能写/*,必须写/,这是REST URL风格的要求,REST风格会在后面介绍 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 解决工程编码过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
8.集成shiro
1.引入maven依赖
<!-- Shrio框架依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- Shrio框架依赖 -->
</dependencies>
2.在web.xml中配置shiroFilter
<!--
1. 配置 Shiro 的 shiroFilter.
2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和
<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.
-->
<filter>
<filter-name>shiroFilter</filter-name>
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.配置applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
1. 配置 SecurityManager!
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="sessionMode" value="native"/>
<property name="realm" ref="jdbcRealm"/>
</bean>
<!--
2. 配置 CacheManager.
2.1 需要加入 ehcache 的 jar 包及配置文件.
-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!--<property name="cacheManager" ref="ehCacheManager"/> -->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
<!--
3. 配置 Realm
3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
-->
<bean id="jdbcRealm" class="com.lx.shiro.realms.ShiroRealm"></bean>
<!--
4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after
the lifecycleBeanProcessor has run: -->
<!--
5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用.
-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--
6. 配置 ShiroFilter.
6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/list.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>-->
<!--配置哪些页面需要受保护.-->
<!--以及访问这些页面需要的权限.-->
<!--1). anon 可以被匿名访问-->
<!--2). authc 必须认证(即登录)后才可能访问的页面.-->
<!--<!-3). logout 登出.–>-->
<!--4). roles 角色过滤器-->
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/shiro/login = anon
/shiro/logout = logout
/user.jsp = roles[user]
/admin.jsp = roles[admin]
# everything else requires authentication:
/** = authc
</value>
</property>
</bean>
</beans>
<!-- 5.会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 1000ms=1m-->
<property name="globalSessionTimeout" value="10000" />
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true" />
</bean>
自定义Realm
package com.lx.shiro.realms;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* @author luxin
* @create 2019-05-29 17:09
*/
public class ShiroRealm extends AuthorizingRealm {
@Override
public String getName() {
return super.getName();
}
//认证(登录)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 清除缓存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
}