shiro框架

Shiro

Shiro简介

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

 

 

Authentication身份认证/登陆,验证用户是否是拥有相应的身份;java

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

Session Manager会话管理,即用户登陆后就是一次会话,在没有退出以前,它的全部信息都在会话中;会话能够是普通JavaSE环境的,也能够是如Web环境的;github

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

Web SupportWeb支持,能够很是容易的集成到Web环境;spring

Caching:缓存,好比用户登陆后,其用户信息、拥有的角色/权限没必要每次去查,这样能够提升效率;数据库

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

Testing提供测试支持;浏览器

Run As容许一个用户伪装为另外一个用户(若是他们容许)的身份进行访问;spring-mvc

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有两个做用:

一、若是Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMatcher进行凭据验证;(若是没有继承它须要在本身的Realm中本身实现验证);

二、提供给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实例)获得一个包装后的实例;它们都是经过:一、把当前线程的Subject绑定过去;二、在线程执行结束后自动释放。

 

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>   


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

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

三、安全管理器使用用于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>   

 

一、formAuthenticationFilter为基于Form表单的身份验证过滤器;此处能够再添加本身的Filter bean定义;

二、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);  

            }  

        }  

    }  

}

相关文章
相关标签/搜索