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. sysctl -p详解

    个人一般sysctl -p 或sysctl -a比较多使用 sysctl配置与显示在/proc/sys目录中的内核参数.可以用sysctl来设置或重新设置联网功能,如IP转发.IP碎片去除以及源路由检 ...

  2. Openstack中用keypair生成和访问虚机的方法

    Openstack中用keypair生成和访问虚机的方法 标签:task   iso   perm   cte   生成   复制   vol   rsa   sla Openstack中用镜像文件生 ...

  3. 2048 控制台版(C#)

    开篇 2048游戏现在很火啊,很多人应该已经玩过了.在博客园上也看见有人模仿做的GDI+版 2048游戏,鄙人暂且不做那么多动画的东西,毕竟是个小东东,在此奉上一个<控制台版2048>. ...

  4. 用JSP从数据库中读取图片并显示在网页上

    <1>先在mysql下建立如下的table. 并insert图像. mysql.sql文件如下: CREATE TABLE photo ( photo_no int(6) unsigned ...

  5. Linux基础进程管理优先级

    一.进程优先级 Linux进程调度及多任务 每个cpu(或者cpu核心)在一个时间点上只能处理一个进程,通过时间片技术,Linux实际能够运行的进程(和线程数)可以超出实际可用的cpu及核心数量.Li ...

  6. 9-2、大型项目的接口自动化实践记录----递归判断两个json串是否相等

    1.已知json串构成的情况下判断 先构造一下场景,假设已经把各个数据都移除掉不对比的字段 图1 预期.实际结果,复杂接口返回多层嵌套json时,同下 图2 预期.实际结果值为:{child_json ...

  7. Web项目如何做单元测试

    你可能会用单元测试框架,python的unittest.pytest,Java的Junit.testNG等. 那么你会做单元测试么!当然了,这有什么难的? test_demo.py def inc(x ...

  8. 使用阿里云oss

    写这篇博文的原因是公司有个项目需要用到阿里云来存放用户头像文件.后期软件安装版本也可能需要存进去,然后折腾了两天终于摸熟了一点皮毛,在这里给大家简单介绍下. 一.初识对象存储oss 1.进入阿里云控制 ...

  9. BFS DFS模板

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

  10. 消息中间件-activemq安全机制

    activemq作为消息中间件这样一个独立的个体存在,连通用户和服务器.如果没有一套完备的安全机制去设置用户权限设置消息分发机制可想后果是非常严重.ActiveMQ如果不加入安全机制的话,任何人只要知 ...