Spring Boot Shiro 权限信息缓存处理,记住我,thymleaf使用shiro标签

来源:互联网 发布:信用卡在淘宝限额 编辑:程序博客网 时间:2024/05/21 08:48

转:
http://412887952-qq-com.iteye.com/blog/2299784

权限信息缓存处理

实际中我们的权限信息是不怎么会改变的,所以我们希望是第一次访问,然后进行缓存处理,那么Shiro是否支持呢,答案是肯定的,我们在下一小节进行讲解,如何在Shiro中加入缓存机制。

主要分这么几个步骤:在pom.xml中加入缓存依赖;注入缓存;
(a) 在pom.xml文件中加入依赖:

<!-- shiro ehcache --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.3</version></dependency><!-- 包含支持UI模版(Velocity,FreeMarker,JasperReports), 邮件服务, 脚本服务(JRuby), 缓存Cache(EHCache), 任务计划Scheduling(uartz)。 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId></dependency></dependencies>
(b)注入缓存
在ShiroConfiguration中加入如下方法:

/**     * shiro缓存管理器;     * 需要注入对应的其它的实体类中:     * 1、安全管理器:securityManager     * 可见securityManager是整个shiro的核心;     * @return     */    @Bean    public EhCacheManager ehCacheManager(){       System.out.println("ShiroConfiguration.getEhCacheManager()");       EhCacheManager cacheManager = new EhCacheManager();       cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");       return cacheManager;    }

将缓存对象注入到SecurityManager中:

@Beanpublic SecurityManager securityManager(){       DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();       //设置realm.       securityManager.setRealm(myShiroRealm());       //注入缓存管理器;       securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;       return securityManager;    }
(c)添加缓存配置文件:
在src/main/resouces/config添加ehcache-shiro.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?><ehcache name="es">    <diskStore path="java.io.tmpdir"/>         <!--       name:缓存名称。       maxElementsInMemory:缓存最大数目       maxElementsOnDisk:硬盘最大缓存个数。        eternal:对象是否永久有效,一但设置了,timeout将不起作用。        overflowToDisk:是否保存到磁盘,当系统当机时       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。        clearOnFlush:内存数量最大时是否清除。       memoryStoreEvictionPolicy:            Ehcache的三种清空策略;            FIFO,first in first out,这个是大家最熟的,先进先出。            LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。            LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。    -->    <defaultCache            maxElementsInMemory="10000"            eternal="false"            timeToIdleSeconds="120"            timeToLiveSeconds="120"            overflowToDisk="false"            diskPersistent="false"            diskExpiryThreadIntervalSeconds="120"            />    <!-- 登录记录缓存锁定10分钟 -->    <cache name="passwordRetryCache"           maxEntriesLocalHeap="2000"           eternal="false"           timeToIdleSeconds="3600"           timeToLiveSeconds="0"           overflowToDisk="false"           statistics="true">    </cache></ehcache>
在配置文件上已经有很详细的解释了,所以这里就过多介绍ehcache的配置了。
运行程序访问:http://127.0.0.1:8080/userInfo/userAdd
查看控制台的打印信息:
权限配置-->MyShiroRealm.doGetAuthorizationInfo()

这个信息就只打印一次了,说明我们的缓存生效了


密码多次输入错误

CredentialsMatcher是shiro提供的用于加密密码和验证密码服务的接口,而HashedCredentialsMatcher正是CredentialsMatcher的一个实现类

public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcherpublic class HashedCredentialsMatcher extends SimpleCredentialsMatcher

自定义RetryLimitHashedCredentialsMatcher继承HashedCredentialsMatcher

package com.example.config.shiro;import java.util.concurrent.atomic.AtomicInteger;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.ExcessiveAttemptsException;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheManager;public class RetryLimitHashedCredentialsMatcher extends  HashedCredentialsMatcher{private Cache<String, AtomicInteger> passwordRetryCache;        public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {          passwordRetryCache = cacheManager.getCache("passwordRetryCache");      }        @Override      public boolean doCredentialsMatch(AuthenticationToken token,              AuthenticationInfo info) {          String username = (String) token.getPrincipal();          // retry count + 1          AtomicInteger retryCount = passwordRetryCache.get(username);          if (retryCount == null) {              retryCount = new AtomicInteger(0);              passwordRetryCache.put(username, retryCount);          }          if (retryCount.incrementAndGet() > 5) {              // if retry count > 5 throw              throw new ExcessiveAttemptsException();          }            boolean matches = super.doCredentialsMatch(token, info);          if (matches) {              // clear retry count              passwordRetryCache.remove(username);          }          return matches;      }  }

在回调方法doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info)中进行身份认证的密码匹配,这里我们引入了Ehcahe用于保存用户登录次数,如果登录失败retryCount变量则会一直累加,如果登录成功,那么这个count就会从缓存中移除,从而实现了如果登录次数超出指定的值就锁定。


修改ShiroConfiguration的方法

/** * 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 * 所以我们需要修改下doGetAuthenticationInfo中的代码; ) *  * @return */@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());//new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于// md5(md5(""));return hashedCredentialsMatcher;}
ehcache-shiro.xml 加入配置

<!-- 登录记录缓存锁定10分钟 -->    <cache name="passwordRetryCache"           maxEntriesLocalHeap="2000"           eternal="false"           timeToIdleSeconds="3600"           timeToLiveSeconds="0"           overflowToDisk="false"           statistics="true">    </cache>

登录方法加入异常判断

else if (ExcessiveAttemptsException.class.getName().equals(exception)) {                  System.out.println("ExcessiveAttemptsException -- > 登录失败次数过多:");                  msg = "ExcessiveAttemptsException -- > 登录失败次数过多:";              }




记住我

记住密码实现起来也是比较简单的,主要看下是如何实现的。
在ShiroConfiguration加入两个方法:

@Beanpublic SimpleCookie rememberMeCookie(){       System.out.println("ShiroConfiguration.rememberMeCookie()");       //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe       SimpleCookie simpleCookie = new SimpleCookie("rememberMe");       //<!-- 记住我cookie生效时间30天 ,单位秒;-->       simpleCookie.setMaxAge(259200);       return simpleCookie;}/**  * cookie管理对象;  * @return  */@Beanpublic CookieRememberMeManager rememberMeManager(){       System.out.println("ShiroConfiguration.rememberMeManager()");       CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();       cookieRememberMeManager.setCookie(rememberMeCookie());       return cookieRememberMeManager;}

将rememberMeManager注入到SecurityManager中

@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realm.securityManager.setRealm(myShiroRealm());//注入缓存管理器;     securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;//注入记住我管理器;    securityManager.setRememberMeManager(rememberMeManager());return securityManager;}
在ShiroFilterFactoryBean添加记住我过滤器
@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {System.out.println("ShiroConfiguration.shiroFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置SecuritManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 拦截器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置退出过滤器,其中的具体代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout", "logout");//配置记住我或认证通过可以访问的地址        filterChainDefinitionMap.put("/index", "user");        filterChainDefinitionMap.put("/", "user");        // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->filterChainDefinitionMap.put("/servlet/safecode", "anon");filterChainDefinitionMap.put("/**", "authc");// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}
主要是加入了:
//配置记住我或认证通过可以访问的地址
filterChainDefinitionMap.put("/index", "user");
filterChainDefinitionMap.put("/", "user");

修改登录界面加入rememberMe复选框:
在login.html中加入:
<P><input type="checkbox" name="rememberMe" />记住我</P>

这时候运行程序,登录之后跳转到/index页面,然后我们关闭浏览器,然后直接访问/index还是可以访问的,说明我们写的记住密码已经生效了,如果访问http://127.0.0.1:8080/userInfo/userAdd 的
话还是需要重新登录的。

------

thymleaf使用shiro标签

shiro权限框架,前端验证是为jsp设计的,其中的tag只能用于jsp系列的模板引擎。最近项目使用了thymeleaf作为前端模板引擎,使用HTML文件,没法引入shiro的tag lib,此时如果要使用shiro的话,可以引入 thymeleaf-extras-shiro.jar这个拓展包来曲线实现shiro的前端验证。

https://github.com/theborakompanioni/thymeleaf-extras-shiro

在pom.xml中加入如下依赖:

<dependency>    <groupId>com.github.theborakompanioni</groupId>    <artifactId>thymeleaf-extras-shiro</artifactId>    <version>1.2.1</version></dependency>

ShiroConfiguration.java中添加

    /**     * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean     * @return     */    @Bean    public ShiroDialect shiroDialect(){return new ShiroDialect();}

index.html引入

<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<!DOCTYPE html>  <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"><head>  <meta charset="UTF-8" />  <title>Insert title here</title>  </head>  <body>      <h3>index</h3>     <!-- 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。 -->    <p shiro:guest="">Please <a href="login.html">login</a></p>            <!-- 认证通过或已记住的用户。 -->    <p shiro:user="">       Welcome back John! Not John? Click <a href="login.html">here</a> to login.    </p>        <!-- 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。 -->    <p shiro:authenticated="">      Hello, <span shiro:principal=""></span>, how are you today?    </p>     <a shiro:authenticated="" href="updateAccount.html">Update your contact information</a>        <!-- 输出当前用户信息,通常为登录帐号信息。 -->    <p>Hello, <shiro:principal/>, how are you today?</p>            <!-- 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。 -->    <p shiro:notAuthenticated="">       Please <a href="login.html">login</a> in order to update your credit card information.    </p>         <!-- 验证当前用户是否属于该角色。 -->    <a shiro:hasRole="admin" href="admin.html">Administer the system</a><!-- 拥有该角色 -->        <!-- 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。 -->    <p shiro:lacksRole="developer"><!-- 没有该角色 -->      Sorry, you are not allowed to developer the system.    </p>        <!-- 验证当前用户是否属于以下所有角色。 -->    <p shiro:hasAllRoles="developer, admin"><!-- 角色与判断 -->       You are a developer and a admin.    </p>        <!-- 验证当前用户是否属于以下任意一个角色。  -->    <p shiro:hasAnyRoles="admin, vip, developer"><!-- 角色或判断 -->         You are a admin, vip, or developer.    </p>        <!--验证当前用户是否拥有指定权限。  -->    <a shiro:hasPermission="userInfo:add" href="createUser.html">添加用户</a><!-- 拥有权限 -->        <!-- 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。 -->    <p shiro:lacksPermission="userInfo:del"><!-- 没有权限 -->         Sorry, you are not allowed to delete user accounts.    </p>        <!-- 验证当前用户是否拥有以下所有角色。 -->    <p shiro:hasAllPermissions="userInfo:view, userInfo:add"><!-- 权限与判断 -->           You can see or add users.    </p>        <!-- 验证当前用户是否拥有以下任意一个权限。  -->    <p shiro:hasAnyPermissions="userInfo:view, userInfo:del"><!-- 权限或判断 -->               You can see or delete users.    </p>    </body>  </html>


-----------------------------------------------------转:http://itindex.net/detail/55726-apache-shiro-spring?utm_source=tuicool&utm_medium=referral

实际上在Spring boot里用Spring Security最合适,毕竟是自家东西,最重要的一点是Spring Security里自带有csrf filter,防止csrf攻击,shiro里就没有。
但是Spring Security有点太复杂,custmize起来比较费力,不如shiro来的简单。

如果想要在Spring boot里使用shiro,需要进行以下配置,首先pom.xml里要添加shiro的依赖

<dependency> <groupId>org.apache.shiro</groupId>    <artifactId>shiro-spring</artifactId>    <version>1.2.5</version></dependency><dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-ehcache</artifactId>    <version>1.2.5</version></dependency><dependency>    <groupId>com.github.theborakompanioni</groupId>    <artifactId>thymeleaf-extras-shiro</artifactId> <version>1.2.1</version></dependency>

shiro官方只提供了jsp的标签,没有提供thymeleaf的,而thymeleaf在spring boot里应用已经很广泛了,这里依赖了一个第三方包。
然后就是shiro的配置文件,这里我们使用java-based 配置

@Configurationpublic class ShiroConfiguration {    @Bean(name = "lifecycleBeanPostProcessor")    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {        return new LifecycleBeanPostProcessor();    }@Bean(name = "hashedCredentialsMatcher")public HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();credentialsMatcher.setHashAlgorithmName("MD5");credentialsMatcher.setHashIterations(2);credentialsMatcher.setStoredCredentialsHexEncoded(true);return credentialsMatcher;}    @Bean(name = "shiroRealm")    @DependsOn("lifecycleBeanPostProcessor")    public ShiroRealm shiroRealm() {ShiroRealm realm = new ShiroRealm(); realm.setCredentialsMatcher(hashedCredentialsMatcher());        return realm;    }@Bean(name = "ehCacheManager")@DependsOn("lifecycleBeanPostProcessor")public EhCacheManager ehCacheManager(){EhCacheManager ehCacheManager = new EhCacheManager();return ehCacheManager;}@Bean(name = "securityManager")public SecurityManager securityManager(){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(shiroRealm());securityManager.setCacheManager(ehCacheManager());return securityManager;}@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shiroFilterFactoryBean(){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager());Map<String, Filter> filters = new LinkedHashMap<String, Filter>();LogoutFilter logoutFilter = new LogoutFilter();logoutFilter.setRedirectUrl("/login");filters.put("logout", logoutFilter);shiroFilterFactoryBean.setFilters(filters);Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();filterChainDefinitionManager.put("/logout", "logout");filterChainDefinitionManager.put("/user/**", "authc,roles[user]");filterChainDefinitionManager.put("/shop/**", "authc,roles[shop]");filterChainDefinitionManager.put("/admin/**","authc,roles[admin]");filterChainDefinitionManager.put("/**", "anon");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);shiroFilterFactoryBean.setLoginUrl("/login");shiroFilterFactoryBean.setSuccessUrl("/");shiroFilterFactoryBean.setUnauthorizedUrl("/403");return shiroFilterFactoryBean;}@Bean@ConditionalOnMissingBean    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();        daap.setProxyTargetClass(true);        return daap;    }@Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();        aasa.setSecurityManager(securityManager());        return aasa;    }@Bean(name = "shiroDialect")public ShiroDialect shiroDialect(){return new ShiroDialect();}}


1.LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。主要是AuthorizingRealm类的子类,以及EhCacheManager类。

2.HashedCredentialsMatcher,这个类是为了对密码进行编码的,防止密码在数据库里明码保存,当然在登陆认证的生活,这个类也负责对form里输入的密码进行编码。

3.ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,负责用户的认证和权限的处理,可以参考JdbcRealm的实现。
4.EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来,然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。
5.SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。
6.ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。
7.DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
 8.AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。老实说,这里注入securityManager,我不知道有啥用,从source上看不出它在什么地方会被调用。

    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
            new Class[] {
                    RequiresPermissions.class, RequiresRoles.class,
                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
            };

9.ShiroDialect,为了在thymeleaf里使用shiro的标签的bean


1 0