SpringMVC整合Shiro,Shiro是一个强大易用的Java安全框架,提供了认证、受权、加密和会话管理等功能。前端
Authentication:身份认证/登陆,验证用户是否是拥有相应的身份;java
Authorization:受权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能作事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具备某个权限;git
Session Manager:会话管理,即用户登陆后就是一次会话,在没有退出以前,它的全部信息都在会话中;会话能够是普通JavaSE环境的,也能够是如Web环境的;github
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;web
Web Support:Web支持,能够很是容易的集成到Web环境;spring
Caching:缓存,好比用户登陆后,其用户信息、拥有的角色/权限没必要每次去查,这样能够提升效率;数据库
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另外一个线程,能把权限自动传播过去;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服务器);
SessionDAO:DAO你们都用过,数据访问对象,用于会话的CRUD,好比咱们想把Session保存到数据库,那么能够实现本身的SessionDAO,经过如JDBC写到数据库;好比想把Session放到Memcached中,能够实现本身的Memcached SessionDAO;另外SessionDAO中可使用Cache进行缓存,以提升性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;由于这些数据基本上不多去改变,放到缓存中后能够提升访问的性能
Cryptography:密码模块,Shiro提升了一些常见的加密组件用于如密码加密/解密的。
public class ShiroRealm extends AuthorizingRealm{
}
1、ShiroRealm父类AuthorizingRealm将获取Subject相关信息分红两步:获取身份验证信息(doGetAuthenticationInfo)及受权信息(doGetAuthorizationInfo);
2、doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;而后若是user为空,那么抛出没找到账号异常UnknownAccountException;若是user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,若是不匹配将抛出密码错误异常IncorrectCredentialsException;另外若是密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;在组装SimpleAuthenticationInfo信息时,须要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
3、doGetAuthorizationInfo获取受权信息:PrincipalCollection是一个身份集合,由于咱们如今就一个Realm,因此直接调用getPrimaryPrincipal获得以前传入的用户名便可;而后根据用户名调用UserService接口获取角色及权限信息。
AuthenticationToken用于收集用户提交的身份(如用户名)及凭据(如密码):
扩展接口RememberMeAuthenticationToken:提供了“boolean isRememberMe()”现“记住我”的功能;
扩展接口是HostAuthenticationToken:提供了“String getHost()”方法用于获取用户“主机”的功能。
Shiro提供了一个直接拿来用的UsernamePasswordToken,用于实现用户名/密码Token组,另外其实现了RememberMeAuthenticationToken和HostAuthenticationToken,能够实现记住我及主机验证的支持。
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便可。
由于咱们能够在Shiro中同时配置多个Realm,因此呢身份信息可能就有多个;所以其提供了PrincipalCollection用于聚合这些身份信息:
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用于聚合受权信息的:
当咱们使用AuthorizingRealm时,若是身份验证成功,在进行受权时就经过doGetAuthorizationInfo方法获取角色/权限信息用于受权验证。
Shiro提供了一个实现SimpleAuthorizationInfo,大多数时候使用这个便可。
对于Account及SimpleAccount,以前的【6.3 AuthenticationInfo】已经介绍过了,用于SimpleAccountRealm子类,实现动态角色/权限维护的。
Subject是Shiro的核心对象,基本全部身份验证、受权都是经过Subject完成。
1、身份信息获取
Java代码
2、身份验证
Java代码
经过login登陆,若是登陆失败将抛出相应的AuthenticationException,若是登陆成功调用isAuthenticated就会返回true,即已经经过身份验证;若是isRemembered返回true,表示是经过记住我功能登陆的而不是调用login方法登陆的。isAuthenticated/isRemembered是互斥的,即若是其中一个返回true,另外一个返回false。
3、角色受权验证
Java代码
hasRole*进行角色验证,验证后返回true/false;而checkRole*验证失败时抛出AuthorizationException异常。
4、权限受权验证
Java代码
10. void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;
isPermitted*进行权限验证,验证后返回true/false;而checkPermission*验证失败时抛出AuthorizationException。
5、会话
Java代码
相似于Web中的会话。若是登陆成功就至关于创建了会话,接着可使用getSession获取;若是create=false若是没有会话将返回null,而create=true若是没有会话会强制建立一个。
6、退出
Java代码
7、RunAs
Java代码
RunAs即实现“容许A假设为B身份进行访问”;经过调用subject.runAs(b)进行访问;接着调用subject.getPrincipals将获取到B的身份;此时调用isRunAs将返回true;而a的身份须要经过subject. getPreviousPrincipals获取;若是不须要RunAs了调用subject. releaseRunAs便可。
8、多线程
Java代码
实现线程之间的Subject传播,由于Subject是线程绑定的;所以在多线程执行中须要传播到相应的线程才能获取到相应的Subject。最简单的办法就是经过execute(runnable/callable实例)直接调用;或者经过associateWith(runnable/callable实例)获得一个包装后的实例;它们都是经过:一、把当前线程的Subject绑定过去;二、在线程执行结束后自动释放。
Subject本身不会实现相应的身份验证/受权逻辑,而是经过DelegatingSubject委托给SecurityManager实现;及能够理解为Subject是一个面门。
对于Subject的构建通常不必咱们去建立;通常经过SecurityUtils.getSubject()获取:
Java代码
即首先查看当前线程是否绑定了Subject,若是没有经过Subject.Builder构建一个而后绑定到现场返回。
若是想自定义建立,能够经过:
Java代码
这种能够建立相应的Subject实例了,而后本身绑定到线程便可。在new Builder()时若是没有传入SecurityManager,自动调用SecurityUtils.getSecurityManager获取;也能够本身传入一个实例。
Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制,如根据登陆用户显示相应的页面按钮。
导入标签库
Java代码
标签库定义在shiro-web.jar包下的META-INF/shiro.tld中定义。
guest标签
Java代码
用户没有身份验证时显示相应信息,即游客访问信息。
user标签
Java代码
用户已经身份验证/记住我登陆后显示相应的信息。
authenticated标签
Java代码
用户已经身份验证经过,即Subject.login登陆成功,不是记住我登陆的。
notAuthenticated标签
<shiro:notAuthenticated>
未身份验证(包括记住我)
</shiro:notAuthenticated>
用户已经身份验证经过,即没有调用Subject.login进行登陆,包括记住我自动登陆的也属于未进行身份验证。
principal标签
<shiro: principal/>
显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal。
Java代码
至关于Subject.getPrincipals().oneByType(String.class)。
Java代码
至关于Subject.getPrincipals().oneByType(String.class)。
Java代码
至关于((User)Subject.getPrincipals()).getUsername()。
hasRole标签
Java代码
若是当前Subject有角色将显示body体内容。
hasAnyRoles标签
Java代码
若是当前Subject有任意一个角色(或的关系)将显示body体内容。
lacksRole标签
Java代码
若是当前Subject没有角色将显示body体内容。
hasPermission标签
Java代码
若是当前Subject有权限将显示body体内容。
lacksPermission标签
Java代码
若是当前Subject没有权限将显示body体内容。
另外又提供了几个权限控制相关的标签:
与spring集成:在Web.xml中
12. </filter-mapping>
DelegatingFilterProxy做用是自动到spring容器查找名字为shiroFilter(filter-name)的bean并把全部Filter的操做委托给它。而后将ShiroFilter配置到spring容器便可:
13. </bean>
15. <!-- Realm实现 -->
16. <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">
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">
33. </bean>
34. <!-- 会话验证调度器 -->
35. <bean id="sessionValidationScheduler"
36. class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
39. </bean>
40. <!-- 会话管理器 -->
41. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
47. </bean>
48. <!-- 安全管理器 -->
49. <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
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"/>
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应用和普通JavaSE应用的某些配置是相似的,此处只提供一些不同的配置,详细配置能够参考spring-shiro-web.xml。
17. </bean>
18. <!-- 安全管理器 -->
19. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
20. <property name="realm" ref="userRealm"/>
23. </bean>
一、sessionIdCookie是用于生产Session ID Cookie的模板;
二、会话管理器使用用于web环境的DefaultWebSessionManager;
三、安全管理器使用用于web环境的DefaultWebSecurityManager。
27. </bean>
一、formAuthenticationFilter为基于Form表单的身份验证过滤器;此处能够再添加本身的Filter bean定义;
二、shiroFilter:此处使用ShiroFilterFactoryBean来建立ShiroFilter过滤器;filters属性用于定义本身的过滤器,即ini配置中的[filters]部分;filterChainDefinitions用于声明url和filter的关系,即ini配置中的[urls]部分。
在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权限注解的支持:
如上配置用于开启Shiro Spring AOP权限注解的支持;<aop:config proxy-target-class="true">表示代理类。
接着就能够在相应的控制器(AnnotationController)中使用以下方式进行注解:
访问hello2方法的前提是当前用户有admin角色。
当验证失败,其会抛出UnauthorizedException异常,此时可使用Spring的ExceptionHandler(DefaultExceptionHandler)来进行拦截处理:
权限注解
Java代码
表示当前Subject已经经过login进行了身份验证;即Subject. isAuthenticated()返回true。
Java代码
表示当前Subject已经身份验证或者经过记住我登陆的。
Java代码
表示当前Subject没有身份验证或经过记住我登陆过,便是游客身份。
Java代码
表示当前Subject须要角色admin和user。
Java代码
表示当前Subject须要权限user:a或user:b。
第一步:配置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);
}
}
}
}