原创

Apache Shiro安全认证框架详解

        Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:认证-用户身份识别,常被称为用户“登录”;授权 - 访问控制;密码加密 - 保护或隐藏数据防止被偷窥;会话管理 - 每用户相关的时间敏感的状态。对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。

1、Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

2、realm:基于jdbc,ldap,text,activeDirectory,jndi等多种方式来获取用户基本信息,角色信息,权限信息等。只需要在securityManager中指定使用相应的realm实现即可,其在这各方面都提供了对应的缺省实现,比如我们常用的基于数据库表的形式来配置用户权限信息,就可以使用其缺省实现的jdbcRealm(org.apache.shiro.realm.jdbc.JdbcRealm)。
当然,如果认证信息来自于多方面,多个不同的来源(比如来自两个库中,或者一个数据库,一个是ldap,再配上一个缺省的基于文本的测试用等等),我们可以为securityManager指定realms参数,即把这一组安全配置都配置上。各个具体的realm实现提供了方法来获取用户基本信息、角色、权限等。 realm的授权信息可以存放在Cache中,Cache的名称可以通过设置其authorizationCacheName参数指定。

3、缓存
目前Shrio缺省提供了基于ehCache来缓存用户认证信息和授权信息的实现。只需要配置 org.apache.shiro.web.mgt.DefaultWebSecurityManager 这个 cacheManager并设置给SecurityManager即可。

如果项目中已经存在使用的ehCacheManager配置(org.springframework.cache.ehcache.EhCacheManagerFactoryBean),DefaultWebSecurityManager则可以指定使用现有的ehCacheManager,如果不指定,它将自行使用缺省配置创建一个。

同时,也可以设置cacheManagerConfigFile参数来指定ehCache的配置文件。 下例中的shiro.authorizationCache是用来存放授权信息的Cache,我们在配置realm(如myRealm或jdbcReaml)时,把authorizationCacheName属性设置shiro.authorizationCache来对应。

ehcache-shiro.xml配置文件如下:

<ehcache>
<diskStore path="java.io.tmpdir/tuan-oauth"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>

<cache name="shiro.authorizationCache"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="600"
overflowToDisk="false"/>
</ehcache>

当我们把securityManager的sessionMode参数设置为native时,那么shrio就将用户的基本认证信息保存到缺省名称为shiro-activeSessionCache 的Cache中

org.apache.shiro.web.mgt.DefaultWebSecurityManager 在sessionMode参数设置为native时,缺省使用的是DefaultWebSessionManager来管理Session,该管理类缺省使用的是使用MemorySessionDAO基于内存来保存和操作用户基本认证信息。

如果系统内的用户数特别多,我们需要使用CacheSessionDao来基于Cache进行操作,因此,这里需要显示配置一个sessionManager(org.apache.shiro.web.session.mgt.DefaultWebSessionManager),并配置该sessionManager的sessionDao为CacheSessionDao(org.apache.shiro.session.mgt.eis.CachingSessionDAO,需用其实现类org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO)。

配置CacheSessionDao时,我们可以指定属性activeSessionsCacheName的名称来替换掉缺省名 shiro-activeSessionCache。我们再把该sessionManager配置给DefaultWebSecurityManager就可以了。

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager" />
    <property name="sessionMode" value="native" />
    <!-- Single realm app. If you have multiple realms, use the 'realms' property 
        instead. -->
    <property name="realm" ref="myRealm" />
    <property name="sessionManager" ref="sessionManager" />
</bean>

<bean id="sessionManager"
    class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <property name="sessionDAO" ref="sessionDAO" />
</bean>

<bean id="sessionDAO"
    class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache" />
</bean>


从上述内容可以看出,指定sessionManager的sessionDao,在某些情况下,我们也可以通过实现自定义的sessionDao来把用户认证信息保存在memcache,mongodb,ldap,database中,达到和其他应用共享用户认证信息的目的,以此达到SSO的目的(当然,sessionId得一致,这个属于我们可以在应用商定怎么设定一致的sessionId的问题);cacheManager我们也可以自己实现一个,可以根据应用情况来考虑,比如存放在memcache中之类。

