Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
一、将shiro的依赖包增加到项目pom.xml文件 目前使用的版本是 1.3.2
<!--shiro包-->
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
二、在web.xml文件增加shiro 的监听配置
<!--shiro配置,要放在mvc前面,如果有sitemesh,放在sitemesh前面--> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <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>
三、在spring-application.xml 配置文件中引入shiro-config.xml配置文件
<!--shiro权限管理配置 --> <import resource="classpath:shiro-config.xml" />
shiro-config.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <!-- web.xml中shiro的filter对应的bean --> <!-- Shiro 的Web过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 --> <property name="loginUrl" value="/login" /> <!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 --> <property name="successUrl" value="/"/> <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面--> <property name="unauthorizedUrl" value="/refuse" /> <property name="filters"> <map> <entry key="authc" value-ref="formAuthenticationFilter" /> <entry key="logout" value-ref="logoutFilter" /> </map> </property> <!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 --> <property name="filterChainDefinitions"> <value> <!--开发前端库全部开放 (虽然在容器中用defaultServlet中处理过,但这里是filter,在servlet之前,还是需要指定这些目录不经验证)--> /favicon.ico = anon /lib/** = anon /js/** = anon /css/** = anon /img/** = anon <!--退出登录接口--> /logout = logout <!--admin的url必须角色admin才能访问--> /admin/** = roles[admin] <!--其它url将全部需要登录后才可以访问--> /** = authc </value> </property> </bean> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> <property name="loginUrl" value="/login"/> </bean> <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <!-- 退出登录后跳转的页面,默认将跳回登录页面 --> <property name="redirectUrl" value="/login" /> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true"/> </bean> --> <!-- securityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="defaultRealm"/> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入session管理器 --> <property name="sessionManager" ref="sessionManager" /> </bean> <!-- 默认realm,通过帐号密码进行验证登录 --> <bean id="defaultRealm" class="com.mywind.eemp.utils.shiro.realm.DefaultRealm"> <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher"/> <!--打开缓存--> <property name="cachingEnabled" value="true"/> <!--把登录信息缓存起来--> <property name="authenticationCachingEnabled" value="true"/> <property name="authenticationCacheName" value="authenticationCache"/> <!--把权限信息缓存起来--> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="authorizationCache"/> </bean> <!-- 凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--使用md5进行密码加密--> <property name="hashAlgorithmName" value="md5" /> <!--加密两次--> <property name="hashIterations" value="2" /> </bean> <!-- 缓存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:permission/shiro-ehcache.xml"/> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- session的失效时长,单位毫秒 --> <property name="globalSessionTimeout" value="6000000"/> <!-- 删除失效的session --> <property name="deleteInvalidSessions" value="true"/> </bean> <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie" /> </bean> <!-- 记住我cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- rememberMe是cookie的名字 --> <constructor-arg value="rememberMe" /> <!-- 记住我cookie生效时间30天 --> <property name="maxAge" value="2592000" /> </bean> <!-- 开启Shiro注解的Spring配置方式的beans。在lifecycleBeanPostProcessor之后运行 --> <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> </beans>
这里缓存管理使用 了 shiro-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!--diskStore:缓存数据持久化的目录 地址 --> <diskStore path="D:/WorkSpace/JAVA/ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
四、在shrio-config.xml配置中,自己实现默认的认证授权类。
<bean id="defaultRealm" class="com.mywind.eemp.utils.shiro.realm.DefaultRealm">
DefaultRealm.java 如下:
package com.mywind.eemp.utils.shiro.realm; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.mywind.eemp.beans.entity.SysMenuPermission; import com.mywind.eemp.beans.entity.SysRole; import com.mywind.eemp.beans.entity.SysUser; import com.mywind.eemp.enums.SysUserStatusEnum; import com.mywind.eemp.service.custom.SysRolesPermissionService; import com.mywind.eemp.service.custom.SysUserRolesService; import com.mywind.eemp.service.impl.SysUserServiceImpl; 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.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; /** * 实现默认的登录认证,和认证通过后的授权方法 */ public class DefaultRealm extends AuthorizingRealm { @Autowired private SysUserServiceImpl sysUserService; @Autowired private SysUserRolesService sysUserRolesService; @Autowired private SysRolesPermissionService sysRolesPermissionService; //认证后的授权方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SysUser sysUser = (SysUser) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> rolesString = new HashSet<>(); Set<String> permissionString = new HashSet<>(); //把用户所有的角色查找出来,转化为Set<String>,String为角色名称,并set到AuthorizationInfo中 List<SysRole> roleList = sysUserRolesService.selectRolesBySysUserId(sysUser.getId()); for (SysRole sysRole : roleList) { rolesString.add(sysRole.getMatchString()); } info.setRoles(rolesString); //同时把所有这些角色的权限搜索出来,并set到AuthorizationInfo中 List<SysMenuPermission> permissionList = sysRolesPermissionService.selectPermissionsBySysRoles(roleList); for (SysMenuPermission sysMenuPermission : permissionList) { permissionString.add(sysMenuPermission.getMatchString()); } info.setStringPermissions(permissionString); return info; } //realm的认证方法,从数据库查询用户信息 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String loginName = (String) authenticationToken.getPrincipal(); SysUser sysUser = sysUserService.selectOne( new EntityWrapper<SysUser>().eq("login_name", loginName) ); if(sysUser == null) { throw new UnknownAccountException(); //没找到帐号 } if(sysUser.getIsLock().equals(SysUserStatusEnum.LOCK)) { throw new LockedAccountException(); //帐号锁定 } //第一个参数建议传SysUser对象进去,后面获取权限的时候可以通过对象获取各种信息,不用去数据库再查一次 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( sysUser, sysUser.getPassword(), ByteSource.Util.bytes(sysUser.getSalt()), this.getName() ); return simpleAuthenticationInfo; } }
五、在shrio-config.xml配置文件中filter 指定了登录的入口方法
<property name="loginUrl" value="/login" />
在controller 的login方法,即可以进行登录认证的结果判定。
@Controller public class LoginController { @RequestMapping(value = "/login") public String loginPage(HttpServletRequest request, Model model) { //如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名 String exceptionClassName = (String) request.getAttribute("shiroLoginFailure"); String error = ""; //根据shiro返回的异常类路径判断,抛出指定异常信息 if(exceptionClassName!=null){ if (UnknownAccountException.class.getName().equals(exceptionClassName)) { error = "找不到该用户"; } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { error = "用户名/密码错误"; } else if(LockedAccountException.class.getName().equals(exceptionClassName)){ error = "你的帐号被锁定了,请稍后再试。"; } else if(exceptionClassName != null){ error = "其他错误:" + exceptionClassName; } } model.addAttribute("error", error); //这个login方法当登录成功后不做跳转处理,跳转处理已经由shiro来进行处理 return "login"; } }
六、shiro权限标签在页面的使用:
首先导入 权限标签库:<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
标签:hasPermission 表示拥有某个权限 hasRole 表示拥有某个角色 shiro:user标签表示已经登录的用户属性
hasAllRoles表示拥有所有相关的角色;hasAllPermissions表示拥有所有相关的权限;hasAnyPermissions表示拥有任意一个相关的权限。
<div class="mywp-header clearfix"><div class="mywp-logo"><h1><a href="">logo</a></h1></div><div class="site-menu"><div class="nav"><div class="overTop"></div><div class="overBottom"></div><a href="#" class="active">首页</a><shiro:hasPermission name="monitor"><a href="#">xx</a></shiro:hasPermission><shiro:hasPermission name="powerAnalysis"><a href="#">综合分析</a></shiro:hasPermission><shiro:hasPermission name="reportManager"><a href="#">报表管理</a></shiro:hasPermission><shiro:hasPermission name="generalSearch"><a href="#">故障告警</a></shiro:hasPermission><shiro:hasRole name="admin"><a href="admin/list">系统配置</a></shiro:hasRole></div><div class="action"><shiro:user> <a href="#"><span class="mywp-icon icon-admin"></span>您好,<shiro:principal property="displayName"/></a> </shiro:user> </div><div class="exitSystem"><a href="#" id="logout_button"><span class="mywp-icon icon-exit"></span></a></div></div></div>
至此,从引入包到配置xml。到实现默认认证和授权,页面标签使用完整可以使用。