Shiro<br />1、Shiro简介<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013213224-11320b21-c28b-43c7-9e95-c1f74787354d.png#height=199&width=374)<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013213681-df5bc29c-cd0e-4b9d-b45f-e1ae42b8afbc.png#height=209&width=372)<br /> <br />2、Shiro架构(Shiro外部来看)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013214166-ca9669ad-c402-40b6-a085-bf6c8a0299df.png#height=165&width=397)<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013214685-9c4945bf-679c-4624-bb5e-6da921343222.png#height=214&width=415)<br /> <br />3、Shiro架构(Shiro内部来看)<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013215268-f7f39f90-f912-4386-ab7c-269848265b24.png#height=282&width=389)<br /> <br />4、集成Spring<br /> 第一步:导入jar包<br /> 第二步:配置Shiro(参照shiro文件的spring中的webapp)<br /> (1)在web.xml中配置Shiro的Filter<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013215701-c7eb434f-1c56-43d5-b622-df95ec8d9574.png#height=163&width=414)<br /> (2)配置applicationContext.xml(复制即可)<br /> 1、配置SecurityManager,内含cacheManager和realm<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013216164-b54b7123-d825-4b04-92e3-c467b7c208ed.png#height=69&width=338)<br /> 2、配置CacheManager,内含cacheManagerConfigFile<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013216638-f36d7915-9bfd-45dc-9173-50a0e0adc9fb.png#height=60&width=336)<br /> 3、配置Realm,自定义Realm实现Realm接口<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013216816-e8718193-0872-4983-8d65-6e2c3bba64fe.png#height=53&width=289)<br /> 4、配置LifecycleBeanPostProcessor<br /> 作用:可以自动的调用Spring容器中shiro的生命周期方法<br /> <bean id="lifecycleBeanPostProcessor"<br />class="org.apache.shiro.spring.LifecycleBeanPostProcessor/> 5、配置DefaultAdvisorAutoProxyCreator<br /> 作用:启用IOC容器中使用shiro的注解,但必须配置Lifecycle<br />BeanPostProcessor之后才可以使用<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013217180-043dc614-32b5-4452-ad31-8bbd8e4d8e2d.png#height=41&width=313)<br /> 6、配置shiroFilter。<br />id必须和web.xml中配置的filter-name一致,否则会抛出<br />NoSuchBeanDefinitionException异常。<br />也可以通过targetBeanName初始化参数来定义filter-name,此<br />时id和targetBeanName的值一致也可以。<br /> 属性:securityManager<br /> loginUrl<br /> successUrl<br /> unauthorizedUrl<br /> filterChainDefinitions<br /> 作用:配置哪些页面需要保护以及访问这些页面需要的权限<br /> (1)anno:可以被匿名访问<br /> (2)authc:必须认证(登录)后才可以访问的页面<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013217386-0bead714-0066-4b23-a3c4-18af172f6685.png#height=196&width=372)<br /> <br />5、URL配置细节(即Url链)<br /> 格式:url=拦截器[参数],拦截器[参数]<br /> 拦截器:<br /> anno:匿名访问,不需要登录即可访问<br />authc:需要身份认证通过后才可能访问<br />logout:登出,重定向的路径为”/”<br />roles[角色名]:角色过滤器。拥有相应的角色才能访问相应的页面<br /> 配置模式:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013217748-23f2478e-4659-4356-bf36-d34f05d07dc1.png#height=159&width=370)<br /> 匹配顺序:第一次优先匹配的方式,优先级逐渐降低<br /> <br />6、认证思路分析<br /> 第一步:调用SecurityUtils.getSubject()获取当前的Subject<br /> 第二步:调用Subject的isAuthenticated()测试当前的用户是否已经被<br />认证,即是否登录<br /> 第三步:若没有被认证,则把用户名和密码封装为UsernamePasswordToken<br />对象<br /> (1)创建一个表单页面<br /> (2)把请求提交到SpringMVC的Handler<br /> (3)获取用户名和密码<br /> 第四步:调用Subject的login(AuthenticationToken)方法,执行登录<br /> 第五步:自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro (1)实际上需要继承AuthenticatingRealm类,实现<br />doGetAuthenticationInfo(AuthenticationToken)方法<br />第六步:由shiro完成密码的比对<br /> <br />7、实现认证流程<br /> 第一步:编写登陆的表单页面<br /> 第二步:编写Handler,处理请求<br /> 第三步:自定义Realm(没实现)<br /> 实际上:login方法将参数Token对象传给了Realm类中的<br /> doGetAuthenticationInfo方法的参数<br /> <br />8、实现认证Realm<br /> 第一步:自定义Realm,继承AuthenticatingRealm类<br /> 第二步:实现doGetAuthenticationInfo方法<br /> 1、把AuthenticationToken转换为UsernamePasswordToken<br /> 2、从UsernamePasswordToken中获取Username<br /> 3、调用数据库中的方法,从数据库中查询username对应的用户记录<br /> 4、若用户不存在,则可以抛出UnknownAccountException异常<br /> 5、根据用户的信息,决定是否需要抛出其它的<br />AuthenticationException异常<br /> 6、 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使<br />用的实现类为:SimpleAuthenticationInfo<br />SimpleAuthenticationInfo(principal,credentials,realmName)<br />参数1:认证消息。可以是username,也可以是数据表对应的<br />用户的实体类对象<br />参数2:从数据表中获取的密码<br />参数3:当前Realm对象的name,调用父类的getName()方法即可<br /> <br />9、密码的比对<br /> 通过AuthenticatingRealm的credentialsMatcher属性来进行密码的比对<br />10、密码的MD5加密<br />替换当前的credentialsMatcher属性,直接使用HashedCredentialsMatcher<br />对象,并设置加密算法,shiro就能将前台传来的密码进行自动加密。<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013218253-af7b7079-d10f-4980-a7f8-924648f1aeb1.png#height=114&width=415)<br />手动加密:<br /> Object result=new SimpleHash(“MD5”,密码,盐值,加密次数);<br />11、密码的MD5盐值加密<br /> 盐值:ByteSource salt=ByteSource.Util.bytes(“唯一的字符串”);<br /> SimpleAuthenticationInfo(principal,credentials,salt,realmName)<br />参数1:认证消息。可以是username,也可以是数据表对应的<br />用户的实体类对象<br />参数2:从数据表中获取的密码<br />参数3:盐值<br />参数4:当前Realm对象的name,调用父类的getName()方法即可<br /> <br />12、多Realm验证<br /> 第一个Realm:使用MD5盐值加密<br /> 第二个Realm:使用SHA1盐值加密<br /> 将Realm注册IOC容器中:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013218550-6e810b27-7158-4b93-b7ce-0bf54d231066.png#height=152&width=344)<br /> 原理:Shiro会进入ModularRealmAuthenticator类,进行判断是否存<br />在多个Realm。<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013218798-f6fed6d1-684e-4844-972d-dfbfced15325.png#height=91&width=416)<br /> 配置ModularRealmAuthenticator:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013219272-1bc42114-af18-4616-83d6-b36759d5f38a.png#height=104&width=335)<br /> 将ModularRealmAuthenticator注入到SecurityManager:<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013219501-70a77c92-2dd0-4c7e-97b4-be3f11487061.png#height=105&width=414)<br /> <br />13、Shiro的认证策略<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013220179-4179d827-a98d-4a77-967f-c3e10c3c7c0c.png#height=127&width=371)<br /> 认证策略类:AuthenticationStrategy<br /> ModularRealmAuthenticator默认的认证策略是:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013220429-d79ce3aa-2ef4-4b97-8c2f-4151ec6a38f0.png#height=51&width=315)<br /> 修改认证策略,在ModularRealmAuthenticator的配置中修改:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013220852-3a2db5c0-3da5-453e-abe1-d365ce5394d1.png#height=61&width=325)<br /> <br />14、把realms配置给SecurityManager<br /> 原理:实际上将配置给SecurityManager的Realms,又设置给了<br />Authenticator的setRealms。<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013221131-8f28ad53-187d-439c-84ba-0340e455aff9.png#height=138&width=409)<br /> <br /> <br />15、权限配置<br /> 授权方式(3种):<br /> 编程式:通过写if/else授权代码块来完成<br /> 注解式:通过在执行的java方法上放置相应的注解完成,没有权限<br />将抛出相应的异常<br /> JSP/GSP标签:在JSP/GSP页面通过相应的标签完成<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013221545-e74f41e7-7e02-45af-9ee9-a2b4c77354ca.png#height=220&width=416)<br />拦截器的所有种类:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013221866-e7a60ce5-80b6-4081-858c-4ff7e78198f5.png#height=138&width=390)<br />(1)身份验证相关<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013222179-c8878eef-9535-4692-95fe-412714eea206.png#height=160&width=415)<br />(2)授权相关 ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013222500-a32b9c55-8718-4add-85ad-7e16cbcdeeec.png#height=183&width=450)<br /> <br />16、授权流程分析<br />(1)授权需要继承AuthorizingRealm类,并实现其<br />doGetAuthorizationInfo方法<br />(2)AuthorizingRealm类继承AuthenticatingRealm,但没有实现<br />AuthenticatingRealm中的doGetAuthenticationInfo方法,所以认证<br />和授权只需要继承AuthorizingRealm类,并实现它的两个抽象方法<br /> <br />17、实现授权Realm<br />(1)继承AuthorizingRealm类,并实现其两个方法<br /> 1、doGetAuthenticationInfo :用于认证<br /> 2、doGetAuthorizationInfo(PrincipalCollection) :用于授权<br />(2)从PrincipalCollection中获取登录用户的信息<br /> Realm配置顺序的不同,获取到的用户信息也不同。优先配置的<br />Realm,就优先获取到该Realm设置的用户信息。<br />(3)利用登录的用户信息来获取当前用户的角色(可能需要查询数据库)<br />(4)创建SimpleAuthorizationInfo对象,并设置roles属性<br /> new SimpleAuthorizationInfo(Set<String> roles)<br /> SimpleAuthorizationInfo.addRole(String roles)<br /> SimpleAuthorizationInfo.setRoles(Set<String> roles)<br />(5)返回SimpleAuthorizationInfo对象<br /> <br />18.Shiro标签<br />概述:Shiro提供了JSTL标签用于在JSP页面进行权限控制,如根据登录用<br />户显示相应的页面按钮。<br /> <br /> guest标签:用户没有身份验证时显示相应信息,即游客访问信息:<br /> <shiro:guest><br /> 欢迎游客访问,<a href=”login.jsp”></a><br /> </shiro:guest><br /> <br /> user标签:用户已经经过认证/记住我登录后显示相应的信息。<br /> </shiro:user><br /> 欢迎[<shiro:principal/>]登录,<a href=”logout”>退出</a><br /> </shiro:user><br /> <br /> authenticated标签:用户已经身份验证通过,即Subject.login登录成功,<br />不是记住我登录的<br /> <shiro:authenticated><br /> 用户[<shiro:principal/>]已经身份验证通过<br /></shiro:authenticated><br /> <br /> notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login<br />进行登录,包括记住我自动登录的也属于未进行<br />身份验证<br /> <shiro:notAuthenticated><br /> 未身份验证(包括记住我)<br /></shiro:notAuthenticated><br /> <br /> principal标签:显示用户身份信息,默认调用Subject.getPrincipal()获<br />取,即Primary Principal<br /> <shiro:principal property=”username”/><br /> <br /> hasRole标签:如果当前Subject有角色将显示body体内容<br /> <shiro:hasRole name=”admin”><br /> 用户[<shiro:principal/>]拥有角色admin<br/><br /></shiro:hasRole><br /> <br /> hasAnyRoles标签:如果当前Subject有任意一个角色将显示在body体内容 <shiro:hasAnyRoles name=”admin,user”><br /> 用户[<shiro:principal/>]拥有角色admin或user<br/><br /></shiro:hasAnyRoles><br /> <br /> <br />lackRole标签:如果当前Subject没有角色将显示body体内容<br /> <shiro:lackRole name=”admin”><br /> 用户[<shiro:principal/>]没有角色admin<br/><br /></shiro:lackRole><br /> <br /> hasPermission标签:如果当前Subject有权限将显示body体内容<br /> <shiro:hasPermission name=”user:create”><br /> 用户[<shiro:principal/>]拥有权限user:create<br /></shiro:hasPermission><br /> <br /> lacksPermission标签:如果当前Subject没有权限将显示body体内容<br /> <shiro:lacksPermission name=”org:create”><br /> 用户[<shiro:principal/>]没有权限org.create<br/><br /></shiro:lacksPermission><br /> <br /> 使用:在jsp页面中导入<%@ taglib prefix=”shiro”<br />uri=”[http://shiro.apache.org/tags](http://shiro.apache.org/tags)”%><br /> <br />19、权限注解<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013222942-959da44e-abcd-4ec9-a0ad-ff555ad819db.png#height=213&width=415)<br /> 注意:当Service使用事务注解时,该shiro相关的注解就不能使用在<br />Service层,而是使用在Controller层<br /> <br />20、从数据库中初始化资源和权限<br />配置url权限的方式:setFilterChainDefinitionMap(Map<String,String>)<br />在shiroFilter中配置filterChainDefinitionMap:<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013223435-afa328e6-949e-4697-9272-4fc9c2cb7089.png#height=32&width=349)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013223671-e83f730e-373f-4c0e-8b59-db0114e4fb0f.png#height=98&width=416)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013223905-2fd4e9a5-0064-43a0-a552-09a6a18eceec.png#height=128&width=415)<br /> <br />21、会话管理<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013224211-224a391a-8afa-4f78-8b56-66c889ffe997.png#height=244&width=415) ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013224598-defa5824-393d-44bd-b138-11d594c4bf78.png#height=123&width=411)<br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013226131-d1273b6f-4224-4abf-ac5a-28ec3ce791cc.png#height=166&width=398)<br />作用:在Service层无法获取HttpSession,因此在Service层使用Shiro<br />提供的Session,从而获取Session域中的数据。<br /> <br />22、SessionDao <br /> ![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013226714-872ae22a-835e-4260-a040-3481406af237.png#height=217&width=318)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013227348-182469a9-b8e8-4a64-9084-2cecbc091b1a.png#height=167&width=415)<br />第一步:自定义类,继承EnterpriseCacheSessionDAO<br />第二步:配置三个bean,SessionManager配置给securityManager<br />第三步:实现方法,对Session要进行序列化和反序列化<br /> <br />23、缓存<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013227797-82180316-ab3c-4e0a-9f12-8a98e7c6ca94.png#height=98&width=415)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013228271-9e3b13fe-fc79-4ffc-911e-1d50e93072ac.png#height=101&width=415)<br /> <br />24、记住我<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013228724-aea6e190-0204-4aab-952f-ec1391f8ca45.png#height=188&width=415)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013229264-1e0fc715-36c4-4b4e-99f6-83e909360c98.png#height=189&width=415)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013229751-df9316ee-8953-4993-b944-a70265535e6e.png#height=196&width=415)<br /> <br />25、实现RememberMe<br />原理:实际上是SecurityManager的RememberManager中的cookie来保存密码<br />设置时间:通过设置cookie的属性maxAge(单位:秒)<br />![](https://cdn.nlark.com/yuque/0/2020/png/471109/1608013229994-b3ff026e-b401-4753-b15a-b5b07c0ebcde.png#height=30&width=416)<br /> 实现:token.setRememberMe(true);<br /> <br />