Shiro提供身份验证、受权、企业会话管理和加密等功能。css
一、添加依赖:html
<!-- shiro spring. --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency> <!-- shiro ehcache --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.2</version> </dependency> <!-- shiro-thymeleaf 2.0.0--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
二、在src/main/resources添加config文件夹,建立ehcache-shiro.xml文件,用于权限缓存:前端
<?xml version="1.0" encoding="UTF-8"?> <!--<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"--> <!--xsi:noNamespaceSchemaLocation="ehcache.xsd">--> <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="3600" timeToLiveSeconds="0" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <cache name="user" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"/> <!-- 登陆记录缓存锁定1小时 --> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true" /> </ehcache>
三、实现自定义的ShiroRealm.java类:java
package com.example.demo.realm; import com.example.demo.entity.Menu; import com.example.demo.entity.Role; import com.example.demo.entity.User; import com.example.demo.service.MenuService; import com.example.demo.service.RoleService; import com.example.demo.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 在认证、受权内部实现机制中都有提到,最终处理都将交给Real进行处理。由于在Shiro中,最终是经过Realm来获取应用程序中的用户、角色及权限信息的。一般状况下,在Realm中会直接从咱们的数据源中获取Shiro须要的验证信息。能够说,Realm是专用于安全框架的DAO. * Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。 * 该方法主要执行如下操做: * * 检查提交的进行认证的令牌信息 * 根据令牌信息从数据源(一般为数据库)中获取用户信息 * 对用户信息进行匹配验证。 * 验证经过将返回一个封装了用户信息的AuthenticationInfo实例。 * 验证失败则抛出AuthenticationException异常信息。而在咱们的应用程序中要作的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。 */ public class ShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private MenuService menuService; /** * 验证用户身份 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String) token.getPrincipal(); String password = new String((char[]) token.getCredentials()); //实际项目中,这里能够根据实际状况作缓存,若是不作,Shiro本身也是有时间间隔机制,2分钟内不会重复执行该方法 User user = this.userService.findByName(userName); //这里校验了,CredentialsMatcher就不须要了,若是这里不校验,调用CredentialsMatcher校验 if (user == null) { throw new UnknownAccountException("用户名或密码错误!"); } if (!password.equals(user.getPassword())) { throw new IncorrectCredentialsException("用户名或密码错误!"); } if ("0".equals(user.getEnabled())) { throw new LockedAccountException("帐号已被锁定,请联系管理员!"); } //也能够在此处更新最后登陆时间(或在登陆方法实现) SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); // SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()),getName()); ////salt=username+salt return info; } /** * 受权用户权限 * 受权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的,它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,若是有,里面的内容显示,若是没有,里面的内容不予显示(这就完成了对于权限的认证.) */ /** * shiro的权限受权是经过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo(); * 当访问到页面的时候,连接配置了相应的权限或者shiro标签才会执行此方法不然不会执行,因此若是只是简单的身份认证没有权限的控制的话,那么这个方法能够不进行实现,直接返回null便可。 * 在这个方法中主要是使用类:SimpleAuthorizationInfo * 进行角色的添加和权限的添加。 * authorizationInfo.addRole(role.getRole()); * authorizationInfo.addStringPermission(p.getPermission()); * 固然也能够添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限 * authorizationInfo.setRoles(roles); * authorizationInfo.setStringPermissions(stringPermissions); * 就是说若是在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “perms[权限添加]”); * 就说明访问/add这个连接必需要有“权限添加”这个权限才能够访问, * 若是在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”); * 就说明访问/add这个连接必需要有“权限添加”这个权限和具备“100002”这个角色才能够访问。 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户 User user = (User) SecurityUtils.getSubject().getPrincipal(); // User user = (User) principalCollection.getPrimaryPrincipal(); // User user=(User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();//获取session中的用户 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<Role> roles=this.roleService.findRolesByUserId(user.getId()); //获取用户角色 Set<String> roleSet = new HashSet<String>(); for (Role role:roles) { roleSet.add(role.getName()); } info.setRoles(roleSet); List<Menu> menus=this.menuService.findMenusByUserId(user.getId()); //获取用户权限 Set<String> permissionSet = new HashSet<String>(); for (Menu menu:menus) { if(!StringUtils.isEmpty(menu.getPermission())) { //权限为空会异常,Caused by: java.lang.IllegalArgumentException: Wildcard string cannot be null CollectionUtils.mergeArrayIntoCollection(menu.getPermission().split(","), permissionSet); } } info.setStringPermissions(permissionSet); return info; } /** * 待补充: * shiro+redis集成,避免每次访问有权限的连接都会去执行MyShiroRealm.doGetAuthenticationInfo()方法来查询当前用户的权限,由于实际状况中权限是不会常常变得,这样就可使用redis进行权限的缓存。 * 实现shiro连接权限的动态加载,以前要添加一个连接的权限,要在shiro的配置文件中添加filterChainDefinitionMap.put(“/add”, “roles[100002],perms[权限添加]”),这样很不方便管理,一种方法是将权限使用数据库进行加载,另外一种是经过init配置文件的方式读取。 * Shiro 自定义权限校验Filter定义,及功能实现。 * Shiro Ajax请求权限不知足,拦截后解决方案。这里有一个前提,咱们知道Ajax不能作页面redirect和forward跳转,因此Ajax请求假如没登陆,那么这个请求给用户的感受就是没有任何反应,而用户又不知道用户已经退出了。 * 在线显示,在线用户管理(踢出登陆)。 * 登陆注册密码加密传输。 * 记住个人功能。关闭浏览器后仍是登陆状态。 */ }
四、若有须要实现自定义的密码校验CredentialsMatcher.java:git
package com.example.demo.realm; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; /** * shiro中惟一须要程序员编写的两个类:类ShiroRealm完成根据用户名去数据库的查询,而且将用户信息放入shiro中,供第二个类调用.CredentialsMatcher,完成对于密码的校验.其中用户的信息来自ShiroRealm类 */ public class CredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken utoken=(UsernamePasswordToken) token; //得到用户输入的密码:(能够采用加盐(salt)的方式去检验) String inPassword = new String(utoken.getPassword()); //得到数据库中的密码 String dbPassword=(String) info.getCredentials(); //进行密码的比对 return this.equals(inPassword, dbPassword); } }
五、根据须要,选择建立ShiroSessionListener.java:程序员
package com.example.demo.listener; import java.util.concurrent.atomic.AtomicInteger; import org.apache.shiro.session.Session; import org.apache.shiro.session.SessionListener; public class ShiroSessionListener implements SessionListener{ private final AtomicInteger sessionCount = new AtomicInteger(0); @Override public void onStart(Session session) { sessionCount.incrementAndGet(); } @Override public void onStop(Session session) { sessionCount.decrementAndGet(); } @Override public void onExpiration(Session session) { sessionCount.decrementAndGet(); } }
六、根据须要建立自定义Filter:github
package com.example.demo.filter; import java.io.IOException; import java.util.Set; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.StringUtils; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import org.apache.shiro.web.util.WebUtils; import org.springframework.beans.factory.annotation.Autowired; /** * @Type MyFilter.java * @Desc 用于自定义过滤器,过滤用户请求是否被受权 ,MyFilter是用于过滤须要权限校验的请求 */ public class MyFilter extends AuthorizationFilter { @SuppressWarnings("unchecked") @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object arg2) throws Exception { HttpServletRequest request = (HttpServletRequest) req; //获取请求路径 String path = request.getServletPath(); Subject subject = getSubject(req, resp); if (null != subject.getPrincipals()) { //根据session中存放的用户权限,比对路径,若是拥有该权限则放行 Set<String> userPrivileges = (Set<String>) request.getSession() .getAttribute("USER_PRIVILEGES"); if (null != userPrivileges && userPrivileges.contains(path)) { return true; } } return false; } /** * 会话超时或权限校验未经过的,统一返回401,由前端页面弹窗提示 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { if (isAjax((HttpServletRequest) request)) { WebUtils.toHttp(response).sendError(401); } else { String unauthorizedUrl = getUnauthorizedUrl(); if (StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { WebUtils.toHttp(response).sendError(401); } } return false; } private boolean isAjax(HttpServletRequest request) { String header = request.getHeader("x-requested-with"); if (null != header && "XMLHttpRequest".endsWith(header)) { return true; } return false; } }
package com.example.demo.filter; import java.io.IOException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.StringUtils; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import org.apache.shiro.web.util.WebUtils; /** * @Type LoginFilter.java * @Desc 用于自定义过滤器,过滤用户请求时是不是登陆状态 loginFilter主要是覆盖了自带的authc过滤器,让未登陆的请求统一返回401 */ public class LoginFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object arg2) throws Exception { Subject subject = getSubject(req, resp); if (null != subject.getPrincipals()) { return true; } return false; } /** * 会话超时或权限校验未经过的,统一返回401,由前端页面弹窗提示 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { if (isAjax((HttpServletRequest) request)) { WebUtils.toHttp(response).sendError(401); } else { String unauthorizedUrl = getUnauthorizedUrl(); if (StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { WebUtils.toHttp(response).sendError(401); } } return false; } private boolean isAjax(HttpServletRequest request) { String header = request.getHeader("x-requested-with"); if (null != header && "XMLHttpRequest".endsWith(header)) { return true; } return false; } }
六、建立Shiro配置类,可用shiro-conf.xml代替:web
package com.example.demo.config; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.example.demo.listener.ShiroSessionListener; import com.example.demo.realm.CredentialsMatcher; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; import com.example.demo.realm.ShiroRealm; import org.apache.shiro.codec.Base64; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.session.mgt.ValidatingSessionManager; import org.apache.shiro.session.mgt.eis.*; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; //import org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler /** * shiro的配置类,须要注意一点filterChainDefinitionMap必须是LinkedHashMap由于它必须保证有序 * @author Administrator * */ @Configuration public class ShiroConfig { //受权缓存管理器 @Bean public EhCacheManager getEhCacheManager() { EhCacheManager em = new EhCacheManager(); em.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); return em; } /** * 使用上面的Ehcache或下面的shiro自带的内存缓存实现 */ // @Bean // public MemoryConstrainedCacheManager getMemoryConstrainedCacheManager() { // return new MemoryConstrainedCacheManager(); // } /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,由于在 * 初始化ShiroFilterFactoryBean的时候须要注入:SecurityManager * * Filter Chain定义说明 一、一个URL能够配置多个Filter,使用逗号分隔 二、当设置多个过滤器时,所有验证经过,才视为经过 * 三、部分过滤器可指定参数,如perms,roles * * <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> * <property name="securityManager" ref="securityManager" /> * <!-- 配置登陆页 --> * <property name="loginUrl" value="/login.jsp" /> * <!-- 配置登陆成功后的页面 --> * <property name="successUrl" value="/list.jsp" /> * <property name="unauthorizedUrl" value="/unauthorized.jsp" /> * <property name="filterChainDefinitions"> * <value> * <!-- 静态资源容许访问 --> * <!-- 登陆页容许访问 --> * /login.jsp = anon * /test/login = anon * /user/delete = perms["delete"] * /logout = logout * <!-- 其余资源都须要认证 --> * /** = authc * </value> * </property> * </bean> */ /** * Shiro主过滤器自己功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 * Web应用中,Shiro可控制的Web请求必须通过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 * @param securityManager * @return */ @Bean(name="shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager,Shiro的核心安全接口 shiroFilterFactoryBean.setSecurityManager(securityManager); // 配置登陆的url,若是不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面(源码) shiroFilterFactoryBean.setLoginUrl("/login"); //这是后台的/控制器 // 登陆成功后要跳转的连接,本例中此属性用不到,由于登陆成功后的处理逻辑在LoginController里硬编码了 shiroFilterFactoryBean.setSuccessUrl("/index"); //这是Index.html页面 // 未受权界面;配置不会被拦截的连接 顺序判断 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); ////这里设置403并不会起做用 // 有自定义拦截器就放开 wangzs(源码) // //自定义拦截器 // LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>(); // //限制同一账号同时在线的个数。 // //filtersMap.put("kickout", kickoutSessionControlFilter()); // shiroFilterFactoryBean.setFilters(filtersMap); // 配置访问权限,权限控制map.Shiro链接约束配置,即过滤链的定义, LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 配置不会被拦截的连接 顺序判断 // value值的'/'表明的路径是相对于HttpServletRequest.getContextPath()的值来的 // anon:它对应的过滤器里面是空的,什么都没作,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 // authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter // 配置退出过滤器,其中的具体的退出代码Shiro已经替咱们实现了 // filterChainDefinitionMap.put("/logout", "logout"); // 从数据库获取动态的权限 // filterChainDefinitionMap.put("/add", "perms[权限添加]"); /userList=roles[admin],须要有admin这个角色,若是没有此角色访问此URL会返回无受权页面,或authc,perms[user:list] // <!-- 须要权限为add的用户才能访问此请求--> // /user=perms[user:add] // <!-- 须要管理员角色才能访问此页面 --> // /user/add=roles[admin]或roles[admin],perms[user:add] // <!-- 过滤链定义,从上向下顺序执行,通常将 /**放在最为下边 -->:这是一个坑呢,一不当心代码就很差使了; // <!-- authc:全部url都必须认证经过才能够访问; anon:全部url都均可以匿名访问--> //logout这个拦截器是shiro已经实现好了的。 // 从数据库获取 /*List<SysPermissionInit> list = sysPermissionInitService.selectAll(); for (SysPermissionInit sysPermissionInit : list) { filterChainDefinitionMap.put(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit()); }*/ /** * 可自定义过滤器,好比myFilter替代authc * <bean id="myFilter" class="com.cmcc.hygcc.comm.shiro.MyFilter"></bean> * <property name="filters"> * <map> * <entry key="myFilter" value-ref="myFilter" /> * <!-- 覆盖authc过滤器,使得未登陆的ajax请求返回401状态 --> * <entry key="authc" value-ref="loginFilter" /> * </map> * </property> * * /**=myFilter */ //静态资源容许访问//登陆页容许访问,一个URL能够配置多个Filter,使用逗号分隔,当设置多个过滤器时,所有验证经过,才视为经过,部分过滤器可指定参数,如perms,roles // filterChainDefinitionMap.put("/login.html*", "anon"); //表示能够匿名访问,*表示参数如?error等 filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/unauthorized", "anon"); filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/fonts/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); filterChainDefinitionMap.put("/user/regist", "anon"); filterChainDefinitionMap.put("/gifCode", "anon"); filterChainDefinitionMap.put("/logout", "logout"); //logout是shiro提供的过滤器 filterChainDefinitionMap.put("/user/delete", "perms[\"user:delete\"]"); //此时访问/user/delete须要delete权限,在自定义Realm中为用户受权。 //其余资源都须要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; //Shiro拦截器工厂类注入成功 } //配置核心安全事务管理器 @Bean(name="securityManager") public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(shiroRealm()); //若是方法加上参数@Qualifier("shiroRealm") ShiroRealm shiroRealm,能够直接securityManager.setRealm(shiroRealm); //注入记住我管理器; securityManager.setRememberMeManager(rememberMeManager()); // 自定义缓存实现 可以使用redis // securityManager.setCacheManager(cacheManager()); securityManager.setCacheManager(getEhCacheManager()); //缓存管理器 // 自定义session管理 可以使用redis securityManager.setSessionManager(sessionManager()); return securityManager; } //Shiro生命周期处理器 @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } //配置自定义的权限登陆器,本段不须要配置自定义的密码比较器,能够换成下面的~~里面的写法,须要建立自定义的密码比较器CredentialsMatcher.java @Bean //必须,身份认证realm; (这个须要本身写,帐号密码校验;权限等) public ShiroRealm shiroRealm(){ ShiroRealm shiroRealm = new ShiroRealm(); return shiroRealm; } //须要这种方式就放开 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // //配置自定义的权限登陆器 // @Bean(name="shiroRealm") // public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) { // ShiroRealm shiroRealm=new ShiroRealm(); // shiroRealm.setCredentialsMatcher(matcher); // return shiroRealm; // } // //配置自定义的密码比较器 // @Bean(name="credentialsMatcher") // public CredentialsMatcher credentialsMatcher() { // return new CredentialsMatcher(); // } // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /** * shiro的密码比较器 * @return */ // @Bean // public HashedCredentialsMatcher hashedCredentialsMatcher() { // HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; // hashedCredentialsMatcher.setHashIterations(2);//散列的次数,好比散列两次,至关于 md5(md5("")); // return hashedCredentialsMatcher; // } /** //必须 * cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题: 与SERVLET容器名冲突,从新定义为sid或rememberMe,自定义 * @return */ public SimpleCookie rememberMeCookie() { //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe SimpleCookie cookie = new SimpleCookie("rememberMe"); //<!-- 记住我cookie生效时间30天 ,单位秒;--> cookie.setHttpOnly(true); cookie.setMaxAge(86400); return cookie; } /** //必须 * cookie管理对象;记住我功能,rememberMe管理器 * @return */ public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //rememberMe cookie加密的密钥 建议每一个项目都不同 默认AES算法 密钥长度(128 256 512 位) //3AvVhmFLUs0KTA3Kprsdag== cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } /** * 使用shiro注解为用户受权 1. 在shiro-config.xml开启shiro注解(硬编码,修改权限码很麻烦) * 在方法上配置注解@RequiresPermissions("xxx:yyy") * <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> * 编程方式实现用户权限控制 * Subject subject = SecurityUtils.getSubject(); * if(subject.hasRole("admin")){ * //有权限 * }else{ * //无权限 * } * @return AOP式方法级权限检查,DefaultAdvisorAutoProxyCreator用来扫描上下文,寻找全部的Advistor(通知器),将这些Advisor应用到全部符合切入点的Bean中。 * LifecycleBeanPostProcessor将Initializable和Destroyable的实现类统一在其内部自动分别调用了Initializable.init()和Destroyable.destroy()方法,从而达到管理shiro bean * 生命周期的目的。 */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } // AOP式方法级权限检查 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { //@Qualifier("securityManager") SecurityManager manager AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean //必须(thymeleaf页面使用shiro标签控制按钮是否显示) //未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect public ShiroDialect shiroDialect() { return new ShiroDialect(); } //SessionManager和SessionDAO能够不配置,会话DAO @Bean public SessionDAO sessionDAO() { MemorySessionDAO sessionDAO = new MemorySessionDAO(); return sessionDAO; } /** * sessionDao的方法2 * @return */ // @Bean // // public SessionIdGenerator sessionIdGenerator() { // return new JavaUuidSessionIdGenerator(); // } // @Bean // public SessionDAO sessionDAO() { // EnterpriseCacheSessionDAO cacheSessionDAO=new EnterpriseCacheSessionDAO(); // cacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache"); // cacheSessionDAO.setSessionIdGenerator(sessionIdGenerator()); // return cacheSessionDAO; // } //// 会话管理器,设定会话超时及保存 @Bean public SessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); Collection<SessionListener> listeners = new ArrayList<SessionListener>(); listeners.add(new ShiroSessionListener()); sessionManager.setSessionListeners(listeners); sessionManager.setGlobalSessionTimeout(1800000); //全局会话超时时间(单位毫秒),默认30分钟 sessionManager.setSessionDAO(sessionDAO()); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); //定时清理失效会话, 清理用户直接关闭浏览器形成的孤立会话 sessionManager.setSessionValidationInterval(1800000); // sessionManager.setSessionValidationScheduler(executorServiceSessionValidationScheduler()); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(rememberMeCookie()); return sessionManager; } //会话验证调度器,每30分钟执行一次验证 @Bean(name="sessionValidationScheduler") public ExecutorServiceSessionValidationScheduler executorServiceSessionValidationScheduler() { ExecutorServiceSessionValidationScheduler sessionValidationScheduler=new ExecutorServiceSessionValidationScheduler(); sessionValidationScheduler.setInterval(1800000); sessionValidationScheduler.setSessionManager((ValidatingSessionManager)sessionManager()); return sessionValidationScheduler; } }
七、未受权的设置不生效,须要添加未受权异常处理:ajax
package com.example.demo.resolver; import org.apache.shiro.mgt.SecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.Properties; /** * 定制的异常处理类 */ // private void applyUnauthorizedUrlIfNecessary(Filter filter) { // String unauthorizedUrl = getUnauthorizedUrl(); // if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) { // AuthorizationFilter authzFilter = (AuthorizationFilter) filter; // //only apply the unauthorizedUrl if they haven't explicitly configured one already: // String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl(); // if (existingUnauthorizedUrl == null) { // authzFilter.setUnauthorizedUrl(unauthorizedUrl); // } // } // } //shiro默认过滤器(10个) // anon -- org.apache.shiro.web.filter.authc.AnonymousFilter // authc -- org.apache.shiro.web.filter.authc.FormAuthenticationFilter // authcBasic -- org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter // perms -- org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter // port -- org.apache.shiro.web.filter.authz.PortFilter // rest -- org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter // roles -- org.apache.shiro.web.filter.authz.RolesAuthorizationFilter // ssl -- org.apache.shiro.web.filter.authz.SslFilter // user -- org.apache.shiro.web.filter.authc.UserFilter // logout -- org.apache.shiro.web.filter.authc.LogoutFilter @Configuration public class DzExceptionResolver { /** * shiro中unauthorizedUrl不起做用,这是由于shiro源代码private void applyUnauthorizedUrlIfNecessary(Filter filter)中判断了filter是否为AuthorizationFilter,只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,因此unauthorizedUrl设置后不起做用。 * 解决方法:在shiro配置文件中添加(异常全路径作key,错误页面作value) * <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> * <property name="exceptionMappings"> * <props> * <prop key="org.apache.shiro.authz.UnauthorizedException">/403</prop> * </props> * </property> * </bean> */ @Bean public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver simpleMappingExceptionResolver=new SimpleMappingExceptionResolver(); Properties properties=new Properties(); properties.setProperty("org.apache.shiro.authz.UnauthorizedException","/unauthorized"); simpleMappingExceptionResolver.setExceptionMappings(properties); return simpleMappingExceptionResolver; } /** * 至关于调用SecurityUtils.setSecurityManager(securityManager) * @param securityManager * @return */ @Bean public MethodInvokingFactoryBean getMethodInvokingFactoryBean(@Qualifier("securityManager")SecurityManager securityManager) { MethodInvokingFactoryBean methodInvokingFactoryBean=new MethodInvokingFactoryBean(); methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); methodInvokingFactoryBean.setArguments(securityManager); return methodInvokingFactoryBean; } }
八、登陆和退出Controller:redis
package com.example.demo.controller; import com.example.demo.entity.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ByteSource; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Properties; @Controller public class LoginController { @RequestMapping(value = "/login",method = RequestMethod.GET) public String login() { return "login"; } /** * 和shiro框架的交互彻底经过Subject这个类去交互,用它完成登陆,注销,获取当前的用户对象等操做 * login请求调用subject.login以后,shiro会将token传递给自定义realm,此时realm会先调用doGetAuthenticationInfo(AuthenticationToken authcToken )登陆验证的方法,验证经过后会接着调用 doGetAuthorizationInfo(PrincipalCollection principals)获取角色和权限的方法(受权),最后返回视图。 * 当其余请求进入shiro时,shiro会调用doGetAuthorizationInfo(PrincipalCollection principals)去获取受权信息,如果没有权限或角色,会跳转到未受权页面,如有权限或角色,shiro会放行,此时进入真正的请求方法…… * @param username * @param password * @param model * @param session * @return */ @RequestMapping(value = "/login",method = RequestMethod.POST) // public String loginUser(String username,String password,boolean remeberMe,HttpSession session) { public String loginUser(HttpServletRequest request, String username, String password, Model model, HttpSession session) { // password=new SimpleHash("md5", password, ByteSource.Util.bytes(username.toLowerCase() + "shiro"),2).toHex(); // UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password,remeberMe); UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password); Subject subject = SecurityUtils.getSubject(); try { subject.login(usernamePasswordToken); //完成登陆 User user=(User) subject.getPrincipal(); //更新用户登陆时间,也能够在ShiroRealm里面作 session.setAttribute("user", user); model.addAttribute("user",user); return "index"; } catch(Exception e) { String exception = (String) request.getAttribute("shiroLoginFailure"); //logger.info("登陆失败从request中获取shiro处理的异常信息,shiroLoginFailure:就是shiro异常类的全类名"); model.addAttribute("msg",e.getMessage()); return "login";//返回登陆页面 } } @RequestMapping("/logout") public String logout(HttpSession session,Model model) { Subject subject = SecurityUtils.getSubject(); subject.logout(); // session.removeAttribute("user"); model.addAttribute("msg","安全退出!"); return "login"; } }
九、权限测试Controller:
package com.example.demo.controller; import com.example.demo.entity.User; import com.example.demo.service.UserService; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @Autowired private UserService userService; @RequestMapping("/index") public User index() { return userService.getUserById(1); } @RequiresPermissions("test") @RequestMapping("/test") public String test() { return "ok"; } }
十、项目用的Thymeleaf,要在页面使用shiro标签,首先添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- shiro-thymeleaf 2.0.0--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
十一、application.properties配置thymeleaf:
#thymeleaf的配置是去掉页面缓存(开发环境)和html的校验 spring.thymeleaf.cache=false spring.thymeleaf.mode=LEGACYHTML5
十二、在shiro的configuration中配置:
@Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); }
1三、在html中加入xmlns:
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
1四、使用标签。例如:
<span shiro:authenticated="true" > <span>欢迎您:<span th:text="${userInfo.realName}"></span></span> </span>
1五、标签说明:
一、用户没有身份验证时显示相应信息,即游客访问信息: <shiro:guest>内容</shiro:guest> 二、用户已经身份验证/记住我登陆后显示相应的信息: <shiro:user>内容</shiro:user> 三、用户已经身份验证经过,即Subject.login登陆成功,不是记住我登陆的: <shiro:authenticated>内容</shiro:authenticated> 四、显示用户身份信息,一般为登陆账号信息,默认调用Subject.getPrincipal()获取,即Primary Principal: <shiro:principal/> 五、用户已经身份验证经过,即没有调用Subject.login进行登陆,包括记住我自动登陆的也属于未进行身份验证,与guest标签的区别是,该标签包含已记住用户。: <shiro:notAuthenticated>内容</shiro:notAuthenticated> 六、<shiro:principal type="java.lang.String"/> 至关于Subject.getPrincipals().oneByType(String.class)。 七、<shiro:principal property="username"/> 至关于((User)Subject.getPrincipals()).getUsername()。 八、若是当前Subject有角色将显示body体内容: <shiro:hasRole name="角色名">内容</shiro:hasRole> 九、<shiro:hasAnyRoles name="角色名1,角色名2…">内容</shiro:hasAnyRoles> 若是当前Subject有任意一个角色(或的关系)将显示body体内容。 十、<shiro:lacksRole name="角色名">内容</shiro:lacksRole> 若是当前Subject没有角色将显示body体内容。 十一、<shiro:hasPermission name="权限名">内容</shiro:hasPermission> 若是当前Subject有权限将显示body体内容。 十二、<shiro:lacksPermission name="权限名">内容</shiro:lacksPermission> 若是当前Subject没有权限将显示body体内容。
补充:
1).须要在templates下建立login.html、index.html、unauthorized.html页面。 2).自定义Filter,ShiroSessionListener和自定义密码校验以及ShiroConfig中的配置项,按需添加便可。