shiro 如何判断登录过_Shiro详解

【63】

Shiro

Shiro简介

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

v2-ff51485380848a1d6dd776ed39c6b2cb_b.jpg

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

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

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

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

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

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

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

Testing:提供测试支持;

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

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

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

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

v2-8526e78cc56a9505dcca6d0f3fcac3dd_b.jpg

可以看到:应用代码直接交互的对象是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的架构,如下图所示:

v2-d8a2600974530043f47b33bfe1189f92_b.jpg

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提高了一些常见的加密组件用于如密码加密/解密的。

自定义Realm

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

v2-232ba860488b82173c5ee448d355e61c_b.jpg

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

v2-7696097aa002d31eb92adfadfbc1ac9c_b.jpg

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

v2-75b0dd4b87e650680b9623b381624ec7_b.jpg

因为我们可以在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

v2-7a204fc0d5fb8df5eac93168a2236a03_b.jpg

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

v2-865b7c0b0d8bf6a16e7593014276408f_b.png

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如果没有会话会强制创建一个。

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

<!-- 缓存管理器 使用Ehcache实现 -->

  1. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
  2. <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
  3. </bean>
  4. <!-- 凭证匹配器 -->
  5. <bean id="credentialsMatcher" class="
  6. com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">
  7. <constructor-arg ref="cacheManager"/>
  8. 10. <property name="hashAlgorithmName" value="md5"/>
  9. 11. <property name="hashIterations" value="2"/>
  10. 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]部分。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值