Shiro在于Spring集成中,需要配置SecurityManager,Realm,ShiroFilterFactoryBean这三个类。在Web环境中SecurityManager一般配置DefaultWebSecurityManager,如果需要扩展或者定制一些额外的功能,可以配置DefaultWebSecurityManager的继承类;Realm需要先继承AuthorizingRealm抽象类再配置,如果有多个Realm的话,还需要配置ModularRealmAuthenticator的继承实现类;ShiroFilterFactoryBean主要是提供ShiroFilter,可以配置一些资源的拦截。下面对一些核心类进行一下总结。

SecurityManager

该类继承了三个接口,还额外提供登录,退出和创建用户的功能。

 /**
* 所有与安全有关的操作都会与SecurityManager交互
* 扩展了authenticator、authorizer和sessionmanager接口
*/
public interface SecurityManager extends Authenticator, Authorizer, SessionManager { Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException; void logout(Subject subject); Subject createSubject(SubjectContext context);
} /**
* 认证验证,登录校验
*
*/
public interface Authenticator { /**
* AuthenticationToken 登录未验证的数据
* AuthenticationInfo 身份验证/登录过程相关的帐户信息。
*
*/
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
} /**
* 用户授权,权限校验
*
*/
public interface Authorizer { boolean[] isPermitted(PrincipalCollection subjectPrincipal, String... permissions); boolean[] isPermitted(PrincipalCollection subjectPrincipal, List<Permission> permissions); boolean isPermittedAll(PrincipalCollection subjectPrincipal, String... permissions); boolean isPermittedAll(PrincipalCollection subjectPrincipal, Collection<Permission> permissions); void checkPermissions(PrincipalCollection subjectPrincipal, String... permissions) throws AuthorizationException; void checkPermissions(PrincipalCollection subjectPrincipal, Collection<Permission> permissions) throws AuthorizationException; boolean hasRole(PrincipalCollection subjectPrincipal, String roleIdentifier); boolean[] hasRoles(PrincipalCollection subjectPrincipal, List<String> roleIdentifiers); boolean hasAllRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers); void checkRole(PrincipalCollection subjectPrincipal, String roleIdentifier) throws AuthorizationException; void checkRoles(PrincipalCollection subjectPrincipal, Collection<String> roleIdentifiers) throws AuthorizationException; void checkRoles(PrincipalCollection subjectPrincipal, String... roleIdentifiers) throws AuthorizationException; } /**
* 会话管理
*/
public interface SessionManager { /**
* 基于指定的上下文初始化数据启动一个新Session,Session通常交由SessionFactory创建
*
*/
Session start(SessionContext context); /**
* 通过SessionKey查找Session
*
*/
Session getSession(SessionKey key) throws SessionException;
}

SecurityManager的Web部分源代码实现如下所示。从默认的构造器可以看到在创建SecurityManager的该实现时,会设置一系列默认的值,如ServletContainerSessionManager,CookieRememberMeManager等。而isHttpSessionMode方法判断是否是HttpSession,还是自己实现的Session。

public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {

    public DefaultWebSecurityManager() {
super();
DefaultWebSessionStorageEvaluator webEvalutator = new DefaultWebSessionStorageEvaluator();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(webEvalutator);
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
webEvalutator.setSessionManager(getSessionManager());
} public boolean isHttpSessionMode() {
SessionManager sessionManager = getSessionManager();
return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
}
...
}

以下是SecurityManager中实现SessionManager接口的实现类,从中可以看到SecurityManager并没有实际处理SessionManager接口的方法,而是采用组合模式,将实际的SessionManager作为SecurityManager的成员变量,实际处理还是交由sessionManager来处理。而且在SessionManager初始化完默认的DefaultSessionManager后(在新继承的DefaultWebSecurityManager的类中,为ServletContainerSessionManager)后,如果SessionManager实现CacheManagerAware接口,则会将CacheManager也一同设置到SessionManager中。

public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
private SessionManager sessionManager; public SessionsSecurityManager() {
super();
this.sessionManager = new DefaultSessionManager();
applyCacheManagerToSessionManager();
} protected void applyCacheManagerToSessionManager() {
if (this.sessionManager instanceof CacheManagerAware) {
((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
}
} public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
afterSessionManagerSet();
} protected void afterSessionManagerSet() {
applyCacheManagerToSessionManager();
applyEventBusToSessionManager();
} public SessionManager getSessionManager() {
return this.sessionManager;
} public Session start(SessionContext context) throws AuthorizationException {
return this.sessionManager.start(context);
} public Session getSession(SessionKey key) throws SessionException {
return this.sessionManager.getSession(key);
}
}

从SecurityManager的继承体系来看,每次的继承都会添加一个成员变量,并且对外公开的方法也是由该成员来处理。所以现在来看,SecurityManager是通过继承体系和组合的模式,来充实它的实际功能,并且将Shiro的各个组件都联系到了一起。SecurityManager是线程安全且真个应用只需要一个即可,因此Shiro提供了SecurityUtils让我们绑定它为全局的,方便后续操作。

