Shiro的其他机制

Shiro的拦截器和缓存的实现

Shiro实现拦截器

Shiro使用了与Servlet一样的Filter接口进行扩展;其基础类包括以下:

1、NameableFilter:
NameableFilter给Filter起个名字,如果没有设置默认就是FilterName;

2、OncePerRequestFilter
OncePerRequestFilter用于防止多次执行Filter的;也就是说请求只会走一次拦截器链;另外提供enabled属性,表示是否开启该拦截器实例,默认enabled=true表示开启,如果不想让某个拦截器工作,可以设置为false。

3、ShiroFilter
ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理;

4、AdviceFilter
AdviceFilter提供了AOP风格的支持,类似于SpringMVC的Interceptor:

  • preHandler: 类似于AOP中的前置增强;在拦截器链执行之前执行;如果返回true则继续拦截器链;否则终端后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)。
  • postHandle: 类似于AOP中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的)。
  • afterCompletion: 类似于AOP中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触Subject与线程绑定之类的);

5、PathMatchingFilter

PathMatchingFilter提供了基于Ant风格的请求匹配功能及拦截器参数解析的功能,如roles[admin,user]自动根据,分割解析一个请求参数配置并绑定到相应的路径:

  • pathsMatch: 该方法用于path与请求路径进行匹配的方法;如果匹配返回true;
  • onPreHandle: 在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现pnPreHandle即可,无需实现preHandle。如果没有path与请求路径匹配,默认是通过的

6、AccessControlFilter

AccessControlFilter提供了访问控制的基础功能:

  • isAccessAllowed: 表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许放回true,否则返回false;
  • onAccessDenied: 表示当访问拒绝时是否已经处理了,如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将执行返回即可。
  • onPreHandle: 会自动调用这两个方法决定是否继续处理。

拦截器链

Shiro对Servlet容器的FilterChain进行了代理,即ShiroFilter在继续Servlet容器的Filter链的执行之前,通过ProxiedFilterChain对Servlet容器的FilterChain进行了代理;即先走Shiro自己的Filter体系,然后再委托给Servlet容器的FilterChain进行Servlet容器级别的Filter链执行;Shiro的ProxiedFilterChain执行流程:1、先执行Shiro自己的Filter链;2、再执行Servlet容器的Filter链(即原始的Filter)。

而ProxiedFilterChain是通过FilterChainResolver根据配置文件中的[urls]部分是否与请求的URL是否匹配解析得到的。

1
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain chain);

即传入原始的Chain,得到一个代理的Chain。

Shiro内部还提供了一个路径匹配的FilterChainResolver实现:PathMatchingFilterChainResolver,其根据[urls]中配置的URL模式和请求的URL是否匹配解析得到的配置的拦截器链的;而PathMatchingFilterChainResolver内部通过FilterChainManager维护着拦截器链。因此我们可以通过FilterChainManager进行动态增加URL模式与拦截器链的关系。

DefaultFilterChainManager会默认添加org.apache.shiro.web.filter.mgt.DefaultFilter中声明的拦截器;

默认拦截器

Shiro提供的默认拦截器有以下几种:

