shiro中自定义的realm交给spring service 单例管理,是单例好呢还是多例好

shiro自定义realm实现授权
public class CustomRealm extends AuthorizingRealm {
&@Override
&public void setName(String name) {
&&super.setName("customRealm");
&@Override
&protected AuthenticationInfo
doGetAuthenticationInfo(
&&&AuthenticationToken
token) throws AuthenticationException {
&// 用于授权
&@Override
&protected AuthorizationInfo
doGetAuthorizationInfo(
&&&PrincipalCollection
principals) {
&&&&String
userCode =& (String)
principals.getPrimaryPrincipal();
&&//模拟从数据库获取到数据
&&List permissions = new
ArrayList();
&&permissions.add("user:create");//用户的创建
&&permissions.add("items:add");//商品添加权限
&&SimpleAuthorizationInfo
simpleAuthorizationInfo = new SimpleAuthorizationInfo();
&&simpleAuthorizationInfo.addStringPermissions(permissions);
simpleAuthorizationI
public class AuthorizationTest2 {
&public void testAuthorizationCustomRealm() {
&&Factory factory = new
IniSecurityManagerFactory(
&&&&"classpath:config/shiro-realm.ini");
&&SecurityManager securityManager
= factory.getInstance();
&&SecurityUtils.setSecurityManager(securityManager);
&&Subject subject =
SecurityUtils.getSubject();
&&UsernamePasswordToken token =
new UsernamePasswordToken("zhangsan","111111");
&&&subject.login(token);
(AuthenticationException e) {
&&&e.printStackTrace();
&&System.out.println("认证状态:" +
subject.isAuthenticated());
&&boolean isPermitted =
subject.isPermitted("user:create:1");
&&System.out.println("单个权限判断" +
isPermitted);
&&boolean isPermittedAll =
subject.isPermittedAll("user:create:1","user:create");
&&System.out.println("多个权限判断" +
isPermittedAll);
&&subject.checkPermission("items:add:1");
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。shiro源码分析(一)入门
最近闲来无事,准备读个框架源码,经别人推荐shiro,那就准备读读其中的设计。开涛大神已经有了跟我学Shiro系列,那我就跟着这个系列入门然后再深入源代码,所以我的侧重点就是源码分析。
话不多说,上开涛大神的入门案例 地址http://jinnianshilongnian.
最近闲来无事,准备读个框架源码,经别人推荐shiro,那就准备读读其中的设计。开涛大神已经有了跟我学Shiro系列,那我就跟着这个系列入门然后再深入源代码,所以我的侧重点就是源码分析。
话不多说,上开涛大神的入门案例 地址:
public void testHelloworld() {
Factory&org.apache.shiro.mgt.SecurityManager& factory =
new IniSecurityManagerFactory("classpath:shiro.ini");
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123232");
subject.login(token);
} catch (AuthenticationException e) {
Assert.assertEquals(true, subject.isAuthenticated());
subject.logout();
1:使用工厂模式来得到SecurityManager,由于可以通过不同工厂创建出不同的SecurityManager,如通过配置文件的形式来创建的IniSecurityManagerFactory工厂。类图如下:
Factory接口:通过泛型定义了一个T getInstance()方法
AbstractFactory抽象类:对于getInstance返回的对象加入单例或者非单例的功能,而把真正创建实例对象的createInstance功能留给子类去实现
public T getInstance() {
if (isSingleton()) {
if (this.singletonInstance == null) {
this.singletonInstance = createInstance();
instance = this.singletonI
instance = createInstance();
if (instance == null) {
String msg = "Factory 'createInstance' implementation returned a null object.";
throw new IllegalStateException(msg);
protected abstract T createInstance();
IniFactorySupport:加入了Ini ini属性,同过该对象来创建出一个实例,IniFactorySupport对于ini的获取给出了两种方式,方式一:在构造IniFactorySupport时传入Ini 对象,另一种就是加载类路径下默认的Ini,如下:
public static Ini loadDefaultClassPathIni() {
Ini ini = null;
if (ResourceUtils.resourceExists(DEFAULT_INI_RESOURCE_PATH)) {
log.debug("Found shiro.ini at the root of the classpath.");
ini = new Ini();
ini.loadFromPath(DEFAULT_INI_RESOURCE_PATH);
if (CollectionUtils.isEmpty(ini)) {
log.warn("shiro.ini found at the root of the classpath, but it did not contain any data.");
其中DEFAULT_INI_RESOURCE_PATH为classpath:shiro.ini。然而IniFactorySupport并不负责通过ini配置文件来创建出什么样的对象,它仅仅负责获取ini配置文件,所以它要留出了两个方法让子类实现:
protected abstract T createInstance(Ini ini);
protected abstract T createDefaultInstance();
第一个方法就是通过ini配置文件创建出什么对象,第二个方法就是当获取不到ini配置文件时,要创建默认的对象。
IniSecurityManagerFactory:通过Ini配置文件可以创建出SecurityManager对象,也可以通过ini配置文件创建FilterChainResolver对象,而IniSecurityManagerFactory则是通过ini配置文件来创建SecurityManager的,所以对于泛型的实例化是在该类完成的,如下:
public class IniSecurityManagerFactory extends IniFactorySupport&SecurityManager&
public class IniFilterChainResolverFactory extends IniFactorySupport&FilterChainResolver&
IniSecurityManagerFactory 还不具有web功能,WebIniSecurityManagerFactory则加入了web功能。
可以看到,有很多的类继承关系,每一个类都完成了一个基本功能,把职责划分的更加明确,而不是一锅粥把很多功能放到一个类中,导致很难去复用某些功能。
2 :将创建的SecurityManager放到SecurityUtils类的静态变量中,供所有对象来访问。
3 :创建一个Subject实例,接口Subject的文档介绍如下:
A {@code Subject} represents state and security operations for a &em&single&/em& application user.These operations include authentication (login/logout), authorization (access control), and session access
及外界通过Subject接口来和SecurityManager进行交互,该接口含有登录、退出、权限判断、获取session,其中的Session可不是平常我们所使用的HttpSession等,而是shiro自定义的,是一个数据上下文,与一个Subject相关联的。
先回到创建Subject的地方:
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
一看就是使用的是ThreadLocal设计模式,获取当前线程相关联的Subject 对象,如果没有则创建一个,然后绑定到当前线程。然后我们来看下具体实现:
ThreadContext是org.apache.shiro.util包下的一个工具类,它是用来操作和当前线程绑定的SecurityManager和Subject,它必然包含了一个ThreadLocal对象如下:
public abstract class ThreadContext {
public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
private static final ThreadLocal&Map&Object, Object&& resources = new InheritableThreadLocalMap&Map&Object, Object&&();
ThreadLocal中所存放的数据是一个Map集合,集合中所存的key有两个SECURITY_MANAGER_KEY 和SUBJECT_KEY ,就是通过这两个key来存取SecurityManager和Subject两个对象的。具体的ThreadLocal设计模式分析可以详见我的另一篇博客。
当前线程还没有绑定一个Subject时,就需要通过Subject.Builder来创建一个然后绑定到当前线程。Builder是Subject的一个内部类,它拥有两个重要的属性,SubjectContext和SecurityManager,创建Builder时使用SecurityUtils工具来获取它的全局静态变量SecurityManager,SubjectContext则是使用newSubjectContextInstance创建一个DefaultSubjectContext对象:
public Builder() {
this(SecurityUtils.getSecurityManager());
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
this.securityManager = securityM
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
this.subjectContext.setSecurityManager(securityManager);
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
Builder准备工作完成后,调用buildSubject来创建一个Subject:
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
最终还是通过securityManager根据subjectContext来创建一个Subject。最终是通过一个SubjectFactory来创建的,SubjectFactory是一个接口,接口方法为Subject createSubject(SubjectContext context),默认的SubjectFactory实现是DefaultSubjectFactory,DefaultSubjectFactory创建的Subject是DelegatingSubject。至此创建Subject就简单说完了。
4 继续看登陆部分
登陆方法为:void login(AuthenticationToken token),AuthenticationToken 接口如下:
public interface AuthenticationToken extends Serializable {
Object getPrincipal();
Object getCredentials();
Principal就相当于用户名,Credentials就相当于密码,AuthenticationToken 的实现UsernamePasswordToken有四个重要属性,即username、char[] password、boolean rememberMe、host。认证过程是由Authenticator来完成的,先来看下Authenticator的整体:
public interface Authenticator {
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationE
很简单,就是根据AuthenticationToken 返回一个AuthenticationInfo ,如果认证失败会抛出AuthenticationException异常。
AbstractAuthenticator实现了Authenticator 接口,它仅仅加入了对认证成功与失败的监听功能,即有一个Collection&AuthenticationListener&集合:
private Collection&AuthenticationListener&
对于认证过程:
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationI
info = doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this " +
"Authenticator instance.
Please check that it is configured correctly.";
throw new AuthenticationException(msg);
} catch (Throwable t) {
AuthenticationException ae = null;
if (t instanceof AuthenticationException) {
ae = (AuthenticationException)
if (ae == null) {
String msg = "Authentication failed for token submission [" + token + "].
Possible unexpected " +
"error? (Typical or expected login exceptions should extend from AuthenticationException).";
ae = new AuthenticationException(msg, t);
notifyFailure(token, ae);
} catch (Throwable t2) {
if (log.isWarnEnabled()) {
String msg = "Unable to send notification for failed authentication attempt - listener error?.
"Please check your AuthenticationListener implementation(s).
Logging sending exception " +
"and propagating original AuthenticationException instead...";
log.warn(msg, t2);
log.debug("Authentication successful for token [{}].
Returned account [{}]", token, info);
notifySuccess(token, info);
protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
throws AuthenticationE
从上面可以看到实际的认证过程doAuthenticate是交给子类来实现的,AbstractAuthenticator只对认证结果进行处理,认证成功时调用notifySuccess(token, info)通知所有的listener,认证失败时调用notifyFailure(token, ae)通知所有的listener。
具体的认证过程就需要看AbstractAuthenticator子类对于doAuthenticate方法的实现,ModularRealmAuthenticator继承了AbstractAuthenticator,它有两个重要的属性如下
private Collection&Realm&
private AuthenticationStrategy authenticationS
首先就是Realm的概念:就是配置各种角色、权限和用户的地方,即提供了数据源供shiro来使用,它能够根据一个AuthenticationToken中的用户名和密码来判定是否合法等,文档如下:
A &tt&Realm&/tt& is a security component that can access application-specific security entities such as users, roles, and permissions to determine authentication and authorization operations
接口如下:
public interface Realm {
String getName();
boolean supports(AuthenticationToken token);
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationE
Realm 首先有一个重要的name属性,全局唯一的标示。supports、getAuthenticationInfo方法就是框架中非常常见的一种写法,ModularRealmAuthenticator拥有Collection&Realm& realms集合,在判定用户合法性时,会首先调用每个Realm的supports方法,如果支持才会去掉用相应的getAuthenticationInfo方法。
关于Realm的详细接口设计之后再给出详细说明,此时先继续回到ModularRealmAuthenticator认证的地方
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection&Realm& realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
return doMultiRealmAuthentication(realms, authenticationToken);
代码很简单,当只有一个Realm时先调用Realm的supports方法看是否支持,若不支持则抛出认证失败的异常,若支持则调用Realm的getAuthenticationInfo(token)方法如下:
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);
若有多个Realm 时怎样才算是认证成功的呢?这就需要ModularRealmAuthenticator的认证策略AuthenticationStrategy 来指定,对于AuthenticationStrategy目前有三种实现
AllSuccessfulStrategy:即所有的Realm 都验证通过才算是通过
AtLeastOneSuccessfulStrategy:只要有一个Realm 验证通过就算通过
FirstSuccessfulStrategy:这个刚开始不太好理解,和AtLeastOneSuccessfulStrategy稍微有些区别。AtLeastOneSuccessfulStrategy返回了所有Realm认证成功的信息,FirstSuccessfulStrategy只返回了第一个Realm认证成功的信息。
试想一下,如果让你来设计,你会怎么设计?
然后来具体看下AuthenticationStrategy 的接口设计:
public interface AuthenticationStrategy {
AuthenticationInfo beforeAllAttempts(Collection&? extends Realm& realms, AuthenticationToken token) throws AuthenticationE
AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationE
AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)
throws AuthenticationE
AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationE
验证过程是这样的,每一个Realm验证token后都会返回一个当前Realm的验证信息AuthenticationInfo singleRealmInfo,然后呢会有一个贯穿所有Realm验证过程的验证信息AuthenticationInfo aggregateInfo,每一个Realm验证过后会进行singleRealmInfo和aggregateInfo的合并,这是大体的流程
对于AllSuccessfulStrategy来说:它要确保每一个Realm都要验证成功,所以必然
(1)要在beforeAttempt中判断当前realm是否支持token,如不支持抛出异常结束验证过程
(2)要在afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)中判断是否验证通过了,即异常t为空,并且singleRealmInfo不为空,则表示验证通过了,然后将singleRealmInfo和aggregateInfo合并,所以最终返回的aggregateInfo是几个Realm认证信息合并后的结果
AllSuccessfulStrategy就会在这两处进行把关,一旦不符合抛出异常,认证失败,如下:
public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] of type [" + realm.getClass().getName() + "] does not support " +
" the submitted AuthenticationToken [" + token + "].
The [" + getClass().getName() +
"] implementation requires all configured realm(s) to support and be able to process the submitted " +
"AuthenticationToken.";
throw new UnsupportedTokenException(msg);
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t)
throws AuthenticationException {
if (t != null) {
if (t instanceof AuthenticationException) {
throw ((AuthenticationException) t);
String msg = "Unable to acquire account data from realm [" + realm + "].
getClass().getName() + " implementation requires all configured realm(s) to operate successfully " +
"for a successful authentication.";
throw new AuthenticationException(msg, t);
if (info == null) {
String msg = "Realm [" + realm + "] could not find any associated account data for the submitted " +
"AuthenticationToken [" + token + "].
The [" + getClass().getName() + "] implementation requires " +
"all configured realm(s) to acquire valid account data for a submitted token during the " +
"log-in process.";
throw new UnknownAccountException(msg);
log.debug("Account successfully authenticated using realm [{}]", realm);
merge(info, aggregate);
对于AtLeastOneSuccessfulStrategy来说:它只需确保在所有Realm验证完成之后,判断下aggregateInfo是否含有用户信息即可,若有则表示有些Realm是验证通过了,此时aggregateInfo也是合并后的信息,如下
public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
if (aggregate == null || CollectionUtils.isEmpty(aggregate.getPrincipals())) {
throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
"could not be authenticated by any configured realms.
Please ensure that at least one realm can " +
"authenticate these tokens.");
对于FirstSuccessfulStrategy来说:它只需要第一个Realm验证成功的信息,不需要去进行合并,所以它必须在合并上做手脚,即不会进行合并,一旦有一个Realm验证成功,信息保存到
aggregateInfo中,之后即使再次验证成功也不会进行合并,如下
protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {
return info != null ? info :
验证策略分析完成之后,我们来看下ModularRealmAuthenticator的真个验证的代码过程:
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;
info = realm.getAuthenticationInfo(token);
} catch (Throwable 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);
log.debug("Realm [{}] does not support token {}.
Skipping realm.", realm, token);
aggregate = strategy.afterAllAttempts(token, aggregate);
有了之前的分析,这个过程便变的相当容易了。
再回到我们的入门案例中,有了AuthenticationInfo 验证信息,之后进行了那些操作呢?
回到DefaultSecurityManager的如下login方法中:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationI
info = authenticate(token);
} catch (AuthenticationException ae) {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
("onFailedLogin method threw an " +
"exception.
Logging and propagating original AuthenticationException.", e);
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
Subject loggedIn = createSubject(token, info, subject)会根据已有的token、认证结果信息info、和subject从新创建一个已登录的Subject,含有Session信息,创建过程如下:
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
SubjectContext context = createSubjectContext();
context.setAuthenticated(true);
context.setAuthenticationToken(token);
context.setAuthenticationInfo(info);
if (existing != null) {
context.setSubject(existing);
return createSubject(context);
就是填充SubjectContext,然后根据SubjectContext来创建Subject,此Subject的信息是经过SubjectDAO保存的,再回到登陆方法:
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
Subject subject = securityManager.login(this, token);
PrincipalC
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)
principals = delegating.
host = delegating.
principals = subject.getPrincipals();
if (principals == null || principals.isEmpty()) {
String msg = "Principals returned from securityManager.login( token ) returned a null or " +
"empty value.
This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
this.principals =
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost();
if (host != null) {
this.host =
Session session = subject.getSession(false);
if (session != null) {
this.session = decorate(session);
this.session = null;
最后的这些操作就是将刚才创建出来的Subject信息复制到我们所使用的Subject上,即
subject.login(token)
中的subject中。至此已经太长了,先告一段落,如SubjectDAO和Session的细节后面再详细说明。
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至: 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】浅析混合云和跨地域网络构建实践,分享高性能负载均衡设计,9月21日阿里云专家和你说说网络那些事儿,足不出户看直播,赶紧预约吧!&&
分析的很不错,学习了
阿里云消息服务(Message Service,原MQS)是阿里云商用的消息中间件服务。与传统的消息中间件不同,...
提供一种性能卓越、稳定、安全、便捷的计算服务,帮助您快速构建处理能力出色的应用,解放计算给服务带来的压力,使您的...
基于全网公开发布数据、传播路径和受众群体画像,利用语义分析、情感算法和机器学习,分析公众对品牌形象、热点事件和公...
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
MaxCompute75折抢购
Loading...}

我要回帖

更多关于 spring是单例还是多例 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信