Realm

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成 DataSource,即安全数据源。 通常由程序实现AuthorizingRealm类,如果有多个实现,还需要重写ModularRealmAuthenticator的doAuthenticate的方法,来指定Realm对应处理的AuthenticationToken。另外AuthorizingRealm提供设置缓存,加密和权限的相关功能。

public interface Realm {

	/**
* 返回应用中Realm的唯一名字
*/
String getName(); /**
* 多Realm中,该Realm是否匹配AuthenticationToken
*/
boolean supports(AuthenticationToken token); /**
* 依据未认证的AuthenticationToken,返回认证后的AuthenticationInfo
*/
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; }

ModularRealmAuthenticator

Authenticator的功能是验证用户帐号,是Shiro API中身份认证核心的入口点。如果验证成功,将返回 AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException实现异常。它的默认实现类是ModularRealmAuthenticator,可以从这个类中看到校验的整个流程,而且还提供了AuthenticationListener来监听认证的过程(主要有登录成功事件,登录失败事件和退出事件)。

public class ModularRealmAuthenticator extends AbstractAuthenticator {

    private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);

    private Collection<Realm> realms;

    private AuthenticationStrategy authenticationStrategy;

    public ModularRealmAuthenticator() {
this.authenticationStrategy = new AtLeastOneSuccessfulStrategy();
} public void setRealms(Collection<Realm> realms) {
this.realms = realms;
} protected Collection<Realm> getRealms() {
return this.realms;
} public AuthenticationStrategy getAuthenticationStrategy() {
return authenticationStrategy;
} public void setAuthenticationStrategy(AuthenticationStrategy authenticationStrategy) {
this.authenticationStrategy = authenticationStrategy;
} protected void assertRealmsConfigured() throws IllegalStateException {
Collection<Realm> realms = getRealms();
if (CollectionUtils.isEmpty(realms)) {
String msg = "Configuration error: No realms have been configured! One or more realms must be " +
"present to execute an authentication attempt.";
throw new IllegalStateException(msg);
}
} /**
* 单Realm的校验,最后调用realm.getAuthenticationInfo方法来通过Realm校验正确性。
*/
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
} /**
* 多Realm的校验,还需要考虑认证的策略(全部成功,至少一个成功)
*/
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { AuthenticationStrategy strategy = getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
} for (Realm realm : realms) { aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm); AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isDebugEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg, t);
}
} aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
} aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate;
} /**
* 多Realm的校验,还需要考虑认证的策略(全部成功,至少一个成功)
*/
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
} public void onLogout(PrincipalCollection principals) {
super.onLogout(principals);
Collection<Realm> realms = getRealms();
if (!CollectionUtils.isEmpty(realms)) {
for (Realm realm : realms) {
if (realm instanceof LogoutAware) {
((LogoutAware) realm).onLogout(principals);
}
}
}
}
}

SessionManager

SecurityManager提供了如下接口,另外用于 Web 环境的 WebSessionManager又提供了如下接口,判断是否是Servlet容器的Session,还是自己维护Session。SecurityManager管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。

public interface SessionManager {
/**
* 启动会话
*/
Session start(SessionContext context); Session getSession(SessionKey key) throws SessionException;
} public interface WebSecurityManager extends SecurityManager { boolean isHttpSessionMode();
}

在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler来做这件事情。SecurityManager的实现类中一般都实现了该接口。

Shiro提供了SessionManager的三个默认实现:

  • DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于JavaSE环境

  • ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于 Web环境,其直接使用Servlet容器的会话;

  • DefaultWebSessionManager :用于Web环境的实现,可以代替ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。

Session

Session是用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。Shiro的会话支持不仅可以在普通的JavaSE应用中使用,也可以在JavaEE应用中使用,如web应用。且使用方式是一致的 。

public interface Session {

    Serializable getId();

    Date getStartTimestamp();

    Date getLastAccessTime();

    long getTimeout() throws InvalidSessionException;

    void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;

    String getHost();

    /**
* 如果是 JavaSE 应用需要自己定期调用 session.touch()去更新最后访问时间;
* 如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch()来更新最后访问时间
*/
void touch() throws InvalidSessionException; /**
* 当Subject.logout()时会自动调用 stop 方法来销毁会话
*/
void stop() throws InvalidSessionException; Collection<Object> getAttributeKeys() throws InvalidSessionException; Object getAttribute(Object key) throws InvalidSessionException; void setAttribute(Object key, Object value) throws InvalidSessionException; Object removeAttribute(Object key) throws InvalidSessionException;
}

Session提供了监听器SessionListener,用于监听会话创建、过期及停止事件,如果只想监听某一个事件,可以继承SessionListenerAdapter实现。

Shiro提 SessionDAO用于会话的CRUD操作,AbstractSessionDAO提供了 SessionDAO的基础实现,如生成会话 ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行会话维护;而EnterpriseCacheSessionDAO提供了缓存功能的会话维护,但是都是空方法,需要继承实现这些方法。

总结

