16年的项目里用过一次shiro,但是时间久远又没有做好笔记,忘得差不多了,这次又走了一遍,记录一下以便以后翻看,以下是流水。
1.加入依赖
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <!--shiro-->
2.web.xml里加入过滤器
<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>
3.将shiro相关配置单独放在applicationContext-shiro.xml中并在spring配置文件中import
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.wqqd.findfocus" annotation-config="true"/> <!-- 1. 配置 SecurityManager--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager"/> <property name="authenticator" ref="authenticator"></property> <property name="realms"> <list> <ref bean="jdbcRealm"/> </list> </property> <!--<property name="rememberMeManager.cookie.maxAge" value="10"></property>--> </bean> <!--2. 配置 CacheManager(需要加入 ehcache 的 jar 包及配置文件).--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean> <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean><!--多realm时的认证策略--> </property> </bean> <!--3. 配置 Realm(直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean)--> <bean id="jdbcRealm" class="com.wqqd.findfocus.shiro.ShiroRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="SHA1"></property> <property name="hashIterations" value="10"></property> </bean> </property> </bean> <!--<bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">--> <!--<property name="credentialsMatcher">--> <!--<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">--> <!--<property name="hashAlgorithmName" value="SHA1"></property>--> <!--<property name="hashIterations" value="1024"></property>--> <!--</bean>--> <!--</property>--> <!--</bean>--> <!-- 4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 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> <!-- 6. 配置 ShiroFilter. 6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致. 若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean. --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/index.do"/> <property name="successUrl" value="/welcome.do"/> <property name="unauthorizedUrl" value="/unauthorized.do"/> <property name="filters"> <map> <entry key="roles"> <!--通过拦截器将roles[]里角色之间的'且'改为'或'--> <bean class="com.wqqd.findfocus.shiro.CustomRolesAuthorizationFilter" /> </entry> </map> </property> <!--将下面的filterChainDefinitions内容改为动态获取--> <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property> <!-- 配置哪些页面需要受保护. 以及访问这些页面需要的权限. 1). anon 可以被匿名访问 2). authc 必须认证(即登录)后才可能访问的页面. 3). logout 登出. 4). roles 角色过滤器 --> <!--<property name="filterChainDefinitions">--> <!--<value>--> <!--/logout.do = logout--> <!--/** = anon--> <!--</value>--> <!--</property>--> </bean> <!--通过实例工厂方法的形式注入map--> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"></bean> <bean id="filterChainDefinitionMapBuilder" class="com.wqqd.findfocus.shiro.factory.FilterChainDefinitionMapBuilder"></bean> </beans>
下面是对应的几个bean
ShiroRealm
package com.wqqd.findfocus.shiro; import com.wqqd.findfocus.bean.UserLoginBean; import com.wqqd.findfocus.common.Const; import com.wqqd.findfocus.pojo.UserAdmin; import com.wqqd.findfocus.service.IMenuSysService; import com.wqqd.findfocus.service.IUserAdminService; 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 java.util.HashSet; import java.util.Set; /** * @author kexiang.bao * @create 2018-09-18 15:59 */ public class ShiroRealm extends AuthorizingRealm{ @Autowired IUserAdminService iUserAdminService; @Autowired IMenuSysService iMenuSysService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; String userAdminAcct = upToken.getUsername(); UserAdmin userAdmin = iUserAdminService.getUserAdminByAcct(userAdminAcct); if(userAdmin == null){ throw new UnknownAccountException(); } UserLoginBean userLoginBean = new UserLoginBean(); userLoginBean.setUserAdmin(userAdmin); Object principal = userLoginBean; Object credentials = userAdmin.getUserAdminPwd(); userAdmin.setUserAdminPwd(""); ByteSource credentialsSalt = ByteSource.Util.bytes(userAdmin.getUserAdminAcct()); String realmName = super.getName(); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName); return info; } //授权会被 shiro 回调的方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //1. 从 PrincipalCollection 中来获取登录用户的信息 Object principal = principals.getPrimaryPrincipal(); UserLoginBean userLoginBean = (UserLoginBean)principal; UserAdmin userAdmin = userLoginBean.getUserAdmin(); //2. 利用登录的用户的信息来用户当前用户的角色和权限(可能需要查询数据库) String roleCode = userAdmin.getRoleCode(); //超级管理员拥有所有权限 Set<String> permissionSet = null; boolean isSuperAdmin = Const.SUPER_ADMIN_ROLE_CODE.equals(roleCode); if(isSuperAdmin){ permissionSet = iMenuSysService.getAllPermissionCodes(); }else{ permissionSet = iMenuSysService.getPermissionCodes(roleCode); } Set<String> roles = new HashSet<String>(); roles.add(roleCode); //3. 创建 SimpleAuthorizationInfo, 并设置其 roles 属性和 permissions 属性. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); info.addStringPermissions(permissionSet); //4. 返回 SimpleAuthorizationInfo 对象. return info; } }
CustomRolesAuthorizationFilter
package com.wqqd.findfocus.shiro; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** * @author kexiang.bao * @create 2018-09-19 20:26 */ public class CustomRolesAuthorizationFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception { Subject subject = getSubject(req, resp); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //没有角色限制,有权限访问 return true; } for (int i = 0; i < rolesArray.length; i++) { if (subject.hasRole(rolesArray[i])) { //若当前用户是rolesArray中的任何一个,则有权限访问 return true; } } return false; } }
FilterChainDefinitionMapBuilder
package com.wqqd.findfocus.shiro.factory; /** * @author kexiang.bao * @create 2018-09-19 11:46 */ import com.wqqd.findfocus.service.IMenuSysService; import org.springframework.beans.factory.annotation.Autowired; import java.util.LinkedHashMap; public class FilterChainDefinitionMapBuilder { @Autowired IMenuSysService iMenuSysService; public LinkedHashMap<String, String> buildFilterChainDefinitionMap(){ LinkedHashMap<String, String> map = new LinkedHashMap<>(); map.put("/index.do", "anon"); map.put("/validateMsgSend.do","anon"); map.put("/login.do","anon"); map.put("/logout.do","logout"); map.put("/static/**","anon"); map.putAll(iMenuSysService.getRoleMenuMap()); map.put("/**", "authc"); return map; } }
至此就基本结束了。但是还有一个小坑。。就是当ajax请求如果超时或者没有权限的时候,需要前后端稍微处理一下。
@RequestMapping(value = "/index.do") public String jsp(HttpServletRequest request,HttpServletResponse response){ if (SecurityUtils.getSubject().isAuthenticated()){ return "redirect:/welcome.do"; } boolean isAjax = isAjaxRequest(request); if(isAjax) { response.setHeader("Session-Status", "timeout"); } return "login"; } private boolean isAjaxRequest(HttpServletRequest request){ Enumeration<String> values = request.getHeaders("X-Requested-With"); while(values.hasMoreElements()) { String value = values.nextElement(); if("XMLHttpRequest".equalsIgnoreCase(value)) { return true; } } return false; } @RequestMapping(value = "/unauthorized.do") @ResponseBody public ServerResponse<String> unauthorized(HttpServletRequest request,HttpServletResponse response){ boolean isAjax = isAjaxRequest(request); if(isAjax) { response.setHeader("Access-Status", "denied"); } return ServerResponse.createByErrorMessage("您无权进行该操作"); }
前端加上
$(document).ajaxComplete(function(event, xhr, settings) { if (xhr.getResponseHeader('Session-Status') == 'timeout') { alert("您长时间未操作,请重新登录"); window.location.reload(); } else if(xhr.getResponseHeader('Access-Status') == 'denied'){ alert("您没有权限进行该操作!请联系管理员"); window.location.reload(); }else if(403 == xhr.status) { window.location.reload(); } });
至此就应该结束了,有问题再回来补充。