4、Web项目spring框架配置内容
Web项目中,普通的web项目可以采用ini文件来对shiro进行配置。基于spring的项目可以采用和Spring集成的方式配置。 基于Spring集成的Web项目的基本配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
   
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager" />
        <property name="sessionMode" value="native" />
        <property name="realm" ref="myRealm" />
    </bean>
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="ehCacheManager" />
    </bean>
    <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
        <property name="name" value="jdbcRealm" />
        <property name="dataSource" ref="dataSource" />
        <property name="credentialsMatcher">
            <!-- The 'bootstrapDataPopulator' Sha256 hashes the password (using the 
                username as the salt) then base64 encodes it: -->
            <bean class="org.apache.shiro.authc.credential.Sha256CredentialsMatcher">
                <!-- true means hex encoded, false means base64 encoded -->
                <property name="storedCredentialsHexEncoded" value="false" />
                <!-- We salt the password using the username, the most common practice: -->
                <property name="hashSalted" value="true" />
            </bean>
        </property>
        <property name="authorizationCacheName" value="shiro.authorizationCache" />
    </bean>

    <bean id="myRealm" class="org.apache.shiro.realm.text.IniRealm"
        init-method="init">
        <property name="name" value="myRealm" />
        <property name="authorizationCacheName" value="shiro.authorizationCache" />
        <property name="resourcePath" value="classpath:config/myRealm.ini" />

    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
        depends-on="lifecycleBeanPostProcessor" />
    <bean
        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    <bean id="secureRemoteInvocationExecutor"
        class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
        <property name="securityManager" ref="securityManager" />
    </bean>
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="successUrl" value="/index" />
        <property name="unauthorizedUrl" value="/unauthorized" />
        <property name="filterChainDefinitions">
            <value>
                /login = authc
                /account = user
                /manage = user,roles[admin]
        ...
            </value>
        </property>
    </bean>
</beans>


5、基于url资源的权限管理
我们可以简单配置在shiroFilter的filterChainDefinitions中,也可以考虑通过一个文本文件,我们读入内容后设置进去。或者通过Ini类来装入Ini文件内容,到时取出urls的部分来设置给shiroFilter的filterChainDefinitions。也可以把这部分数据存入数据库表中,到时读出一个Map来设置给shiroFilter的filterChainDefinitionsMap属性;authc是认证用户(rememberMe的用户也必须再次登录才能访问该url),配置成user才能让rememberMe用户也可以访问。

6、rememberMe Cookie的处理
Shiro有一套缺省机制,由CookieRememberMeManager实现。其有一个SimpleCookie类,保存对应的用户信息等。

    

每次保存时,系统把SimpleCookie的信息设置好之后,先用DefaultSerializer把其用jvm缺省序列化方式序列化成byte[],然后再用cipherService(缺省是aes加密算法)来加密该byte[],最后用Base64.encodeToString(serialized)压缩成一个字符串,再写入名称为rememberMe的Cookie中。

读取时,通过把该rememberMe Cookie的内容用byte[] decoded = Base64.decode(base64)解压出该byte[],再用cipherService解密,最后用DefaultSerializer反序列化出来该SimpleCookie类。


如果自定义rememberMe Cookie是需要处理的,特别是在和其他网站一起SSO时,通过访问主域的Cookie来获取记录的用户信息时,我们需要重新实现rememberMeManager(可以考虑继承AbstractRememberMeManager),和根据实际用的序列化方式Serializer来实现一个(比如考虑通用性,用json方式序列化)。

在Spring配置中,配置好RememberMeManager,装配上sericerlizer和cipherService(根据实际情况选用适当的加密算法),最后把rememberMeManager设置给DefaultWebSecurityManager即可。如果非常简单的cookie,可以直接实现RememberMeManager的几个接口方法也行。

关注下方微信公众号“Java精选”(w_z90110),回复关键字领取资料:如HadoopDubboCAS源码等等,免费领取资料视频和项目。 

涵盖:程序人生、搞笑视频、算法与数据结构、黑客技术与网络安全、前端开发、Java、Python、Redis缓存、Spring源码、各大主流框架、Web开发、大数据技术、Storm、Hadoop、MapReduce、Spark、elasticsearch、单点登录统一认证、分布式框架、集群、安卓开发、iOS开发、C/C++、.NET、Linux、Mysql、Oracle、NoSQL非关系型数据库、运维等。

评论

分享:

支付宝

微信