默认拦截器名 说明
身份验证相关的
authc 基于表单的拦截器,如:/=authc 那么未经身份验证的页面不能访问,将会自动跳转到登录页面
authcBasic Basic HTTP身份验证拦截器,主要属性:applicationName,弹出登录框显示的信息(application)
logout 退出拦截器,主要属性:redirectUrl,退出成功后重定向的地址,如:/logout=logout
user 用户登录拦截器,用户已经身份验证、记住我登录的都可;如:/**=user
anon 匿名拦截器,即不需要登录就能访问,一般用于静态资源的过滤;如:/static/**=anon
授权相关的
roles 角色授权拦截器,验证用户是否有拥有所有角色;主要属性:loginUrl,登录页面地址(/login.jsp);unauthorizedUrl,未经授权后重定向的地址;如:/admin/**=roles[admin]
perms 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;如:/user/**=perms["user:create"]
rest rest风格的拦截器,会自动根据请求方法构建全权限字符串(GET=read,POST=create,PUT=update…)构建权限字符串:/users=rest[user],会自动拼接处user:read,user:create,user:update…权限字符串进行权限匹配
ssl SSL拦截器,自由请求协议是https,才能通过,否则会自动跳转到https端口(443)
其他
noSessionCreation 不创建会话拦截器

Shiro实现会话管理

会话:即用户访问应用时保持的连接关系。在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。
如:

1
2
3
Subject subject = SecurityUtils.getSubject();
subject.login(token);
Session session = subject.getSession();

登录成功后使用Subject.getSession()即可获取会话;其等价于Subject.getSession(true),即当前如果没有创建Session对象会自动创建一个;而Subject.getSession(false),若当前没有创建Session,会返回null(不过默认情况下如果启用会话储存功能的话在创建Subject时会主动创建一个Session)。
其常用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
session.getId(); //获取当前会话的唯一标识

session.getHost(); //获取当前Subject的主机地址,该地址是通过HostAuthenticationToken.getHost()提供的

session.getTimeout();
session.getTimeout(毫秒); //设置当前Session的过期时间;如果不设置默认是会话管理器的全局过期时间

session.getStartTimestamp();
session.getLastAccessTime(); //获取会话的启动时间和最后访问时间;

session.touch();
session.stop(); //更新会话最后访问时间及销毁会话

session.setAttribute("key", "123");
session.getAttribute("key");
session.removeAttribute("key"); // 设置、获取、删除会话属性,在整个会话范围内都可以对这些属性进行操作

会话管理器

会话管理器管理着所有Subject的会话的创建、维护、删除、失效、验证等工作。是shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager。且提供了SessionSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionSecurityManager。

1
2
3
4
5
6
Session start(SessionContext context); //启动会话
Session getSession(SessionKey key) throws SessionException; //根据会话Key获取会话

boolean isServletContainerSessions(); //是否使用Servlet容器的会话

void validateSessions(); //验证所有会话是否过期

会话监听器

会话监听器用于监听会话创建、过期及停止事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MySessionListener implements SessionListener {
@Override
public void onStart(Session session) {
System.out.println("会话创建:" + session.getId());
}

@Override
public void onStop(Session session) {
System.out.println("会话过期:" + session.getId());
}

@Override
public void onExpiration(Session session) {
System.out.println("会话停止:" + session.getId());
}
}

ini配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[main]
authc.loginUrl=/login

sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager=$sessionManager
mySessionListener=com.shiro.listener.MySessionListener
sessionManager.sessionListeners=$mySessionListener
#设置全局会话过期时间:1分钟
sessionManager.globalSessionTimeout=6000

[users]
tycoding=123,admin

[urls]
#格式:url=拦截器[参数],拦截器[参数]
/login=anon

Shiro实现缓存

Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实现Cache,但是又对Chache进行了抽象,方便更换不同的底层Cache实现。

Shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了缓存的一些基础实现;另外AuthenticatingRealm及AuthorizingRealm分别提供了对AuthenticationInfo和AuthorizationInfo信息的缓存。

测试案例

ini配置

1
2
3
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml
securityManager.cacheManager=$cacheManager

Test

1
2
3
4
5
6
7
8
9
@Test
public void testClearCachedAuthenticationInfo() {
login(u1.getUsername(), password);
userService.changePassword(u1.getId(), password + "1");
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();
userRealm.clearCachedAuthenticationInfo(subject().getPrincipals());
login(u1.getUsername(), password + "1");
}

解释: 上面的测试代码中,模拟出了Shiro缓存作用:1、用户登录成功后,先调用service方法改变原数据库中的密码;2、此时调用clearCachedAuthenticationInfo清空之前缓存的AuthenticationInfo;3、使用新密码进行登录。如果没有清空缓存,下次登录的时候获取到的还是原来未修改的密码。
注意: 以上代码仅仅是用来解释Shiro实现的缓存机制的案例,没有完整的代码,详细整合过程请看之后的文章。


交流

如果大家有兴趣,欢迎大家加入我的Java交流群:671017003 ,一起交流学习Java技术。博主目前一直在自学JAVA中,技术有限,如果可以,会尽力给大家提供一些帮助,或是一些学习方法,当然群里的大佬都会积极给新手答疑的。所以,别犹豫,快来加入我们吧!


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励