zoukankan      html  css  js  c++  java
  • shiro框架

    Shiro

    Shiro简介

    SpringMVC整合Shiro,Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。

     

    Authentication身份认证/登录,验证用户是不是拥有相应的身份;

    Authorization授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

    Session Manager会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

    Cryptography加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

    Web SupportWeb支持,可以非常容易的集成到Web环境;

    Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

    Concurrencyshiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

    Testing提供测试支持;

    Run As允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

    Remember Me记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

    记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

    首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:

    可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

    Subject主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

    SecurityManager安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

    Realm域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

     

           接下来我们来从Shiro内部来看下Shiro的架构,如下图所示:

     

    Subject主体,可以看到主体可以是任何可以与应用交互的“用户”;

    SecurityManager相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

    Authenticator认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

    Authrizer授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

    Realm可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

    SessionManager如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

    SessionDAODAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

    CacheManager缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

    Cryptography密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

    自定义Realm

    public class ShiroRealm extends AuthorizingRealm{

    }

    1ShiroRealm父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo);

    2doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,如果不匹配将抛出密码错误异常IncorrectCredentialsException;另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;在组装SimpleAuthenticationInfo信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。

    3doGetAuthorizationInfo获取授权信息:PrincipalCollection是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;然后根据用户名调用UserService接口获取角色及权限信息。

    AuthenticationToken

     

    AuthenticationToken用于收集用户提交的身份(如用户名)及凭据(如密码):

    1. public interface AuthenticationToken extends Serializable {  
    2.     Object getPrincipal(); //身份  
    3.     Object getCredentials(); //凭据  
    4. }  

    扩展接口RememberMeAuthenticationToken:提供了“boolean isRememberMe()”现“记住我”的功能;

    扩展接口是HostAuthenticationToken:提供了“String getHost()”方法用于获取用户“主机”的功能。

     

    Shiro提供了一个直接拿来用的UsernamePasswordToken,用于实现用户名/密码Token组,另外其实现了RememberMeAuthenticationToken和HostAuthenticationToken,可以实现记住我及主机验证的支持。

    AuthenticationInfo

     

    AuthenticationInfo有两个作用:

    1、如果Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMatcher进行凭据验证;(如果没有继承它需要在自己的Realm中自己实现验证);

    2、提供给SecurityManager来创建Subject(提供身份信息);

     

    MergableAuthenticationInfo用于提供在多Realm时合并AuthenticationInfo的功能,主要合并Principal、如果是其他的如credentialsSalt,会用后边的信息覆盖前边的。

     

    比如HashedCredentialsMatcher,在验证时会判断AuthenticationInfo是否是SaltedAuthenticationInfo子类,来获取盐信息。

     

    Account相当于我们之前的User,SimpleAccount是其一个实现;在IniRealm、PropertiesRealm这种静态创建帐号信息的场景中使用,这些Realm直接继承了SimpleAccountRealm,而SimpleAccountRealm提供了相关的API来动态维护SimpleAccount;即可以通过这些API来动态增删改查SimpleAccount;动态增删改查角色/权限信息。及如果您的帐号不是特别多,可以使用这种方式,具体请参考SimpleAccountRealm Javadoc。

     

    其他情况一般返回SimpleAuthenticationInfo即可。

    PrincipalCollection

     

    因为我们可以在Shiro中同时配置多个Realm,所以呢身份信息可能就有多个;因此其提供了PrincipalCollection用于聚合这些身份信息:

    1. public interface PrincipalCollection extends Iterable, Serializable {  
    2.     Object getPrimaryPrincipal(); //得到主要的身份  
    3.     <T> T oneByType(Class<T> type); //根据身份类型获取第一个  
    4.     <T> Collection<T> byType(Class<T> type); //根据身份类型获取一组  
    5.     List asList(); //转换为List  
    6.     Set asSet(); //转换为Set  
    7.     Collection fromRealm(String realmName); //根据Realm名字获取  
    8.     Set<String> getRealmNames(); //获取所有身份验证通过的Realm名字  
    9.     boolean isEmpty(); //判断是否为空  

    10. }  

    因为PrincipalCollection聚合了多个,此处最需要注意的是getPrimaryPrincipal,如果只有一个Principal那么直接返回即可,如果有多个Principal,则返回第一个(因为内部使用Map存储,所以可以认为是返回任意一个);oneByType / byType根据凭据的类型返回相应的Principal;fromRealm根据Realm名字(每个Principal都与一个Realm关联)获取相应的Principal。

    目前Shiro只提供了一个实现SimplePrincipalCollection,还记得之前的AuthenticationStrategy实现嘛,用于在多Realm时判断是否满足条件的,在大多数实现中(继承了AbstractAuthenticationStrategy)afterAttempt方法会进行AuthenticationInfo(实现了MergableAuthenticationInfo)的merge,比如SimpleAuthenticationInfo会合并多个Principal为一个PrincipalCollection。

    AuthorizationInfo

     

    AuthorizationInfo用于聚合授权信息的:

    1. public interface AuthorizationInfo extends Serializable {  
    2.     Collection<String> getRoles(); //获取角色字符串信息  
    3.     Collection<String> getStringPermissions(); //获取权限字符串信息  
    4.     Collection<Permission> getObjectPermissions(); //获取Permission对象信息  
    5. }   

    当我们使用AuthorizingRealm时,如果身份验证成功,在进行授权时就通过doGetAuthorizationInfo方法获取角色/权限信息用于授权验证。

     

    Shiro提供了一个实现SimpleAuthorizationInfo,大多数时候使用这个即可。

     

    对于Account及SimpleAccount,之前的【6.3 AuthenticationInfo】已经介绍过了,用于SimpleAccountRealm子类,实现动态角色/权限维护的。

    Subject

     

    Subject是Shiro的核心对象,基本所有身份验证、授权都是通过Subject完成。

    1、身份信息获取

    Java代码  

    1. Object getPrincipal(); //Primary Principal  
    2. PrincipalCollection getPrincipals(); // PrincipalCollection   

     

    2、身份验证

    Java代码  

    1. void login(AuthenticationToken token) throws AuthenticationException;  
    2. boolean isAuthenticated();  
    3. boolean isRemembered();  

    通过login登录,如果登录失败将抛出相应的AuthenticationException,如果登录成功调用isAuthenticated就会返回true,即已经通过身份验证;如果isRemembered返回true,表示是通过记住我功能登录的而不是调用login方法登录的。isAuthenticated/isRemembered是互斥的,即如果其中一个返回true,另一个返回false。

      

    3、角色授权验证 

    Java代码  

    1. boolean hasRole(String roleIdentifier);  
    2. boolean[] hasRoles(List<String> roleIdentifiers);  
    3. boolean hasAllRoles(Collection<String> roleIdentifiers);  
    4. void checkRole(String roleIdentifier) throws AuthorizationException;  
    5. void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;  
    6. void checkRoles(String... roleIdentifiers) throws AuthorizationException;   

    hasRole*进行角色验证,验证后返回true/false;而checkRole*验证失败时抛出AuthorizationException异常。 

     

    4、权限授权验证

    Java代码  

    1. boolean isPermitted(String permission);  
    2. boolean isPermitted(Permission permission);  
    3. boolean[] isPermitted(String... permissions);  
    4. boolean[] isPermitted(List<Permission> permissions);  
    5. boolean isPermittedAll(String... permissions);  
    6. boolean isPermittedAll(Collection<Permission> permissions);  
    7. void checkPermission(String permission) throws AuthorizationException;  
    8. void checkPermission(Permission permission) throws AuthorizationException;  
    9. void checkPermissions(String... permissions) throws AuthorizationException;  

    10. void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;  

    isPermitted*进行权限验证,验证后返回true/false;而checkPermission*验证失败时抛出AuthorizationException。

     

    5、会话

    Java代码  

    1. Session getSession(); //相当于getSession(true)  
    2. Session getSession(boolean create);    

    类似于Web中的会话。如果登录成功就相当于建立了会话,接着可以使用getSession获取;如果create=false如果没有会话将返回null,而create=true如果没有会话会强制创建一个。

     

    6、退出 

    Java代码  

    1. void logout();  

     

    7RunAs  

    Java代码  

    1. void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;  
    2. boolean isRunAs();  
    3. PrincipalCollection getPreviousPrincipals();  
    4. PrincipalCollection releaseRunAs();   

    RunAs即实现“允许A假设为B身份进行访问”;通过调用subject.runAs(b)进行访问;接着调用subject.getPrincipals将获取到B的身份;此时调用isRunAs将返回true;而a的身份需要通过subject. getPreviousPrincipals获取;如果不需要RunAs了调用subject. releaseRunAs即可。

     

    8、多线程

    Java代码  

    1. <V> V execute(Callable<V> callable) throws ExecutionException;  
    2. void execute(Runnable runnable);  
    3. <V> Callable<V> associateWith(Callable<V> callable);  
    4. Runnable associateWith(Runnable runnable);   

    实现线程之间的Subject传播,因为Subject是线程绑定的;因此在多线程执行中需要传播到相应的线程才能获取到相应的Subject。最简单的办法就是通过execute(runnable/callable实例)直接调用;或者通过associateWith(runnable/callable实例)得到一个包装后的实例;它们都是通过:1、把当前线程的Subject绑定过去;2、在线程执行结束后自动释放。

     

    Subject自己不会实现相应的身份验证/授权逻辑,而是通过DelegatingSubject委托给SecurityManager实现;及可以理解为Subject是一个面门。

     

    对于Subject的构建一般没必要我们去创建;一般通过SecurityUtils.getSubject()获取:

    Java代码  

    1. public static Subject getSubject() {  
    2.     Subject subject = ThreadContext.getSubject();  
    3.     if (subject == null) {  
    4.         subject = (new Subject.Builder()).buildSubject();  
    5.         ThreadContext.bind(subject);  
    6.     }  
    7.     return subject;  
    8. }   

    即首先查看当前线程是否绑定了Subject,如果没有通过Subject.Builder构建一个然后绑定到现场返回。

     

    如果想自定义创建,可以通过:

    Java代码  

    1. new Subject.Builder().principals(身份).authenticated(true/false).buildSubject()  

    这种可以创建相应的Subject实例了,然后自己绑定到线程即可。在new Builder()时如果没有传入SecurityManager,自动调用SecurityUtils.getSecurityManager获取;也可以自己传入一个实例。

    Shiro的jstl标签

    Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制,如根据登录用户显示相应的页面按钮。

     

    导入标签库

    Java代码  

    1. <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  

    标签库定义在shiro-web.jar包下的META-INF/shiro.tld中定义。

     

    guest标签 

    Java代码  

    1. <shiro:guest>  
    2. 欢迎游客访问,<a href="${pageContext.request.contextPath}/login.jsp">登录</a>  
    3. </shiro:guest>   

    用户没有身份验证时显示相应信息,即游客访问信息。

     

    user标签 

    Java代码  

    1. <shiro:user>  
    2. 欢迎[<shiro:principal/>]登录,<a href="${pageContext.request.contextPath}/logout">退出</a>  
    3. </shiro:user>   

    用户已经身份验证/记住我登录后显示相应的信息。

      

    authenticated标签 

    Java代码  

    1. <shiro:authenticated>  
    2.     用户[<shiro:principal/>]已身份验证通过  
    3. </shiro:authenticated>   

    用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。    

     

    notAuthenticated标签

    <shiro:notAuthenticated>

        未身份验证(包括记住我)

    </shiro:notAuthenticated> 

    用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。 

     

    principal标签 

    <shiro: principal/>

    显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal。

     

    Java代码 

    1. <shiro:principal type="java.lang.String"/>  

    相当于Subject.getPrincipals().oneByType(String.class)。 

     

    Java代码 

    1. <shiro:principal type="java.lang.String"/>  

    相当于Subject.getPrincipals().oneByType(String.class)。

     

    Java代码 

    1. <shiro:principal property="username"/>  

    相当于((User)Subject.getPrincipals()).getUsername()。   

     

    hasRole标签 

    Java代码 

    1. <shiro:hasRole name="admin">  
    2.     用户[<shiro:principal/>]拥有角色admin<br/>  
    3. </shiro:hasRole>   

    如果当前Subject有角色将显示body体内容。

     

    hasAnyRoles标签 

    Java代码 

    1. <shiro:hasAnyRoles name="admin,user">  
    2.     用户[<shiro:principal/>]拥有角色admin或user<br/>  
    3. </shiro:hasAnyRoles>   

    如果当前Subject有任意一个角色(或的关系)将显示body体内容。 

     

    lacksRole标签 

    Java代码 

    1. <shiro:lacksRole name="abc">  
    2.     用户[<shiro:principal/>]没有角色abc<br/>  
    3. </shiro:lacksRole>   

    如果当前Subject没有角色将显示body体内容。 

      

    hasPermission标签

    Java代码 

    1. <shiro:hasPermission name="user:create">  
    2.     用户[<shiro:principal/>]拥有权限user:create<br/>  
    3. </shiro:hasPermission>   

    如果当前Subject有权限将显示body体内容。 

      

    lacksPermission标签

    Java代码 

    1. <shiro:lacksPermission name="org:create">  
    2.     用户[<shiro:principal/>]没有权限org:create<br/>  
    3. </shiro:lacksPermission>   

    如果当前Subject没有权限将显示body体内容。

     

    另外又提供了几个权限控制相关的标签:

    Shiro与web

    与spring集成:在Web.xml中

    1. <filter>  
    2.     <filter-name>shiroFilter</filter-name>  
    3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
    4.     <init-param>  
    5.         <param-name>targetFilterLifecycle</param-name>  
    6.         <param-value>true</param-value>  
    7.     </init-param>  
    8. </filter>  
    9. <filter-mapping>  
    10. 10.     <filter-name>shiroFilter</filter-name>  
    11. 11.     <url-pattern>/*</url-pattern>  

    12. </filter-mapping>   

    DelegatingFilterProxy作用是自动到spring容器查找名字为shiroFilter(filter-name)的bean并把所有Filter的操作委托给它。然后将ShiroFilter配置到spring容器即可:

    Shiro集成spring

    1. <!-- 缓存管理器 使用Ehcache实现 -->  
    2. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
    3.     <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>  
    4. </bean>  
    5.   
    6. <!-- 凭证匹配器 -->  
    7. <bean id="credentialsMatcher" class="  
    8. com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">  
    9.     <constructor-arg ref="cacheManager"/>  
    10. 10.     <property name="hashAlgorithmName" value="md5"/>  
    11. 11.     <property name="hashIterations" value="2"/>  
    12. 12.     <property name="storedCredentialsHexEncoded" value="true"/>  

    13. </bean>  

    1. 14.   

    15. <!-- Realm实现 -->  

    16. <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">  

    1. 17.     <property name="userService" ref="userService"/>  
    2. 18.     <property name="credentialsMatcher" ref="credentialsMatcher"/>  
    3. 19.     <property name="cachingEnabled" value="true"/>  
    4. 20.     <property name="authenticationCachingEnabled" value="true"/>  
    5. 21.     <property name="authenticationCacheName" value="authenticationCache"/>  
    6. 22.     <property name="authorizationCachingEnabled" value="true"/>  
    7. 23.     <property name="authorizationCacheName" value="authorizationCache"/>  

    24. </bean>  

    25. <!-- 会话ID生成器 -->  

    26. <bean id="sessionIdGenerator"   

    27. class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>  

    28. <!-- 会话DAO -->  

    29. <bean id="sessionDAO"   

    30. class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">  

    1. 31.     <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>  
    2. 32.     <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  

    33. </bean>  

    34. <!-- 会话验证调度器 -->  

    35. <bean id="sessionValidationScheduler"   

    36. class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">  

    1. 37.     <property name="sessionValidationInterval" value="1800000"/>  
    2. 38.     <property name="sessionManager" ref="sessionManager"/>  

    39. </bean>  

    40. <!-- 会话管理器 -->  

    41. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">  

    1. 42.     <property name="globalSessionTimeout" value="1800000"/>  
    2. 43.     <property name="deleteInvalidSessions" value="true"/>  
    3. 44.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
    4. 45.    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
    5. 46.     <property name="sessionDAO" ref="sessionDAO"/>  

    47. </bean>  

    48. <!-- 安全管理器 -->  

    49. <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">  

    1. 50.     <property name="realms">  
    2. 51.         <list><ref bean="userRealm"/></list>  
    3. 52.     </property>  
    4. 53.     <property name="sessionManager" ref="sessionManager"/>  
    5. 54.     <property name="cacheManager" ref="cacheManager"/>  

    55. </bean>  

    56. <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->  

    57. <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  

    58. <property name="staticMethod"   

    59. value="org.apache.shiro.SecurityUtils.setSecurityManager"/>  

    1. 60.     <property name="arguments" ref="securityManager"/>  

    61. </bean>  

    62. <!-- Shiro生命周期处理器-->  

    63. <bean id="lifecycleBeanPostProcessor"   

    64. class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

    可以看出,只要把之前的ini配置翻译为此处的spring xml配置方式即可,无须多解释。LifecycleBeanPostProcessor用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调,在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调。如UserRealm就实现了Initializable,而DefaultSecurityManager实现了Destroyable。具体可以查看它们的继承关系。

    Web应用:

    Web应用和普通JavaSE应用的某些配置是类似的,此处只提供一些不一样的配置,详细配置可以参考spring-shiro-web.xml。 

    1. <!-- 会话Cookie模板 -->  
    2. <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
    3.     <constructor-arg value="sid"/>  
    4.     <property name="httpOnly" value="true"/>  
    5.     <property name="maxAge" value="180000"/>  
    6. </bean>  
    7. <!-- 会话管理器 -->  
    8. <bean id="sessionManager"   
    9. class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
    10. 10.     <property name="globalSessionTimeout" value="1800000"/>  
    11. 11.     <property name="deleteInvalidSessions" value="true"/>  
    12. 12.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
    13. 13.     <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
    14. 14.     <property name="sessionDAO" ref="sessionDAO"/>  
    15. 15.     <property name="sessionIdCookieEnabled" value="true"/>  
    16. 16.     <property name="sessionIdCookie" ref="sessionIdCookie"/>  

    17. </bean>  

    18. <!-- 安全管理器 -->  

    19. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

    20. <property name="realm" ref="userRealm"/>  

    1. 21.     <property name="sessionManager" ref="sessionManager"/>  
    2. 22.     <property name="cacheManager" ref="cacheManager"/>  

    23. </bean>   


    1、sessionIdCookie是用于生产Session ID Cookie的模板;

    2、会话管理器使用用于web环境的DefaultWebSessionManager;

    3、安全管理器使用用于web环境的DefaultWebSecurityManager。

    1. <!-- 基于Form表单的身份验证过滤器 -->  
    2. <bean id="formAuthenticationFilter"   
    3. class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
    4.     <property name="usernameParam" value="username"/>  
    5.     <property name="passwordParam" value="password"/>  
    6.     <property name="loginUrl" value="/login.jsp"/>  
    7. </bean>  
    8. <!-- Shiro的Web过滤器 -->  
    9. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
    10. 10.     <property name="securityManager" ref="securityManager"/>  
    11. 11.     <property name="loginUrl" value="/login.jsp"/>  
    12. 12.     <property name="unauthorizedUrl" value="/unauthorized.jsp"/>  
    13. 13.     <property name="filters">  
    14. 14.         <util:map>  
    15. 15.             <entry key="authc" value-ref="formAuthenticationFilter"/>  
    16. 16.         </util:map>  
    17. 17.     </property>  
    18. 18.     <property name="filterChainDefinitions">  
    19. 19.         <value>  
    20. 20.             /index.jsp = anon  
    21. 21.             /unauthorized.jsp = anon  
    22. 22.             /login.jsp = authc  
    23. 23.             /logout = logout  
    24. 24.             /** = user  
    25. 25.         </value>  
    26. 26.     </property>  

    27. </bean>   

    1、formAuthenticationFilter为基于Form表单的身份验证过滤器;此处可以再添加自己的Filter bean定义;

    2、shiroFilter:此处使用ShiroFilterFactoryBean来创建ShiroFilter过滤器;filters属性用于定义自己的过滤器,即ini配置中的[filters]部分;filterChainDefinitions用于声明url和filter的关系,即ini配置中的[urls]部分。

    Shiro权限注解

    注意:

    在spring中需要开启权限注解与aop:

    <!-- AOP式方法级权限检查  -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>

    <!-- 启用shrio授权注解拦截方式 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    Shiro提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP的功能来进行判断,如Spring AOP;Shiro提供了Spring AOP集成用于权限注解的解析和验证。

    为了测试,此处使用了Spring MVC来测试Shiro注解,当然Shiro注解不仅仅可以在web环境使用,在独立的JavaSE中也是可以用的,此处只是以web为例了。

     

    在spring-mvc.xml配置文件添加Shiro Spring AOP权限注解的支持:

    1. <aop:config proxy-target-class="true"></aop:config>  
    2. <bean class="  
    3. org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
    4.     <property name="securityManager" ref="securityManager"/>  
    5. </bean>   

    如上配置用于开启Shiro Spring AOP权限注解的支持;<aop:config proxy-target-class="true">表示代理类。

     

    接着就可以在相应的控制器(AnnotationController)中使用如下方式进行注解: 

    1. @RequiresRoles("admin")  
    2. @RequestMapping("/hello2")  
    3. public String hello2() {  
    4.     return "success";  
    5. }  

    访问hello2方法的前提是当前用户有admin角色。

    当验证失败,其会抛出UnauthorizedException异常,此时可以使用Spring的ExceptionHandler(DefaultExceptionHandler)来进行拦截处理:

    1. @ExceptionHandler({UnauthorizedException.class})  
    2. @ResponseStatus(HttpStatus.UNAUTHORIZED)  
    3. public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {  
    4.     ModelAndView mv = new ModelAndView();  
    5.     mv.addObject("exception", e);  
    6.     mv.setViewName("unauthorized");  
    7.     return mv;  
    8. }   

    权限注解      

    Java代码  

    1. @RequiresAuthentication  

    表示当前Subject已经通过login进行了身份验证;即Subject. isAuthenticated()返回true。 

     

    Java代码  

    1. @RequiresUser  

    表示当前Subject已经身份验证或者通过记住我登录的。

     

    Java代码  

    1. @RequiresGuest  

    表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。  

     

    Java代码  

    1. @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)  

    表示当前Subject需要角色admin和user。

     

    Java代码  

    1. @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)  

    表示当前Subject需要权限user:a或user:b。  

    Shiro 完整项目配置

    第一步:配置web.xml

    <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->  

    <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> -->  

    <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->  

    <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->  

    <filter>  

        <filter-name>shiroFilter</filter-name>  

        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  

        <init-param>  

        <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->  

        <param-name>targetFilterLifecycle</param-name>  

        <param-value>true</param-value>  

        </init-param>  

    </filter>  

    <filter-mapping>  

            <filter-name>shiroFilter</filter-name>  

            <url-pattern>/*</url-pattern>  

    </filter-mapping>

    第二步:配置applicationContext.xml

    <!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->  

    <bean id="myRealm" class="com.jadyer.realm.MyRealm"/>  

      

    <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->  

    <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->  

    <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->  

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  

        <property name="realm" ref="myRealm"/>  

    </bean>  

      

    <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->  

    <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->  

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  

        <!-- Shiro的核心安全接口,这个属性是必须的 -->  

        <property name="securityManager" ref="securityManager"/>  

        <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->  

        <property name="loginUrl" value="/"/>  

        <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) -->  

        <!-- <property name="successUrl" value="/system/main"/> -->  

        <!-- 用户访问未对其授权的资源时,所显示的连接 -->  

        <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp -->  

        <property name="unauthorizedUrl" value="/"/>  

        <!-- Shiro连接约束配置,即过滤链的定义 -->  

        <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 -->  

        <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->  

        <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 -->  

        <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->  

        <property name="filterChainDefinitions">  

            <value>  

                 /mydemo/login=anon  

                 /mydemo/getVerifyCodeImage=anon  

                 /main**=authc  

                 /user/info**=authc  

                 /admin/listUser**=authc,perms[admin:manage]  

            </value>  

        </property>

    </bean>  

      

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

      

    <!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->  

    <!-- 配置以下两个bean即可实现此功能 -->  

    <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run -->  

    <!-- 由于本例中并未使用Shiro注解,故注释掉这两个bean(个人觉得将权限通过注解的方式硬编码在程序中,查看起来不是很方便,没必要使用) -->  

    <!--   

    <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>  

    -->

    第三步:自定义的Realm

    public class MyRealm extends AuthorizingRealm {  

        /** 

         * 为当前登录的Subject授予角色和权限 

         * @see  经测试:本例中该方法的调用时机为需授权资源被访问时 

         * @see  经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache 

         * @see  个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache 

         * @see  比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache 

         */  

        @Override  

        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){  

            //获取当前登录的用户名,等价于(String)principals.fromRealm(this.getName()).iterator().next()  

            String currentUsername = (String)super.getAvailablePrincipal(principals);  

    //      List<String> roleList = new ArrayList<String>();  

    //      List<String> permissionList = new ArrayList<String>();  

    //      //从数据库中获取当前登录用户的详细信息  

    //      User user = userService.getByUsername(currentUsername);  

    //      if(null != user){  

    //          //实体类User中包含有用户角色的实体类信息  

    //          if(null!=user.getRoles() && user.getRoles().size()>0){  

    //              //获取当前登录用户的角色  

    //              for(Role role : user.getRoles()){  

    //                  roleList.add(role.getName());  

    //                  //实体类Role中包含有角色权限的实体类信息  

    //                  if(null!=role.getPermissions() && role.getPermissions().size()>0){  

    //                      //获取权限  

    //                      for(Permission pmss : role.getPermissions()){  

    //                          if(!StringUtils.isEmpty(pmss.getPermission())){  

    //                              permissionList.add(pmss.getPermission());  

    //                          }  

    //                      }  

    //                  }  

    //              }  

    //          }  

    //      }else{  

    //          throw new AuthorizationException();  

    //      }  

    //      //为当前用户设置角色和权限  

    //      SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();  

    //      simpleAuthorInfo.addRoles(roleList);  

    //      simpleAuthorInfo.addStringPermissions(permissionList);  

            SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();  

            //实际中可能会像上面注释的那样从数据库取得  

            if(null!=currentUsername && "mike".equals(currentUsername)){  

                //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色    

                simpleAuthorInfo.addRole("admin");  

                //添加权限  

                simpleAuthorInfo.addStringPermission("admin:manage");  

                System.out.println("已为用户[mike]赋予了[admin]角色和[admin:manage]权限");  

                return simpleAuthorInfo;  

            }

            //若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址  

            //详见applicationContext.xml中的<bean id="shiroFilter">的配置  

            return null;  

        }  

      

          

        /** 

         * 验证当前登录的Subject 

         * @see  经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时 

         */  

        @Override  

        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {  

            //获取基于用户名和密码的令牌  

            //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的  

            //两个token的引用都是一样的

            UsernamePasswordToken token = (UsernamePasswordToken)authcToken;  

            System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));  

    //      User user = userService.getByUsername(token.getUsername());  

    //      if(null != user){  

    //          AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), user.getNickname());  

    //          this.setSession("currentUser", user);  

    //          return authcInfo;  

    //      }else{  

    //          return null;  

    //      }  

            //此处无需比对,比对的逻辑Shiro会做,我们只需返回一个和令牌相关的正确的验证信息  

            //说白了就是第一个参数填登录用户名,第二个参数填合法的登录密码(可以是从数据库中取到的,本例中为了演示就硬编码了)  

            //这样一来,在随后的登录页面上就只有这里指定的用户和密码才能通过验证  

            if("mike".equals(token.getUsername())){  

                AuthenticationInfo authcInfo = new SimpleAuthenticationInfo("mike", "mike", this.getName());  

                this.setSession("currentUser", "mike");  

                return authcInfo;  

            }

            //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常  

            return null;  

        }  

          

          

        /** 

         * 将一些数据放到ShiroSession中,以便于其它地方使用 

         * @see  比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到 

         */  

        private void setSession(Object key, Object value){  

            Subject currentUser = SecurityUtils.getSubject();  

            if(null != currentUser){  

                Session session = currentUser.getSession();  

                System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒");  

                if(null != session){  

                    session.setAttribute(key, value);  

                }  

            }  

        }  

    }

  • 相关阅读:
    LeetCode1049. 最后一块石头的重量 II
    LeetCode416. 分割等和子集
    LeetCode96. 不同的二叉搜索树
    LeetCode343. 整数拆分
    python笔记---内置容器
    Numpy学习笔记(一)
    tensorflow入门代码分析
    神经网络
    回归算法
    机器学习入门笔记
  • 原文地址:https://www.cnblogs.com/maofa/p/6407102.html
Copyright © 2011-2022 走看看