什么是shiro?

Apache Shiro官网上对Shiro的解释如下:

Apache Shiro (pronounced “shee-roh”, the Japanese word for ‘castle’) is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management and can be used to secure any application - from the command line applications, mobile applications to the largest web and enterprise applications.
----Shiro provides the application security API to perform the following aspects (I like to call these the 4 cornerstones of application security):
* Authentication - proving user identity, often called user ‘login’.
* Authorization - access control
* Cryptography - protecting or hiding data from prying eyes
* Session Management - per-user time-sensitive state

大概意思就是说:
shiro是一个功能强大且易于使用的Java安全框架,它的认证,授权,加密和会话管理可以用于保护任何应用程序——来自从命令行应用程序、移动应用程序到最大的web和企业应用程序。
shiro为以下几个方面提供应用程序的安全API(应用程序安全的4大基石):

Authentication - 提供用户身份认证,俗称登录
Authorization - 访问权限控制
Cryptography - 使用加密算法保护或者隐藏数据
Session Management - 用户的会话管理
Login
这部分我会结合自己项目中的一些实例来做出分析:
我的LoginController中对登录操作所做的处理:

@RequestMapping("/doLogin")
    public String doLogin(String loginName, String password, HttpServletRequest request){
        String result = "redirect:/index";
        String fail = "/login";
        String errorMsg = "";

// shiro认证
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(loginName, password);
        try {
            subject.login(token);
            User user = userService.getUserByLogiName(loginName);
            if(user != null){
                user.setPassword("");
                //userCache.put(user);
                redisTemplate.opsForValue().set(Constant.cacheName,user);
            }
        } catch (UnknownAccountException e) {
            result = fail;
            errorMsg = "账户不存在";
        } catch (DisabledAccountException e) {
            result = fail;
            errorMsg = "账户存在问题";
        } catch (AuthenticationException e) {
            result = fail;
            errorMsg = "密码错误";
        } catch (Exception e) {
            result = fail;
            errorMsg = "登陆异常";
            log.error(e.getMessage(),e);
        }

request.setAttribute("error",errorMsg);
        return result;
    }
在进行登录操作的方法doLogin()里首先要做的是shiro认证这个过程。

shiro认证

这个过程主要分为三个部分:

1:获取客户端输入放入用户名,密码。
2:获取数据源中存放的数据即相应的用户名,密码。
3:进行两者的比对,判断是否登录操作成功。
先看一下shiro中是如何实现这个认证的过程的吧:

这里的意思就是说,通过当前的用户对象Subject执行login()方法将用户信息传给Shiro的SecurityManager,而这个SecurityManager会将用户信息委托给内部登录模块,由内部登录模块来调用Realm中的方法来进行数据比对进而判断是否登录成功。
那么我们就有疑问了,这里怎么有出现了三个关键词Subject,SecurityManager和Realm这又有什么用呢?

Subject,SecurityManager和Realm
Subject
对于subject官网上是这样定义的:

Subject
When you’re securing your application, probably the most relevant questions to ask yourself are, “Who is the current user?” or “Is the current user allowed to do X”? It is common for us to ask ourselves these questions as we're writing code or designing user interfaces: applications are usually built based on user stories, and you want functionality represented (and secured) based on a per-user basis. So, the most natural way for us to think about security in our application is based on the current user. Shiro’s API fundamentally represents this way of thinking in its Subject concept.

The word Subject is a security term that basically means "the currently executing user". It's just not called a 'User' because the word 'User' is usually associated with a human being. In the security world, the term 'Subject' can mean a human being, but also a 3rd party process, daemon account, or anything similar. It simply means 'the thing that is currently interacting with the software'. For most intents and purposes though, you can think of this as Shiro’s ‘User’ concept. You can easily acquire the Shiro Subject anywhere in your code as shown in Listing 1 below.

大意就是:subject指的是当前用户,因为我们人的思维更倾向于某个用户有某个角色,因此可以理解为基于当前用户。(不过在安全领域,术语“Subject”可以指一个人,也可以指第三方进程、守护进程帐户或任何类似的东西。)
它的获取方法在官方文档中定义为:相信在我上面的部分项目代码中大家也已经看到了,这块用法是固定的:

import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();

一旦您获得了subject,就可以立即访问当前用户使用Shiro想要做的90%的事情,比如登录、注销、访问他们的会话、执行授权检查,等等.

SecurityManager
SecurityManager是shiro架构核心,协调内部安全组件(如登录,授权,数据源等),用来管理所有的subject。这块会后续进行补充说明。

Realm
官网定义如下:

Realms
The third and final core concept in Shiro is that of a Realm. A Realm acts as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. That is, when it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application.

In this sense a Realm is essentially a security-specific DAO: it encapsulates connection details for data sources and makes the associated data available to Shiro as needed. When configuring Shiro, you must specify at least one Realm to use for authentication and/or authorization. More than one Realm may be configured, but at least one is required.

Shiro provides out-of-the-box Realms to connect to a number of security data sources (aka directories) such as LDAP, relational databases (JDBC), text configuration sources like INI and properties files, and more. You can plug-in your own Realm implementations to represent custom data sources if the default Realms do not meet your needs. Listing 4 below is an example of configuring Shiro (via INI) to use an LDAP directory as one of the application’s Realms.

大意是指:Realm充当的是Shiro和应用程序安全数据之间的==“桥梁”或“连接器”==。也就是说,当实际需要与与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从一个或多个为应用程序配置的Realm中查找这些内容。也是说Realm本质上是一个特定于安全性的DAO(逻辑处理):它封装了数据源的连接细节,并根据需要将关联的数据提供给Shiro。在配置Shiro时,必须指定至少一个用于身份验证和/或授权的领域。可以配置多个域,但至少需要一个。

Shiro提供了开箱即用的领域,可以连接到许多安全数据源(即目录),如LDAP、关系数据库(JDBC)、文本配置源(如INI)和属性文件,等等。也可以自定义Realm实现来表示自定义数据源。(下文我会贴出项目中自定义的Realm做为参考)。

下面是详细的shiro结构图:

获取subject

首先是第一步要获取客户端传来的数据

Subject subject = SecurityUtils.getSubject();
login()
subject.login(token);

内部调用的是subject接口声明的方法:

void login(AuthenticationToken token) throws AuthenticationException;

我们来看一下login()的具体实现:

public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        
        Subject subject = securityManager.login(this, token);
        
        PrincipalCollection principals;
        String host = null;
        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            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 = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

要注意的是内部调用的是securityManager.login(this, toke)方法。
我们再来进一步的看一下securityManager.login(this, toke)的内部实现:
首先SecurityManager中对login方法的声明:

Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;