Shiro与Spring的整合中,很多对Shiro的功能扩展,都需要继承原来的类,再修改为默认的实现。比如在Web环境中可以自己实现Session管理,就需要在SecurityManager中调用setSessionManager()方法,修改默认的SessionManager。

还有在Shiro和Spring整合中碰到了一个问题UserRealm中注入IUserService,导致IUserService的AOP失效(会导致事务失效等),只是查明了原有,解决办法可以不用注入,改为SpringContextHolder.getBean(IUserService.class)的方式。

Shiro的几个关键类的更多相关文章

  1. Red5源代码分析 - 关键类及其初始化过程

    原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...

  2. 《MonkeyRunner原理剖析》第九章-MonkeyImage实现原理 - 第一节 - 关键类作用及关系

    MonkeyRunner框架暴露了几个类的大量的API出去给用户编写脚本时候使用,其中最主要的三个就是: MonkeyDevice目标设备操作类,HierarchyViewer窗口界面对象操作类以及M ...

  3. WebRTC 学习之 Intel® Collaboration Suite for WebRTC 关键类整理

    关键类整理 ---> ConferenceClient.ConferenceClientObserver. 一.ConferenceClient ConferenceClient是一个应用程序在 ...

  4. spring初始化源码浅析之关键类和扩展接口

    目录 1.关键接口和类 1.1.关键类之 DefaultListableBeanFactory 1.2.关键类之XmlBeanDefinitionReader 1.3.关键类之ClassPathXml ...

  5. Shiro 使用 JWT Token 配置类参考

    项目中使用了 Shiro 进行验证和授权,下面是 Shiro 配置类给予参考. 后来并没有使用 Shiro,感觉使用 JWT 还是自己写拦截器比较灵活,使用 Shiro 后各种地方需要魔改,虽然功能也 ...

  6. quartz中关键类

    job job是一个接口,你对某一类job的定义,可以通过实现该接口来实现.例如为销售报告定义一个SalesReportJob,包含变量name. job可以使用的几个注解 @DisallowConc ...

  7. Hibernate的常用关键类以及接口介绍

    上一篇初步的对Hibernate进行了认识,并测试了Hibernate的HelloWorld, 这里主要介绍HibernateTest类中的相关类和接口,以及其作用和特性,关于Session中的相关方 ...

  8. Spring-MongoDB 关键类的源码分析

    本文分析的是 spring-data-mongodb-1.9.2.RELEASE.jar 和 mongodb-driver-core-3.2.2.jar. 一.UML Class Diagram 核心 ...

  9. [微信开发] - weixin4j关键类解析

    TokenUtil : get()获取我方自定义的token(从配置文件或数据库) checkSignature(Str..... (服务器配置连接验证有效性) /* * 微信公众平台(JAVA) S ...

随机推荐

  1. UnityWebSocket

    !!!转载注明:http://www.cnblogs.com/yinlong1991/p/unity_ylwebsocket.html Unity WebSocket 使用 Demo 线上测试地址 h ...

  2. 【转载】【VSCode】Windows下VSCode编译调试c/c++

    转载自:http://blog.csdn.net/c_duoduo/article/details/51615381 懒得自己配置或自己配置出现不明问题的朋友可以点这里: [VSCode]Window ...

  3. 【python-django后端开发】Redis缓存配置使用详细教程!!!

    官方查阅资料:https://django-redis-chs.readthedocs.io/zh_CN/latest/ 1. 安装django-redis扩展包 1.安装django-redis扩展 ...

  4. Jenkins Java 反序列化远程执行代码漏洞(CVE-2017-1000353)

    Jenkins Java 反序列化远程执行代码漏洞(CVE-2017-1000353) 一.漏洞描述 该漏洞存在于使用HTTP协议的双向通信通道的具体实现代码中,jenkins利用此通道来接收命令,恶 ...

  5. Intent 使用详解

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android Intent 是一个消息传递对象,主要用于组建之间的通讯,例如:启动Activit ...

  6. Git命令备忘录

    目录 前言 基本内容 开始之前 基础内容 远程仓库 分支管理 前言 Git在平时的开发中经常使用,整理Git使用全面的梳理. 基本内容 开始之前 请自行准备好Git工具以及配置好Git的基本配置 基础 ...

  7. BFS DFS模板

    转载于https://blog.csdn.net/alalalalalqp/article/details/9155419 BFS模板: #include<cstdio> #include ...

  8. 2019牛客多校训练第三场B.Crazy Binary String(思维+前缀和)

    题目传送门 大致题意: 输入整数n(1<=n<=100000),再输入由n个0或1组成的字符串,求该字符串中满足1和0个数相等的最长子串.子序列. sample input: 801001 ...

  9. Oracle中查看最近被修改过的表的方法

    1.select uat.table_name from user_all_tables uat 该SQL可以获得所有用户表的名称 2.select object_name, created,last ...

  10. 1.1Django简介和虚拟环境配置

    MVC 大部分开发语言中都有MVC框架 MVC框架的核心思想是:解耦 降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用 m表示model,主要用于对数据库层的封装 v表示 ...