实现类DefaultSecurityManager中对login()的实现

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }
        Subject loggedIn = createSubject(token, info, subject);
        onSuccessfulLogin(token, info, loggedIn);
        return loggedIn;
    }

在这里我们发现调用了authenticate(token)
这个方法是从哪里来的呢?再来看看SecurityManager接口中的方法和它所继承的类:

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
    Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException;
    void logout(Subject subject);
    Subject createSubject(SubjectContext context);

}

这里我们看到 SecurityManager 接口继承了 Authenticator 登录认证的接口比如登录(Authenticator),权限验证(Authorizer)等。
再来看一看Authenticator接口中都声明了哪些方法:

public interface Authenticator {
    public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException;
}

也就是我们刚才在DefaultSecurityManager中对login()的实现中调用的方法,忘了的小盆友可以回过头去看一眼哦,O(∩_∩)O哈哈~。
AbstractAuthenticator中authenticate()的实现:

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        // 调用doAuthenticate方法
        info = doAuthenticate(token);
        if (info == null) {
            ...
        }
    } catch (Throwable t) {
        ...
    }
    ...
}

调用了doAuthenticate(token)方法。
我们再来看ModularRealmAuthenticator中doAuthenticate(token)方法的实现:

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
        // Realm唯一时
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        return doMultiRealmAuthentication(realms, authenticationToken);
    }
}

调用了doSingleRealmAuthentication(realms.iterator().next(), authenticationToken)
再往下看:doSingleRealmAuthentication的实现:

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
    if (!realm.supports(token)) {
        ...
    }
    // 调用Realm的getAuthenticationInfo方法获取AuthenticationInfo信息
    AuthenticationInfo info = realm.getAuthenticationInfo(token);
    if (info == null) {
        ...
    }
    return info;
}

realm.getAuthenticationInfo(token)
它调用Realm的getAuthenticationInfo(token)方法。
而在Realm中我们看一下用户认证方法重写:

@Service
public class MyShiroRealm extends AuthorizingRealm {

//用于用户查询
    @Reference
    private UserService userService;
    //用户认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户信息
        String name = authenticationToken.getPrincipal().toString();
        User user = userService.getUserByLogiName(name);
        if(user == null){
            return null;
        }else{
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName());
            return simpleAuthenticationInfo;
        }
    }

//角色权限和对应权限添加
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户名
        String name= (String) principalCollection.getPrimaryPrincipal();
        //查询用户名称
        //User user = loginService.findByName(name);
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        /*for (Role role:user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            for (Permission permission:role.getPermissions()) {
                //添加权限
                simpleAuthorizationInfo.addStringPermission(permission.getPermission());
            }
        }*/
        return simpleAuthorizationInfo;
    }
}

主要重写了俩个方法:
doGetAuthenticationInfo()主要是进行登录认证
doGetAuthorizationInfo()主要是进行角色权限和对应权限的添加

Shiro 配置

要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现(类似SpringMvc 通过DispachServlet 来主控制一样)
filter主要是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。如下:

@Configuration
public class ShiroConfiguration {
    //将自己的验证方式加入容器
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(new CredentialsMatcher());
        return myShiroRealm;
    }
    //权限管理,配置主要是Realm的管理认证
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

//Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> map = new HashMap<String, String>();
        //登出
        map.put("/logout","logout");
        //对所有用户认证
        map.put("/user/**","authc");
        map.put("/org/**","authc");
        map.put("/role/**","authc");
        map.put("/menu/**","authc");
        map.put("/log/**","authc");
        map.put("/index","authc");
        //其他资源不拦截
        map.put("/**","anon");
        map.put("/doLogin","anon");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

}

方法一:myShiroRealm()方法
主要是将我自定义的匹配器对象当做参数传给MyShiroRealm并返回。
(也就是说把我自定义来判断规则告诉shiro让shiro来管理)
而在我自定义的密码匹配器中是这样实现的:

public class CredentialsMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken uToken =(UsernamePasswordToken) token;
        //获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
        String inPassword = new String(uToken.getPassword());
        //获得数据库中的密码
        String dbPassword = (String) info.getCredentials();
        System.err.println("inPassword:" + inPassword);
        System.err.println("dbPassword:" + dbPassword);
        //进行密码的比对
        boolean flag = PasswordHash.validatePassword(inPassword,dbPassword);
        return flag;
    }
}

这里我使用的是将客户端的密码进行加盐处理之后再和我数据库中的数据进行比对判断。
方法二:securityManager()
实例化了DefaultWebSecurityManager类,将上面myShiroRealm()的返回值当做参数,也就是配置Realm的管理认证。
方法三:shiroFilterFactoryBean(DefaultWebSecurityManager securityManager)
Filter工厂,设置对应的过滤条件和跳转条件。

shiro源码分析(二)Subject和Session

继续上一篇文章的案例,第一次使用SecurityUtils.getSubject()来获取Subject时

public static Subject getSubject() {Subject subject = ThreadContext.getSubject();if (subject == null) {subject = (new Subject.Builder()).buildSubject();ThreadContext.bind(subject);}return subject;}

使用ThreadLocal模式来获取,若没有则创建一个并绑定到当前线程。此时创建使用的是Subject内部类Builder来创建的,Builder会创建一个SubjectContext接口的实例DefaultSubjectContext,最终会委托securityManager来根据SubjectContext信息来创建一个Subject,下面详细说下该过程,在DefaultSecurityManager的createSubject方法中:

public Subject createSubject(SubjectContext subjectContext) {SubjectContext context = copy(subjectContext);context = ensureSecurityManager(context);context = resolveSession(context);context = resolvePrincipals(context);Subject subject = doCreateSubject(context);save(subject);return subject;}

首先就是复制SubjectContext,SubjectContext 接口继承了Map<String, Object>,然后加入了几个重要的SecurityManager、SessionId、Subject、PrincipalCollection、Session、boolean authenticated、boolean sessionCreationEnabled、Host、AuthenticationToken、AuthenticationInfo等众多信息。

讨论1:首先是SubjectContext为什么要去实现Map<String, Object>?
SubjectContext提供了常用的get、set方法,还提供了一个resolve方法,以SecurityManager为例:

SecurityManager getSecurityManager();void setSecurityManager(SecurityManager securityManager);SecurityManager resolveSecurityManager();

这些get、set方法则用于常用的设置和获取,而resolve则表示先调用getSecurityManager,如果获取不到,则使用其他途径来获取,如DefaultSubjectContext的实现:

public SecurityManager resolveSecurityManager() {SecurityManager securityManager = getSecurityManager();if (securityManager == null) {if (log.isDebugEnabled()) {log.debug("No SecurityManager available in subject context map.  " +"Falling back to SecurityUtils.getSecurityManager() lookup.");}try {securityManager = SecurityUtils.getSecurityManager();} catch (UnavailableSecurityManagerException e) {if (log.isDebugEnabled()) {log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);}}}return securityManager;}

如果getSecurityManager获取不到,则使用SecurityUtils工具来获取。
再如resolvePrincipals

public PrincipalCollection resolvePrincipals() {PrincipalCollection principals = getPrincipals();if (CollectionUtils.isEmpty(principals)) {//check to see if they were just authenticated:AuthenticationInfo info = getAuthenticationInfo();if (info != null) {principals = info.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {Subject subject = getSubject();if (subject != null) {principals = subject.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {//try the session:Session session = resolveSession();if (session != null) {principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}

普通的getPrincipals()获取不到,尝试使用其他属性来获取。
讨论2:此时就有一个问题,有必要再对外公开getPrincipals方法吗?什么情况下外界会去调用getPrincipals方法而不会去调用resolvePrincipals方法?

然后我们继续回到上面的类图设计上:
DefaultSubjectContext继承了MapContext,MapContext又实现了Map<String, Object>,看下此时的MapContext有什么东西:

public class MapContext implements Map<String, Object>, Serializable {private static final long serialVersionUID = 5373399119017820322L;private final Map<String, Object> backingMap;public MapContext() {this.backingMap = new HashMap<String, Object>();}public MapContext(Map<String, Object> map) {this();if (!CollectionUtils.isEmpty(map)) {this.backingMap.putAll(map);}}//略
}

MapContext内部拥有一个类型为HashMap的backingMap属性,大部分方法都由HashMap来实现,然后仅仅更改某些行为,MapContext没有选择去继承HashMap,而是使用了组合的方式,更加容易去扩展,如backingMap的类型不一定非要选择HashMap,可以换成其他的Map实现,一旦MapContext选择继承HashMap,如果想对其他的Map类型进行同样的功能增强的话,就需要另写一个类来继承它然后改变一些方法实现,这样的话就会有很多重复代码。这也是设计模式所强调的少用继承多用组合。但是MapContext的写法使得子类没法去替换HashMap,哎,心塞 。
MapContext又提供了如下几个返回值不可修改的方法:

public Set<String> keySet() {return Collections.unmodifiableSet(backingMap.keySet());}public Collection<Object> values() {return Collections.unmodifiableCollection(backingMap.values());}public Set<Entry<String, Object>> entrySet() {return Collections.unmodifiableSet(backingMap.entrySet());}

有点扯远了。继续回到DefaultSecurityManager创建Subject的地方:

public Subject createSubject(SubjectContext subjectContext) {//create a copy so we don't modify the argument's backing map:SubjectContext context = copy(subjectContext);//ensure that the context has a SecurityManager instance, and if not, add one:context = ensureSecurityManager(context);//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before//sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the//process is often environment specific - better to shield the SF from these details:context = resolveSession(context);//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first//if possible before handing off to the SubjectFactory:context = resolvePrincipals(context);Subject subject = doCreateSubject(context);//save this subject for future reference if necessary://(this is needed here in case rememberMe principals were resolved and they need to be stored in the//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).//Added in 1.2:save(subject);return subject;}

对于context,把能获取到的参数都凑齐,SecurityManager、Session。resolveSession尝试获取context的map中获取Session,若没有则尝试获取context的map中的Subject,如果存在的话,根据此Subject来获取Session,若没有再尝试获取sessionId,若果有了sessionId则构建成一个DefaultSessionKey来获取对应的Session。
整个过程如下;

protected SubjectContext resolveSession(SubjectContext context) {if (context.resolveSession() != null) {log.debug("Context already contains a session.  Returning.");return context;}try {//Context couldn't resolve it directly, let's see if we can since we have direct access to//the session manager:Session session = resolveContextSession(context);if (session != null) {context.setSession(session);}} catch (InvalidSessionException e) {log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +"(session-less) Subject instance.", e);}return context;}

先看下context.resolveSession():

public Session resolveSession() {//这里则是直接从map中取出SessionSession session = getSession();if (session == null) {//try the Subject if it exists://若果没有,尝试从map中取出SubjectSubject existingSubject = getSubject();if (existingSubject != null) {//这里就是Subject获取session的方法,需要详细看下session = existingSubject.getSession(false);}}return session;}

existingSubject.getSession(false):通过Subject获取Session如下

public Session getSession(boolean create) {if (log.isTraceEnabled()) {log.trace("attempting to get session; create = " + create +"; session is null = " + (this.session == null) +"; session has id = " + (this.session != null && session.getId() != null));}if (this.session == null && create) {//added in 1.2:if (!isSessionCreationEnabled()) {String msg = "Session creation has been disabled for the current subject.  This exception indicates " +"that there is either a programming error (using a session when it should never be " +"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +"for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +"for more.";throw new DisabledSessionException(msg);}log.trace("Starting session for host {}", getHost());SessionContext sessionContext = createSessionContext();Session session = this.securityManager.start(sessionContext);this.session = decorate(session);}return this.session;}

getSession()的参数表示是否创建session,如果Session为空,并且传递的参数为true,则会创建一个Session。然而这里传递的是false,也就是说不会在创建Subject的时候来创建Session,所以把创建Session过程说完后,再回到此处是要记着不会去创建一个Session。但是我们可以来看下是如何创建Session的,整体三大步骤,先创建一个SessionContext ,然后根据SessionContext 来创建Session,最后是装饰Session,由于创建Session过程内容比较多,先说说装饰Session。

protected Session decorate(Session session) {if (session == null) {throw new IllegalArgumentException("session cannot be null");}return new StoppingAwareProxiedSession(session, this);}

装饰Session就是讲Session和DelegatingSubject封装起来。
然后来说Session的创建过程,这和Subject的创建方式差不多。

看下SessionContext的主要内容:

void setHost(String host);String getHost();Serializable getSessionId();void setSessionId(Serializable sessionId);

主要两个内容,host和sessionId。
接下来看下如何由SessionContext来创建Session:

protected Session doCreateSession(SessionContext context) {Session s = newSessionInstance(context);if (log.isTraceEnabled()) {log.trace("Creating session for host {}", s.getHost());}create(s);return s;}protected Session newSessionInstance(SessionContext context) {return getSessionFactory().createSession(context);}

和Subject一样也是由一个SessionFactory根据SessionContext来创建出一个Session,看下默认的SessionFactory SimpleSessionFactory的创建过程:

public Session createSession(SessionContext initData) {if (initData != null) {String host = initData.getHost();if (host != null) {return new SimpleSession(host);}}return new SimpleSession();}

如果SessionContext有host信息,就传递给Session,然后就是直接new一个Session接口的实现SimpleSession,先看下Session接口有哪些内容:

public interface Session {Serializable getId();Date getStartTimestamp();Date getLastAccessTime();long getTimeout() throws InvalidSessionException;void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;String getHost();void touch() throws InvalidSessionException;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;
}

id:Session的唯一标识,创建时间、超时时间等内容。
再看SimpleSession的创建过程:

public SimpleSession() {this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT;this.startTimestamp = new Date();this.lastAccessTime = this.startTimestamp;}public SimpleSession(String host) {this();this.host = host;}

设置下超时时间为DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT 30分钟,startTimestamp 和lastAccessTime设置为现在开始。就这样构建出了一个Session的实例,然后就是需要将该实例保存起来:

protected Session doCreateSession(SessionContext context) {Session s = newSessionInstance(context);if (log.isTraceEnabled()) {log.trace("Creating session for host {}", s.getHost());}create(s);return s;}
protected void create(Session session) {if (log.isDebugEnabled()) {log.debug("Creating new EIS record for new session instance [" + session + "]");}sessionDAO.create(session);}

即该进行create(s)操作了,又和Subject极度的相像,使用sessionDAO来保存刚才创建的Session。再来看下SessionDAO接口:

public interface SessionDAO {Serializable create(Session session);Session readSession(Serializable sessionId) throws UnknownSessionException;void update(Session session) throws UnknownSessionException;void delete(Session session);Collection<Session> getActiveSessions();
}AbstractSessionDAO:有一个重要的属性SessionIdGenerator,它负责给Session创建sessionId,SessionIdGenerator接口如下:
public interface SessionIdGenerator {Serializable generateId(Session session);
}

很简单,参数为Session,返回sessionId。SessionIdGenerator 的实现有两个JavaUuidSessionIdGenerator、RandomSessionIdGenerator。而AbstractSessionDAO默认采用的是JavaUuidSessionIdGenerator,如下:

public AbstractSessionDAO() {this.sessionIdGenerator = new JavaUuidSessionIdGenerator();}

MemorySessionDAO继承了AbstractSessionDAO,它把Session存储在一个ConcurrentMap<Serializable, Session> sessions集合中,key为sessionId,value为Session。
CachingSessionDAO:主要配合在别的地方存储session。先不介绍,之后的文章再详细说。
对于本案例来说SessionDAO为MemorySessionDAO。至此整个Session的创建过程就走通了。

刚才虽然说了整个Session的创建过程,回到上文所说的,不会去创建Session的地方。在创建Subject搜集session信息时,使用的此时的Subject的Session、sessionId都为空,所以获取不到Session。然后就是doCreateSubject:

protected Subject doCreateSubject(SubjectContext context) {return getSubjectFactory().createSubject(context);}

就是通过SubjectFactory工厂接口来创建Subject的,而DefaultSecurityManager默认使用的
SubjectFactory是DefaultSubjectFactory:

public DefaultSecurityManager() {super();this.subjectFactory = new DefaultSubjectFactory();this.subjectDAO = new DefaultSubjectDAO();}

继续看DefaultSubjectFactory是怎么创建Subject的:

public Subject createSubject(SubjectContext context) {SecurityManager securityManager = context.resolveSecurityManager();Session session = context.resolveSession();boolean sessionCreationEnabled = context.isSessionCreationEnabled();PrincipalCollection principals = context.resolvePrincipals();boolean authenticated = context.resolveAuthenticated();String host = context.resolveHost();return new DelegatingSubject(principals, authenticated, host, session, sessionCreationEnabled, securityManager);}

仍然就是将这些属性传递给DelegatingSubject,也没什么好说的。创建完成之后,就需要将刚创建的Subject保存起来,仍回到:

public Subject createSubject(SubjectContext subjectContext) {//create a copy so we don't modify the argument's backing map:SubjectContext context = copy(subjectContext);//ensure that the context has a SecurityManager instance, and if not, add one:context = ensureSecurityManager(context);//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before//sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the//process is often environment specific - better to shield the SF from these details:context = resolveSession(context);//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first//if possible before handing off to the SubjectFactory:context = resolvePrincipals(context);Subject subject = doCreateSubject(context);//save this subject for future reference if necessary://(this is needed here in case rememberMe principals were resolved and they need to be stored in the//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).//Added in 1.2:save(subject);return subject;}

来看下save方法:

protected void save(Subject subject) {this.subjectDAO.save(subject);}

可以看到又是使用另一个模块来完成的即SubjectDAO,SubjectDAO接口如下:

public interface SubjectDAO {Subject save(Subject subject);void delete(Subject subject);
}

很简单,就是保存和删除一个Subject。我们看下具体的实现类DefaultSubjectDAO是如何来保存的:

public Subject save(Subject subject) {if (isSessionStorageEnabled(subject)) {saveToSession(subject);} else {log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " +"authentication state are expected to be initialized on every request or invocation.", subject);}return subject;}

首先就是判断isSessionStorageEnabled,是否要存储该Subject的session来
DefaultSubjectDAO:有一个重要属性SessionStorageEvaluator,它是用来决定一个Subject的Session来记录Subject的状态,接口如下

public interface SessionStorageEvaluator {boolean isSessionStorageEnabled(Subject subject);
}

其实现为DefaultSessionStorageEvaluator:

public class DefaultSessionStorageEvaluator implements SessionStorageEvaluator {private boolean sessionStorageEnabled = true;public boolean isSessionStorageEnabled(Subject subject) {return (subject != null && subject.getSession(false) != null) || isSessionStorageEnabled();}

决定策略就是通过DefaultSessionStorageEvaluator 的sessionStorageEnabled的true或false 和subject是否有Session对象来决定的。如果允许存储Subject的Session的话,下面就说具体的存储过程:

protected void saveToSession(Subject subject) {//performs merge logic, only updating the Subject's session if it does not match the current state:mergePrincipals(subject);mergeAuthenticationState(subject);}
protected void mergePrincipals(Subject subject) {//merge PrincipalCollection state:PrincipalCollection currentPrincipals = null;//SHIRO-380: added if/else block - need to retain original (source) principals//This technique (reflection) is only temporary - a proper long term solution needs to be found,//but this technique allowed an immediate fix that is API point-version forwards and backwards compatible////A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 +if (subject.isRunAs() && subject instanceof DelegatingSubject) {try {Field field = DelegatingSubject.class.getDeclaredField("principals");field.setAccessible(true);currentPrincipals = (PrincipalCollection)field.get(subject);} catch (Exception e) {throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e);}}if (currentPrincipals == null || currentPrincipals.isEmpty()) {currentPrincipals = subject.getPrincipals();}Session session = subject.getSession(false);if (session == null) {//只有当Session为空,并且currentPrincipals不为空的时候才会去创建Session//Subject subject = SecurityUtils.getSubject()此时两者都是为空的,//不会去创建Sessionif (!CollectionUtils.isEmpty(currentPrincipals)) {session = subject.getSession();session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);}//otherwise no session and no principals - nothing to save} else {PrincipalCollection existingPrincipals =(PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (CollectionUtils.isEmpty(currentPrincipals)) {if (!CollectionUtils.isEmpty(existingPrincipals)) {session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);}//otherwise both are null or empty - no need to update the session} else {if (!currentPrincipals.equals(existingPrincipals)) {session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals);}//otherwise they're the same - no need to update the session}}}

上面有我们关心的重点,当subject.getSession(false)获取的Session为空时(它不会去创建Session),此时就需要去创建Session,subject.getSession()则默认调用的是subject.getSession(true),则会进行Session的创建,创建过程上文已详细说明了。
在第一次创建Subject的时候

Subject subject = SecurityUtils.getSubject();

虽然Session为空,但此时还没有用户身份信息,也不会去创建Session。案例中的subject.login(token),该过程则会去创建Session,具体看下过程:

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {info = authenticate(token);} catch (AuthenticationException ae) {try {onFailedLogin(token, ae, subject);} catch (Exception e) {if (log.isInfoEnabled()) {log.info("onFailedLogin method threw an " +"exception.  Logging and propagating original AuthenticationException.", e);}}throw ae; //propagate}//在该过程会进行Session的创建Subject loggedIn = createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;}

对于验证过程上篇文章已经简单说明了,这里不再说明,重点还是在验证通过后,会设置Subject的身份,即用户名:

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);}

有了认证成功的AuthenticationInfo信息,SubjectContext在resolvePrincipals便可以获取用户信息,即通过AuthenticationInfo的getPrincipals()来获得。

public PrincipalCollection resolvePrincipals() {PrincipalCollection principals = getPrincipals();if (CollectionUtils.isEmpty(principals)) {//check to see if they were just authenticated:AuthenticationInfo info = getAuthenticationInfo();if (info != null) {principals = info.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {Subject subject = getSubject();if (subject != null) {principals = subject.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {//try the session:Session session = resolveSession();if (session != null) {principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}

PrincipalCollection不为空了,在save(subject)的时候会得到session为空,同时PrincipalCollection不为空,则会执行Session的创建。也就是说在认证通过后,会执行Session的创建,Session创建完成之后会进行一次装饰,即用StoppingAwareProxiedSession将创建出来的session和subject关联起来,然后又进行如下操作:

public void login(AuthenticationToken token) throws AuthenticationException {clearRunAsIdentitiesInternal();//这里的Subject则是经过认证后创建的并且也含有刚才创建的session,类型为//StoppingAwareProxiedSession,即是该subject本身和session的合体。Subject subject = securityManager.login(this, token);PrincipalCollection principals;String host = null;if (subject instanceof DelegatingSubject) {DelegatingSubject delegating = (DelegatingSubject) subject;//we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:principals = delegating.principals;host = delegating.host;} else {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 = principals;this.authenticated = true;if (token instanceof HostAuthenticationToken) {host = ((HostAuthenticationToken) token).getHost();}if (host != null) {this.host = host;}Session session = subject.getSession(false);if (session != null) {//在这里可以看到又进行了一次装饰this.session = decorate(session);} else {this.session = null;}}

subject 创建出来之后,暂且叫内部subject,就是把认证通过的内部subject的信息和session复制给我们外界使用的subject.login(token)的subject中,这个subject暂且叫外部subject,看下session的赋值,又进行了一次装饰,这次装饰则把session(类型为StoppingAwareProxiedSession,即是内部subject和session的合体)和外部subject绑定到一起。

在Shiro中,无论是认证还是权限控制都是通过过滤器来实现的,在应用中可能会配置很多个过滤器,但对于不同的访问请求所需要经过的过滤器肯定是不一样的,那么当发起一个请求时,到底会应用上哪些过滤器,对于我们使用Shiro就显示得格外重要;下面就来讲讲一个请求到底会经过哪些过滤器。

在Shiro中,确证一个请求会经过哪些过滤器是通过org.apache.shiro.web.filter.mgt.FilterChainResolver接口来定义的,下面是接口定义:

public interface FilterChainResolver {FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);}

接口中只有一个方法getChain,就是用于确定请求到底要经过哪些过滤器,然后将这些过滤器封装成一个FilterChain对象,FilterCahin我们很熟悉,在使用Servlet的时候经常见面。
FilterChainResolver只是一个接口,Shiro提供了一个默认的实现类org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver,该实现类会根据请求路径进行匹配过滤器。
在看PathMatchingFilterChainResolver源码之前先说一下FilterChainManager中的FilterChain是怎么来的,以ini配置为例:

[urls]
/static/**=anon
/formfilterlogin=authc
/role=authc,roles[admin]

其中/static/**/formfilterlogin/role 就是受FilterChainManager管理的FilterChain的名称。下面看看FilterChainManager是如何管理FilterChain的。
Shiro提供了FilterChainManager一个的默认实现:org.apache.shiro.web.filter.mgt.DefaultFilterChainManager,其createChain方法会在系统启动的时候被org.apache.shiro.web.config.IniFilterChainResolverFactory调用,用于创建各个FilterChain。下面以/role=authc,roles[admin]配置为例,chainName就是/rolechainDefinition就是authc,roles[admin]

public void createChain(String chainName, String chainDefinition) {if (!StringUtils.hasText(chainName)) {throw new NullPointerException("chainName cannot be null or empty.");}if (!StringUtils.hasText(chainDefinition)) {throw new NullPointerException("chainDefinition cannot be null or empty.");}if (log.isDebugEnabled()) {log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");}// filterTokens数组有两个元素,第一个为authc,第二个为roles[admin],因为配置时可以配置多个Filter,// 多个Filter间以逗号分隔String[] filterTokens = splitChainDefinition(chainDefinition);for (String token : filterTokens) {// 对roles[admin]进行分隔,数组中第一个元素为roles,第二个为adminString[] nameConfigPair = toNameConfigPair(token);// 添加FilterChainaddToChain(chainName, nameConfigPair[0], nameConfigPair[1]);}
}
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {if (!StringUtils.hasText(chainName)) {throw new IllegalArgumentException("chainName cannot be null or empty.");}// 根据filterName(role)查找出FilterFilter filter = getFilter(filterName);if (filter == null) {throw new IllegalArgumentException("There is no filter with name '" + filterName +"' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " +"filter with that name/path has first been registered with the addFilter method(s).");}// 应用FilterChain配置,以roles[amdin]为例,调用该方法后roles过滤器就知道其进行拦截器需要admin角色applyChainConfig(chainName, filter, chainSpecificFilterConfig);// 如果chainName以前没处理过则创建一个新的NamedFilterList对象,如果处理过则返回以前的NamedFilterList对象// 所以在FilterChainManager中,存储Filter的是NamedFilterList对象NamedFilterList chain = ensureChain(chainName);// 将过滤器添加至链中chain.add(filter);
}

在了解了FilterChainManager是如何创建与存储FilterChain以后,再来看看FilterChainResolver是如何确定一个请求需要经过哪些过滤器的。

public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {FilterChainManager filterChainManager = getFilterChainManager();// 判断FilterChainManager中是否有FilterChain,如果没有则返回nullif (!filterChainManager.hasChains()) {return null;}// 获取请求URIString requestURI = getPathWithinApplication(request);// FilterChain的名称就是路径匹配符,如果请求URI匹配上了某个FilterChain // 则调用FilterChainManager.proxy方法返回一个FilterChain对象,注意是返回第一个匹配FilterChain// 也就是说如果在ini配置文件中配置了多个同名的FilterChain,则只有第一个FilterChain有效for (String pathPattern : filterChainManager.getChainNames()) {if (pathMatches(pathPattern, requestURI)) {if (log.isTraceEnabled()) {log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +"Utilizing corresponding filter chain...");}return filterChainManager.proxy(originalChain, pathPattern);}}return null;
}

下面是DefualtFilterChainManager.proxy方法源码:

public FilterChain proxy(FilterChain original, String chainName) {// 路径模式匹配(如/static/**)就是FilterChain名称// 根据FilterChain名称查找NamedFilterList对象(存储了配置的Filter)NamedFilterList configured = getChain(chainName);if (configured == null) {String msg = "There is no configured chain under the name/key [" + chainName + "].";throw new IllegalArgumentException(msg);}// 调用NamedFilterList.proxy方法return configured.proxy(original);
}

NamedFilterList的实现类为org.apache.shiro.web.filter.mgt.SimpleNamedFilterList

public FilterChain proxy(FilterChain orig) {// 返回ProxiedFilterChain对象,该对象就是当一个请求到来后需要被执行的FilterChain对象// 该对象只是一个代理对象,代理了两个FilterChain,一个是NamedFilterList,另一个是原始的FilterChain对象// 原始的FilterChain对象包含了在web.xml中配置并应用上的Filterreturn new ProxiedFilterChain(orig, this);
}
public class ProxiedFilterChain implements FilterChain {private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);private FilterChain orig;private List<Filter> filters;private int index = 0;public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {if (orig == null) {throw new NullPointerException("original FilterChain cannot be null.");}this.orig = orig;this.filters = filters;this.index = 0;}public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// 可以看出,先执行原始Filter,再执行NamedFilterList中的Filterif (this.filters == null || this.filters.size() == this.index) {//we've reached the end of the wrapped chain, so invoke the original one:if (log.isTraceEnabled()) {log.trace("Invoking original filter chain.");}this.orig.doFilter(request, response);} else {if (log.isTraceEnabled()) {log.trace("Invoking wrapped filter at index [" + this.index + "]");}this.filters.get(this.index++).doFilter(request, response, this);}}
}

至此,Shiro创建FilterChain过程讲解完毕。

Shiro源码分析-初始化-SecurityManager

一、Shiro解析ini的步骤如下: 
1、org.apache.shiro.config.IniSecurityManagerFactory类构造方法: 
Java代码

  1. public IniSecurityManagerFactory(String iniResourcePath) {
  2. this(Ini.fromResourcePath(iniResourcePath));
  3. }

将ini文件解析交给Ini的静态方法fromResourcePath完成。并把解析后的Ini对象设置由自身持有 
Java代码

  1. public IniSecurityManagerFactory(Ini config) {
  2. setIni(config);
  3. }

2、org.apache.shiro.config.Ini类解析逻辑: 
Java代码

  1. public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
  2. if (!StringUtils.hasLength(resourcePath)) {
  3. throw new IllegalArgumentException("Resource Path argument cannot be null or empty.");
  4. }
  5. //此处新建Ini对象
  6. Ini ini = new Ini();
  7. ini.loadFromPath(resourcePath);
  8. return ini;
  9. }
  10. //根据资源路径获取输入流,交给load方法进行处理
  11. public void loadFromPath(String resourcePath) throws ConfigurationException {
  12. InputStream is;
  13. try {
  14. is = ResourceUtils.getInputStreamForPath(resourcePath);
  15. } catch (IOException e) {
  16. throw new ConfigurationException(e);
  17. }
  18. load(is);
  19. }

ResourceUtils.getInputStreamForPath(resourcePath);这里支持三种获取资源的方式:

classpath shiro-config.ini 从类路径中查找ini配置
url http://....../shiro-config.ini 从指定的url获取ini配置
file D:\shiro-config.ini 从指定的文件路径获取ini配置

3、对获取的资源输入流最终交给文本扫描器Scaner,执行过程代码片段: 
Java代码

  1. public void load(Scanner scanner) {
  2. String sectionName = DEFAULT_SECTION_NAME;
  3. StringBuilder sectionContent = new StringBuilder();
  4. while (scanner.hasNextLine()) {
  5. String rawLine = scanner.nextLine();
  6. String line = StringUtils.clean(rawLine);
  7. //此处跳过ini文件格式的注释及空值
  8. if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
  9. //skip empty lines and comments:
  10. continue;
  11. }
  12. //此处主要获取section部分,根据[]规则
  13. String newSectionName = getSectionName(line);
  14. if (newSectionName != null) {
  15. //此处代码主要用于构造Section对象,并放进sections集合中
  16. addSection(sectionName, sectionContent);
  17. //reset the buffer for the new section:
  18. sectionContent = new StringBuilder();
  19. sectionName = newSectionName;
  20. if (log.isDebugEnabled()) {
  21. log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
  22. }
  23. } else {
  24. //normal line - add it to the existing content buffer:
  25. sectionContent.append(rawLine).append("\n");
  26. }
  27. }
  28. //finish any remaining buffered content:
  29. addSection(sectionName, sectionContent);
  30. }

上段代码主要是组装ini文件中的section。细心的同学会发现Section、Ini都是实现了Map接口。Section持有的LinkedHashMap容器实际上是当前section中的所有键值对,而Ini持有的LinkedHashMap容器实际上是所有section名称与section对象的键值对。 
至此,ini文件的解析已经完成,其ini中的内容已全部以map的形式存放在Ini实例中。 
至于保存的结果是什么样的呢?以下面的一段配置为例: 
Java代码

  1. [main]
  2. #authenticator
  3. authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
  4. authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
  5. authenticator.authenticationStrategy=$authenticationStrategy
  6. securityManager.authenticator=$authenticator

Ini的sections属性[key=main,value=Section对象],此Section对象内部的props保存了所有main部分key-value映射。目前都是String类型,实例化在下一步完成的。

二、由Ini实例构造SecurityManager对象 
Java代码

  1. SecurityManager securityManager = factory.getInstance();

IniSecurityManagerFactory的继承关系为: 
IniSecurityManagerFactory->IniFactorySupport->AbstractFactory->Factory 
这里的getInstance方法由最上级的抽象类:org.apache.shiro.util.AbstractFactory提供,源码如下: 
Java代码

  1. //该方法只是用于判断是否单例(默认为单例)
  2. public T getInstance() {
  3. T instance;
  4. if (isSingleton()) {
  5. if (this.singletonInstance == null) {
  6. this.singletonInstance = createInstance();
  7. }
  8. instance = this.singletonInstance;
  9. } else {
  10. instance = createInstance();
  11. }
  12. if (instance == null) {
  13. String msg = "Factory 'createInstance' implementation returned a null object.";
  14. throw new IllegalStateException(msg);
  15. }
  16. return instance;
  17. }
  18. //由子类IniFactorySupport创建实例
  19. protected abstract T createInstance();

IniFactorySupport的createInstance实现如下(省略了无关紧要的日志、判断代码): 
Java代码

  1. public T createInstance() {
  2. //此处获取解析ini文件产生的Ini对象
  3. Ini ini = resolveIni();
  4. T instance;
  5. if (CollectionUtils.isEmpty(ini)) {
  6. //如果ini为空,则创建默认实例
  7. instance = createDefaultInstance();
  8. } else {
  9. //根据ini对象创建实例
  10. instance = createInstance(ini);
  11. }
  12. return instance;
  13. }
  14. protected abstract T createInstance(Ini ini);
  15. protected abstract T createDefaultInstance();

上面两个抽象方法,则交给实现类IniSecurityManagerFactory完成了。 
先阅读createDefaultInstance方法源码 
Java代码

  1. protected SecurityManager createDefaultInstance() {
  2. return new DefaultSecurityManager();
  3. }

终于在这个不起眼的地方,看到了初始化最核心的接口SecurityManager了。稍微暂停一下,发个SecurityManager的类图: 
 
由此图可以看出来,SecurityManager继承了三个接口(认证、授权、session管理),认证授权是安全框架最核心的功能,而shiro提供了自身的session管理机制(这也是shiro的一大亮点)。图中除了DefaultSecurityManager,其它所有类都是抽象类,由此可看出,DefaultSecurityManager是作为默认的安全管理器。 
再来看new DefaultSecurityManager();做的事情 
Java代码

  1. public DefaultSecurityManager() {
  2. super();
  3. this.subjectFactory = new DefaultSubjectFactory();
  4. this.subjectDAO = new DefaultSubjectDAO();
  5. }

这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。

类名称 构造方法创建的默认对象
DefaultSecurityManager DefaultSubjectFactory,DefaultSubjectDAO
SessionsSecurityManager DefaultSessionManager
AuthorizingSecurityManager ModularRealmAuthorizer
AuthenticatingSecurityManager ModularRealmAuthenticator
RealmSecurityManager
CachingSecurityManager

在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。需要注意的是,RealmSecurityManager、CachingSecurityManager并没有设置默认的对象,所以这个是交给开发人员自己配置的。

接下来看createInstance实现细节: 
Java代码

  1. protected SecurityManager createInstance(Ini ini) {
  2. if (CollectionUtils.isEmpty(ini)) {
  3. throw new NullPointerException("Ini argument cannot be null or empty.");
  4. }
  5. //SecurityManager的创建工作交给createSecurityManager方法
  6. SecurityManager securityManager = createSecurityManager(ini);
  7. if (securityManager == null) {
  8. String msg = SecurityManager.class + " instance cannot be null.";
  9. throw new ConfigurationException(msg);
  10. }
  11. return securityManager;
  12. }
  13. private SecurityManager createSecurityManager(Ini ini) {
  14. //单独获取main部分进行初始化
  15. Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
  16. if (CollectionUtils.isEmpty(mainSection)) {
  17. //try the default:
  18. mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
  19. }
  20. return createSecurityManager(ini, mainSection);
  21. }
  22. private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
  23. //创建DefaultSecurityManager,并且根据ini初始化realm
  24. Map<String, ?> defaults = createDefaults(ini, mainSection);
  25. //根据main部分构造对象(包括属性依赖,创建实例等)
  26. Map<String, ?> objects = buildInstances(mainSection, defaults);
  27. //从ReflectionBuilder的objects获取SecurityManager实例。ReflectionBuilder负责shiro的所有反射构造实例的工作,并且把实例放在objects的集合中持有。
  28. SecurityManager securityManager = getSecurityManagerBean();
  29. //如果显示指定了realm,则不会自动配置realm。
  30. boolean autoApplyRealms = isAutoApplyRealms(securityManager);
  31. if (autoApplyRealms) {
  32. //如果在ini文件中未显示指定realm(即:securityManager.realms=$myRealm1)
  33. //那么ini配置的所有realm会自动设置(即:realm1=test.Realm1)
  34. //getRealms方法只是把ini配置的所有实例对象中实现Realm、RealmFactory接口的实例添加到SecurityManager中
  35. Collection<Realm> realms = getRealms(objects);
  36. //set them on the SecurityManager
  37. if (!CollectionUtils.isEmpty(realms)) {
  38. applyRealmsToSecurityManager(realms, securityManager);
  39. }
  40. }
  41. return securityManager;
  42. }
  43. //创建默认的SecurityManager
  44. protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
  45. Map<String, Object> defaults = new LinkedHashMap<String, Object>();
  46. //这里还是先创建了DefaultSecurityManager
  47. SecurityManager securityManager = createDefaultInstance();
  48. defaults.put(SECURITY_MANAGER_NAME, securityManager);
  49. //判断ini是否配置了roles、users
  50. if (shouldImplicitlyCreateRealm(ini)) {
  51. //如果配置了roles或users,则创建IniRealm
  52. Realm realm = createRealm(ini);
  53. if (realm != null) {
  54. defaults.put(INI_REALM_NAME, realm);
  55. }
  56. }
  57. return defaults;
  58. }
  59. //对main部分配置的所有类型、属性进行实例化,并设置依赖,放到objects集合中
  60. private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) {
  61. this.builder = new ReflectionBuilder(defaults);
  62. return this.builder.buildObjects(section);
  63. }

再看看ReflectionBuilder如何对main进行实例化及依赖的设置: 
Java代码

  1. public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
  2. if (kvPairs != null && !kvPairs.isEmpty()) {
  3. //实例配置集合
  4. Map<String, String> instanceMap = new LinkedHashMap<String, String>();
  5. //属性依赖配置集合
  6. Map<String, String> propertyMap = new LinkedHashMap<String, String>();
  7. for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
  8. //如果ini配置的key包括.号,或者以.class后缀的名称,则认为类型配置
  9. if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) {
  10. instanceMap.put(entry.getKey(), entry.getValue());
  11. //其它情况,则认为是属性依赖配置
  12. } else {
  13. propertyMap.put(entry.getKey(), entry.getValue());
  14. }
  15. }
  16. // Create all instances
  17. for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
  18. //实例化每个类型
  19. createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue());
  20. }
  21. // Set all properties
  22. for (Map.Entry<String, String> entry : propertyMap.entrySet()) {
  23. //配置所有的依赖。必须有个先后顺序,先实例化再设置依赖
  24. applyProperty(entry.getKey(), entry.getValue(), objects);
  25. }
  26. }
  27. //SHIRO-413: init method must be called for constructed objects that are Initializable
  28. //如果配置的类型实现了Initializable接口,则调用其init方法
  29. LifecycleUtils.init(objects.values());
  30. return objects;
  31. }

SecurityManager的初始化部分已经完成。

Apache Shiro 全面源码解析汇总相关推荐

  1. 路由框架ARouter最全源码解析

    ARouter是2017年阿里巴巴开源的一款Android路由框架,官方定义: ARouter是Android平台中对页面,服务提供路由功能的中间件,提倡简单且够用 有下面几个优势: 1.直接解析UR ...

  2. ThinkPHP门面源码解析

    本文主要描述了门面的使用和实现过程以及源码的深度解析. 框架门面解析 前言 一.简单认识一下在框架中的门面的好处 二.学习框架中facade的使用 三.优化在框架中facade的使用 四.门面类源码解 ...

  3. channelinactive触发后不关闭channel_golang chan 最详细原理剖析,全面源码分析!看完不可能不懂的!...

    大纲 channel 是什么? channel 使用姿势 chan 创建 chan 入队 chan 出队 结合 select 语句 结合 for-range 语句 源码解析 `makechan` hc ...

  4. Shiro认证源码解析和工作原理

    先行回顾一下使用shiro的步骤: 1. 创建Subject实例对象currUser: 2. 判断当前currUser是否认证过: 3. 如果没有认证过,那么应当调用currUser的login(to ...

  5. 介绍一个很全面源码关于android 账户管理的源码

    基础开始--这些是主要部分: 认证令牌(auth- Token)-服务器提供的临时访问令牌(或安全令牌).用户需要识别出这样的标记并将其附加到他发送到服务器的每个请求上.在这篇文章中,我将使用OAut ...

  6. Spring AOP面向切面源码解析

    IoC 和 AOP 被称为 Spring 两大基础模块 AOP(Aspect-Oriented Programming) 在程序设计领域拥有其不可替代的适用场景和地位.Spring AOP 作为 AO ...

  7. Apache Shiro权限绕过漏洞 (CVE-2020-11989) 挖掘分析和复现

     聚焦源代码安全,网罗国内外最新资讯! 1.1 状态 完成漏洞挖掘条件分析.漏洞复现. 1.2 简介 Apache Shiro作为Java框架,可被用于身份认证.授权等任务. 整合Apache Shi ...

  8. loam源码解析5 : laserOdometry(三)

    transformMaintenance.cpp解析 八.位姿估计 1. 雅可比计算 2. 矩阵求解 3. 退化问题分析 4. 姿态更新 5. 坐标转换 loam源码地址: https://githu ...

  9. loam源码解析1 : scanRegistration(一)

    scanRegistration.cpp解析 一.概述 二.变量说明 三.主函数 四.IMU回调函数laserCloudHandler 1 接受IMU的角度和加速度信息 2 AccumulateIMU ...

最新文章

  1. 导出websphere内存镜像
  2. 实战SSM_O2O商铺_20【商铺编辑】View层开发
  3. 【直播】如何学习计算机视觉各大方向,言有三6大直播集中上线
  4. python3 匹配空格 正则_玩转正则表达式
  5. java calendar_Java Calendar getDisplayNames()方法与示例
  6. -组件基础-局部组件 // 局部组件的简写
  7. Intel 64/x86_64/IA-32/x86处理器 - SIMD指令集 - MMX技术(5) - 逻辑指令
  8. unable to bind to locking port 7054 within 45000 ms
  9. 免费素材下载:学校学院相关图标集
  10. opa847方波放大电路_我现在用lm358做了一个放大3倍的放大电路,输出大概3~4v,要带负载50Ω。带150的时候没事,但50就波形消失...
  11. 排水管网计算机模拟,基于SWMM的城市合流制排水管网计算机模拟方法.ppt
  12. Python语音基础操作--5.4小波分解
  13. MybatisPlus中@TableField注解的使用详解
  14. 2D Pixel Perfect:使用Unity创建任天堂红白机风格复古游戏
  15. CDS 获取系统日期时间
  16. WPF中的3D Wireframe
  17. 自然语言处理NLP星空智能对话机器人系列:Facebook StarSpace框架初体验
  18. 导入依赖butterknife 10.1.0 报错 Attribute application@appComponentFactory ......
  19. 金融科技:贷款平台搭建方案分享
  20. 赤色要塞java7723,本周礼包汇总 超值礼包助力悠闲周末

热门文章

  1. ue快捷键一览 UltraEdit (UE) 快捷键 ue 复制并粘贴当前行
  2. JAVA close关闭流最佳实践
  3. TCP缓冲区大小及限制
  4. python ljust 中文_python ljust 中文_Python为文档批量注音(生僻字歌词为例)
  5. abap语言去除重复项怎么写
  6. 《经典算法》数独问题
  7. 量化交易学习(10)均线交叉策略
  8. JAVA连接FTP实例
  9. 设计模式之装饰器模式(C++)
  10. xbox控制台小助手服务器连接已阻止,win10系统xbox控制台小帮手无法登录,提示目前您无法登录怎么办...