Shiro实际使用(实现各种实用的拦截器)

来源:互联网 发布:mac os x leopard 编辑:程序博客网 时间:2024/05/22 16:57

    目前 shiro基本上属于很火的一个对权限验证的框架 在大系统的使用中 也能够和其他的权限方面的框架进行整合 能够实现单点登录 与redis的集成 实现缓存用户session等,十分灵活 

    今天我就分享下使用了shiro一段时间的体会吧

    首先,对于shiro的介绍 源码 本文就不涉及了 一切以最实用的东西出发

    配置:

<!--shiro配置--><!-- Shiro Filter is defined in the spring application context: --><!--1. 配置  Shiro 的 shiroFilter.2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.--><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>
 这个是在web.xml中的配置 配置shiro的最主要的过滤器 也相当于是入口  其实说入口不是很严谨 如果没有配置的话 实际上调用shiro 的subject进行登录也是可以操作的 但是会一直报一个security的错误。

     然后紧接着再配置spring和shiro的集成

     这里我就贴一个比较全的配置吧 懒得去减下来了 自定义的过滤器应该能看出来

    

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">    <!-- =========================================================         Shiro Core Components - Not Spring Specific         ========================================================= -->    <!-- Shiro's main business-tier object for web-enabled applications         (use DefaultSecurityManager instead when there is no web environment)-->    <!--    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="rememberMeManager" ref="rememberMeManager"/>        <property name="realms">            <list>                <ref bean="ShiroRealm"/>            </list>        </property>       <!-- <property name="rememberMeManager.cookie.maxAge" value="10"></property>-->    </bean>    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->    <bean            class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">        <property name="staticMethod"                  value="org.apache.shiro.SecurityUtils.setSecurityManager" />        <property name="arguments" ref="securityManager" />    </bean>    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->    <!--    2. 配置 CacheManager.    2.1 需要加入 ehcache 的 jar 包及配置文件.    -->    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one             will be creaed with a default config:             <property name="cacheManager" ref="ehCacheManager"/> -->        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want             a specific Ehcache configuration to be used, specify that here.  If you don't, a default             will be used.: -->        <property name="cacheManagerConfigFile" value="classpath:main/resources/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>        </property>    </bean>    <!-- Used by the SecurityManager to access security data (users, roles, etc).         Many other realm implementations can be used too (PropertiesRealm,         LdapRealm, etc. -->    <!--       3. 配置 Realm       3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean    -->    <bean id="ShiroRealm" class="main.java.com.jt.shiro.Realm.ShiroRealm">        <property name="credentialsMatcher">            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">                <property name="hashAlgorithmName" value="MD5"></property>                <property name="hashIterations" value="1024"></property>            </bean>        </property>    </bean>    <!--<bean id="secondRealm" class="">        <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>-->    <!-- =========================================================         Shiro Spring-specific integration         ========================================================= -->    <!-- Post processor that automatically invokes init() and destroy() methods         for Spring-configured Shiro objects so you don't have to         1) specify an init-method and destroy-method attributes for every bean            definition and         2) even know which Shiro objects require these methods to be            called. -->    <!--    4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.    -->    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after         the lifecycleBeanProcessor has run: -->    <!--    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>    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us         to wire things with more control as well utilize nice Spring things such as         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->    <!--自定义LogoutFilter,退出-->    <bean id="logoutFilter" class="main.java.com.jt.shiro.filter.SystemLogoutFilter">    </bean>    <bean id="loginFilter" class="main.java.com.jt.shiro.filter.ShiroLoginFilter">    </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="/login.html"/>        <!--<property name="successUrl" value="/list.jsp"/>-->        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>        <property name="filters">            <map>                <entry key="roles">                    <bean class="main.java.com.jt.shiro.filter.CustomRolesAuthorizationFilter" />                </entry>                <entry key="logout" value-ref="logoutFilter"/>                <entry key="authc" value-ref="loginFilter"/>            </map>        </property>        <!--主过滤器工厂加载配置-->        <!--<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>-->        <!--           配置哪些页面需要受保护.           以及访问这些页面需要的权限.           1). anon 可以被匿名访问           2). authc 必须认证(即登录)后才可能访问的页面.           3). logout 登出.           4). roles 角色过滤器        -->        <!--<property name="filterChainDefinitions">            <value>                /login.jsp = anon                /shiro/login = anon                /shiro/logout = logout                /user.jsp = roles[user]                /admin.jsp = roles[admin]                # everything else requires authentication:                /** = authc            </value>        </property>-->    </bean>    <!--自定义过滤器加载配置文件和配置-->    <bean id="filterChainDefinitionsService"          class="main.java.com.jt.shiro.filter.SimpleFilterChainDefinitionsService">        <property name="definitions">            <value>                /login.html = anon                /shiro/login.do = anon            </value>        </property>        <!--<property name="filterChainMap" ref="filterChainDefinitionMap">        </property>-->    </bean>    <!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 -->   <!-- <bean id="filterChainDefinitionMap"          factory-bean="filterChainDefinitionMapBuilder" factory-method="pushMapForShiro"></bean>    <bean id="filterChainDefinitionMapBuilder"          class="main.java.com.jt.shiro.service.impl.ShiroServiceImpl"></bean>-->    <!-- 会话Cookie模板 -->    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">        <constructor-arg value="sid"/>        <property name="httpOnly" value="true"/>        <property name="maxAge" value="-1"/>    </bean>    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">        <constructor-arg value="rememberMe"/>        <property name="httpOnly" value="true"/>        <property name="maxAge" value="2592000"/><!-- 30天 -->    </bean>    <!-- rememberMe管理器 -->    <bean id="rememberMeManager"          class="org.apache.shiro.web.mgt.CookieRememberMeManager">        <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>        <property name="cookie" ref="rememberMeCookie"/>    </bean>    <bean id="formAuthenticationFilter"          class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">        <property name="rememberMeParam" value="rememberMe"/>    </bean></beans>

 其中的英文注释呢 都是官网下载的shiro配置的说明 然后其中的骨架我也是拿的别人的 然后在上面添加上了血肉

 然后第一步 :

<!--    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="rememberMeManager" ref="rememberMeManager"/>        <property name="realms">            <list>                <ref bean="ShiroRealm"/>            </list>        </property>
 配置好 security 这个是属于shiro的内部大管家 基本上写的东西都是和这个做交互 然后这个security再让其他各个部件去代理 是一种代理模式 

其中第一个cacheManager是配置缓存的 

<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">    <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one         will be creaed with a default config:         <property name="cacheManager" ref="ehCacheManager"/> -->    <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want         a specific Ehcache configuration to be used, specify that here.  If you don't, a default         will be used.: -->    <property name="cacheManagerConfigFile" value="classpath:main/resources/ehcache.xml"/></bean>
最普通的ecache 的缓存 ecache 的配置:

<ehcache updateCheck="false">    <diskStore path="java.io.tmpdir"/>    <defaultCache            maxElementsInMemory="10000"            eternal="false"            timeToIdleSeconds="120"            timeToLiveSeconds="120"            overflowToDisk="true"            maxElementsOnDisk="10000000"            diskPersistent="false"            diskExpiryThreadIntervalSeconds="120"            memoryStoreEvictionPolicy="LRU"            /></ehcache>

然后就是authorticator 这个是认证的策略 这个项目没有改变 因为只使用了一个shiro 的realm进行认证(后文会再说)

配置:

<bean id="authenticator"      class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">    <property name="authenticationStrategy">        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>    </property></bean>
第三个是记住我的登录管理器 这个后文再说

最后一个就是配置shiro 的realm 有多个realm验证的话就是这样写成list 和spring中加载多个配置文件的写法差不多 

配置:

<!--       3. 配置 Realm       3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean    -->    <bean id="ShiroRealm" class="main.java.com.jt.shiro.Realm.ShiroRealm">        <property name="credentialsMatcher">            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">                <property name="hashAlgorithmName" value="MD5"></property>                <property name="hashIterations" value="1024"></property>            </bean>        </property>    </bean>
其中,HashedCreadentialsMatcher是加密的模式 shiro内部有很多的加密策略 现在多数都用的这个 然后md5就是md5的加密 后面的1024是加密的次数 就是对密码md5加密再加密这个已经加密的 一共1024次 这样常见的密码这样加密以后也比较复杂了(加密后的密码长度不会变化很多)

 配置好这个security后:

后面两个配置 一般是用不上的 ,然后实现的过滤器操作后文再细说 

所以现在跳过配置  进入下一步

一般一个系统对于用户的入口就是登录 于是我们跟随这个思路往下继续进行 

登录入口(控制层):

public class ShiroController {    @Autowired    private LoginService loginService;//拿放session里的东西的接口    @Autowired    private ShiroService shiroService;//这里暂时没有用到    @RequestMapping(value = "/login",produces="application/json;charset=UTF-8")    @ResponseBody    public Map loginByShiro(String username , String password , HttpServletRequest request){        HashMap map = new HashMap();        Subject crrutenUser = SecurityUtils.getSubject();        if (!crrutenUser.isAuthenticated()){//判断当前进行交互的用户是否已经通过验证            UsernamePasswordToken token = new UsernamePasswordToken(username,password);//将用户信息封装为一个token            try {                crrutenUser.login(token);//进行登录 然后将令牌传播到Realm                map.put("status",true);                HttpSession session = request.getSession();                HashMap sessionMap = new HashMap();                sessionMap.put("yhid",loginService.selectYh(username).getYhid());                sessionMap.put("jsid",loginService.selectYh(username).getJsid());                session.setAttribute("login",sessionMap);                System.out.println(sessionMap);            }catch (AuthenticationException ae){//shiro所有登录异常的父类                System.out.println(ae);                map.put("status",ae);            }        }else {//已经通过验证            map.put("status",true);        }        return map;    }
说明 用户进行的交互都是subject 这个相当于是shiro的门面 通过security获取subject进行操作,然后生成一个令牌 这个令牌会传播到realm去 然后获得的异常没有区分 其中有很多异常分类 能够得到不同的信息 有需要的可以去看一眼 十秒就搞定了 但是建议暴露出的异常不要过于细节 安全一点

接下来验证(realm):

/** 验证类 * Created by 53444 on 2017/11/13. */public class ShiroRealm extends AuthorizingRealm{    @Autowired    private loginMapper loginMapper;    @Autowired    private  JsMapper jsMapper;    /**     * 授权方法     * @param principalCollection     * @return     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {        //1. 从 PrincipalCollection 中来获取登录用户的信息        Object principal = principalCollection.getPrimaryPrincipal();        //2. 利用登录的用户的信息来用户当前用户的角色或权限        Set<String> roles = new HashSet<>();        YhEntity yhEntity= loginMapper.readByShiro(principal.toString());        String jsid = yhEntity.getJsid();        roles.add(jsMapper.getById(jsid).getMc());        //3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);        //4. 返回 SimpleAuthorizationInfo 对象.        return info;    }    /**     * 认证方法     * @param token     * @return     * @throws AuthenticationException     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {        /**         * 将传播过来的令牌强转类型         */        UsernamePasswordToken upToken = (UsernamePasswordToken) token;        /**         * 取令牌中的数据         */        String usernamebyinput = upToken.getUsername();        /**         * 取数据库中对应数据         */        YhEntity yhEntity = loginMapper.readByShiro(usernamebyinput);        if (yhEntity==null){            throw new UnknownAccountException("未知用户");        }        else if ("lock".equals(yhEntity.getMs())){            throw new LockedAccountException("用户被锁定");        }        /**         * 认证实体信息         */        Object principal = yhEntity.getUsername();        /**         * 密码         */        Object hashedCredentials = yhEntity.getPassword();        /**         * 父类名称         */        String realmName = getName();        /**         * 盐值         */        ByteSource credentialsSalt = ByteSource.Util.bytes(principal);        SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);        info = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);        return info;    }    public static void main(String[] args) {        String hashAlgorithmName = "MD5";        Object credentials = "123456";        Object salt = ByteSource.Util.bytes("test");        int hashIterations = 1024;        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);        System.out.println(result);    }}
现在自定义的realm都是去继承这个抽象类 这个抽象类再去实现顶层的接口 这是一种缺省适配模式 这个设计的思想后文的动态生成权限中有涉及

然后回归正题 刚登陆生成的令牌 现在是传播到了认证的方法 

方法中的逻辑很简单。就是通过传过来的username去数据库中找对应的用户 如果没找到就抛出异常 如果有其他的描述再抛出相应的异常 

最后以用户的名称作为盐值 加上md5加密 验证密码 验证的过程封装到了那个simple认证信息中 这个比较简单,也是目前多数情况的使用,然后后面写了一个主方法 用来看密码的 在不使用Junit的情况下 算是比较实用的了。

ok ,认证成功以后就会返回认证的信息到刚才的controller中 如果没有捕获到异常就是验证成功了 就可以返回你想返回的信息到前端了 然后再实现跳转什么的 如果是ajax呢 就是建议在前端用js进行跳转页面 不然很麻烦 后面也有关于shiro加ajax的各种跳转 

 这个地方需要注意的一点  如果已经登录认证成功了 点击网页的后退键 返回到了登录页面 再进行登录 这个时候是直接认证成功的 这就是ecache的缓存在起作用了 所以就算你退回去登录其他人的账号 也是进的刚才认证成功的那个人的账号 需要退出的话就要实现shiro 的退出过滤器了。后面会讲到 

所以这样 shiro的登录就差不多了 如果有很复杂的认证登录 那么就需要去实现开始的配置中的

bean id="authenticator"      class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">    <property name="authenticationStrategy">        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>    </property></bean>
然后重写他 再贴上你自己的路径 和实现类  这个也不难 百度后一大把 

然后重点就在讲权限的控制,最开始是为什么想要做这个东西 因为现在的架构来讲 只能把用户分配给角色 然后角色和功能挂钩 然后登陆成功以后 根据用户的角色去加载角色有的功能菜单 那么这样用户能看见的就是自己的已经被分配的东西 那么如果用户知道其他的url 那么也能够访问 这就是不足的地方 

所以现在呢 我们就针对这一点进行控制:

首先,你的数据库中一定是有用户和角色的关联 角色和功能的关联的 那么我们控制的地方就在角色和功能的关联上:

如果是默认的权限配置就是这个样子(被注释掉的部分):

<!--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="/login.html"/>    <!--<property name="successUrl" value="/list.jsp"/>-->    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>    <property name="filters">        <map>            <entry key="roles">                <bean class="main.java.com.jt.shiro.filter.CustomRolesAuthorizationFilter" />            </entry>            <entry key="logout" value-ref="logoutFilter"/>            <entry key="authc" value-ref="loginFilter"/>        </map>    </property>    <!--主过滤器工厂加载配置-->    <!--<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>-->    <!--       配置哪些页面需要受保护.       以及访问这些页面需要的权限.       1). anon 可以被匿名访问       2). authc 必须认证(即登录)后才可能访问的页面.       3). logout 登出.       4). roles 角色过滤器    -->    <!--<property name="filterChainDefinitions">        <value>            /login.jsp = anon            /shiro/login = anon            /shiro/logout = logout            /user.jsp = roles[user]            /admin.jsp = roles[admin]            # everything else requires authentication:            /** = authc        </value>    </property>--></bean>

通过一种通配的方式,这种方式也可行 在一些功能目录很明确的系统上是能够行得通的 但是就是控制很麻烦 没有办法动态管理 而且修改都得重启项目 改变代码 。而上面没有被注释的地方就是我们自定义实现的过滤器(有一部分是)这里就是shiro的主过滤器了 

然后这个过滤器的作用是什么呢 就是当你要访问一个资源 shiro的过滤器会拿到这个资源的信息 然后检查过滤器 看是否命中 如果命中 再根据命中资源的所属的过滤器去进行相应的处理。

就是在你进入过滤到的资源的时候 会触发realm中的授权方法 :

/** * 授权方法 * @param principalCollection * @return */@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {    //1. 从 PrincipalCollection 中来获取登录用户的信息    Object principal = principalCollection.getPrimaryPrincipal();    //2. 利用登录的用户的信息来用户当前用户的角色或权限    Set<String> roles = new HashSet<>();    YhEntity yhEntity= loginMapper.readByShiro(principal.toString());//查角色    String jsid = yhEntity.getJsid();//查角色    roles.add(jsMapper.getById(jsid).getMc());//放角色    //3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);    //4. 返回 SimpleAuthorizationInfo 对象.    return info;}
这里的授权方法 只是针对了角色进行管理 一般的系统差不多这样就足够了,拿到角色后

/user.jsp = roles[user]            /admin.jsp = roles[admin]
看角色过滤器 roles 中是否有包含拿到的这个角色 如果有就通过 没有就到你配置了没有权限转到的页面:

<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
这里的默认角色过滤器的实现 是如果有多个角色roles【1,2,3】这三个角色需要同时拥有才可以通过 ,这个一般不符合需求 很多时候都是只要有其中一个角色就ok了,所以这个时候我们要重写roles这个过滤器:

<entry key="roles">                <bean class="main.java.com.jt.shiro.filter.CustomRolesAuthorizationFilter" />            </entry>
过滤器代码:

// AuthorizationFilter抽象类事项了javax.servlet.Filter接口,它是个过滤器。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;    }}
这样,角色过滤器就搞定了 ,当你有其中任何一个角色的时候就可以进入了

这个时候 如果是默认的实现过滤器的配置(被注释那段)发现不好用 于是我采用了另一种配置 将权限的配置写在一个工厂方法中 然后再给shiro:

<!-- 配置一个 bean, 该 bean 实际上是一个 Map. 通过实例工厂方法的方式 --><!-- <bean id="filterChainDefinitionMap"       factory-bean="filterChainDefinitionMapBuilder" factory-method="pushMapForShiro"></bean> <bean id="filterChainDefinitionMapBuilder"       class="main.java.com.jt.shiro.service.impl.ShiroServiceImpl"></bean>-->
这个工厂的实现方法呢 就是从数据库中取出角色和功能的关联 然后拼接成roles[系统管理员,超级系统管理员 ] 这样的形式 这个是根据业务来操作的 所以代码就不贴出了,有需要的再说。最后发现这个办法呢 虽然是在程序中组装好了过滤器链 但是呢 每次的权限更新都需要进行重启项目 因为这个配置是项目启动的时候就加载进去了 不会加载第二次 所以最后也没有采用这种工厂的办法生成过滤器链。

所以我们需要考虑的是 如何动态的实现权限的增删改

<!--自定义过滤器加载配置文件和配置--><bean id="filterChainDefinitionsService"      class="main.java.com.jt.shiro.filter.SimpleFilterChainDefinitionsService">    <property name="definitions">        <value>            /login.html = anon            /shiro/login.do = anon        </value>    </property>    <!--<property name="filterChainMap" ref="filterChainDefinitionMap">    </property>--></bean>
这里我们实现了一个相当于是副过滤器 配置中的shiroFileter是主过滤器 这个副过滤器只需要去实现shiro过滤器的一些方法 那么也就是shiro的过滤器了 :

看代码:

首先顶层接口

public interface FilterChainDefinitionsService {    /*public static final String PREMISSION_STRING = "perms[{0}]"; // 资源结构格式    public static final String ROLE_STRING = "role[{0}]"; // 角色结构格式*/    /** 初始化框架权限资源配置 */     void intiPermission();    /** 重新加载框架权限资源配置 (强制线程同步) */     void updatePermission();    /** 初始化第三方权限资源配置 */     Map<String, String> initOtherPermission();}
然后用一个抽象类去实现这个接口 这样做的好处就是 让需要强行被子类实现的方法不写abstract修饰 不需要所有子类实现的方法写成抽象的 这样就会很灵活 当你改动的时候 不会惊动到所有的子类 这就是接口和抽象类的配合使用 缺省适配模式 

抽象类:

public abstract class AbstractFilterChainDefinitionsService implements FilterChainDefinitionsService {    private final static Logger log = LoggerFactory.getLogger(AbstractFilterChainDefinitionsService.class);    private String definitions = "";    private LinkedHashMap<String,String> filterChainMap = new LinkedHashMap<>();    @Autowired    private ShiroFilterFactoryBean shiroFilterFactoryBean;    @PostConstruct    public void intiPermission() {        shiroFilterFactoryBean.setFilterChainDefinitionMap(obtainPermission());        log.debug("initialize shiro permission success...");    }    public void updatePermission() {        synchronized (shiroFilterFactoryBean) {            AbstractShiroFilter shiroFilter = null;            try {                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();            } catch (Exception e) {                log.error(e.getMessage());            }            // 获取过滤管理器            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter                    .getFilterChainResolver();            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();            // 清空初始权限配置            manager.getFilterChains().clear();            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();            // 重新构建过滤器链            //shiroFilterFactoryBean.setFilterChainDefinitions(definitions);            shiroFilterFactoryBean.setFilterChainDefinitionMap(obtainPermission());            Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();            for (Map.Entry<String, String> entry : chains.entrySet()) {                String url = entry.getKey();                String chainDefinition = entry.getValue().trim().replace(" ", "");                manager.createChain(url, chainDefinition);            }            log.debug("update shiro permission success...");        }    }    /** 读取配置资源 */    private Ini.Section obtainPermission() {        Ini ini = new Ini();        ini.load(definitions); // 加载资源文件节点串        Ini.Section section = ini.getSection("urls"); // 使用默认节点        if (CollectionUtils.isEmpty(section)) {            section = ini.getSection(Ini.DEFAULT_SECTION_NAME); // 如不存在默认节点切割,则使用空字符转换        }        LinkedHashMap<String, String> permissionMap = initOtherPermission();        if (permissionMap != null && !permissionMap.isEmpty()) {            section.putAll(permissionMap);        }        return section;    }    public abstract LinkedHashMap<String, String> initOtherPermission();    public String getDefinitions() {        return definitions;    }    public void setDefinitions(String definitions) {        this.definitions = definitions;    }    public LinkedHashMap<String, String> getFilterChainMap() {        return filterChainMap;    }    public void setFilterChainMap(LinkedHashMap<String, String> filterChainMap) {        this.filterChainMap = filterChainMap;    }}
这个就是实现动态处理权限的核心 注释都写得很清楚了 然后说一下为什么是linkedHashmap 因为这个过滤器链 是有顺序的 从头往后开始执行 所以 如果你的配置中有粒度细的就放在前面 这样命中的时候就是先命中前面的 假如你有一个 /** 的配置 放在了最前面 那么所有的东西都只会匹配到他 不会继续往前走了 所以需要一个有序的键值对的东西 linkedHashMap 

然后其中的读取配置文件的操作 其中有一个读map 这个就写成了一个抽象的方法 让子类去实现 这个方法的实现就是那段没有贴出来的代码 根据业务实现roles【1,2,3】 这样的组装map的代码 ,最后再把这个实现抽闲类的子类给放进spring的ioc中 :

<bean id="filterChainDefinitionsService"      class="main.java.com.jt.shiro.filter.SimpleFilterChainDefinitionsService">    <property name="definitions">        <value>            /login.html = anon            /shiro/login.do = anon        </value>    </property>    <!--<property name="filterChainMap" ref="filterChainDefinitionMap">    </property>--></bean>
其中的value就可以按照这个格式写一些特殊的东西再里面 ,这个是最先加载的 然后再加载了map

这样 副过滤器就实现了 ,我们只需要 在对权限进行了增删改的地方去实现这个接口 

对权限管理的controller中注入这个接口:

@Autowiredprivate FilterChainDefinitionsService filterChainDefinitionsService;
然后在修改完成了权限后 :

filterChainDefinitionsService.updatePermission();//更新权限
这样就可以了。

然后再讲一下shiro配合ajax的重定向问题 

ajax是一个不刷新网页的东西 所以你发出的重定向的请求都是不会做任何响应的 这个时候我们以最常见的登录跳转和退出跳转举例:

登录过滤器:

<bean id="loginFilter" class="main.java.com.jt.shiro.filter.ShiroLoginFilter"></bean>
public class ShiroLoginFilter extends AdviceFilter {    /**     * 在访问controller前判断是否登录,返回json,不进行重定向。     *     * @param request     * @param response     * @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器     * @throws Exception     */    @Override    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        HttpServletResponse httpServletResponse = (HttpServletResponse) response;        Object sysUser = httpServletRequest.getSession().getAttribute("login");        if (null == sysUser && !StringUtils.contains(httpServletRequest.getRequestURI(), "/shiro/login.do")) {            String requestedWith = httpServletRequest.getHeader("X-Requested-With");            if (StringUtils.isNotEmpty(requestedWith) && StringUtils.equals(requestedWith, "XMLHttpRequest")) {//如果是ajax返回指定数据                // 重定向                String path = httpServletRequest.getContextPath();                String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";                System.out.println(path);                System.out.println(basePath);                // ajax请求                httpServletResponse.setHeader("SESSIONSTATUS", "TIMEOUT");                httpServletResponse.setHeader("CONTEXTPATH", basePath + "index.jsp");                httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);//403 禁止                return false;            } else {//不是ajax进行重定向处理                httpServletResponse.sendRedirect("/jt_bid/login.html");                return false;            }        }        return true;    }}
这个操作呢 在我以前的一片文章中 spring登录拦截器对ajax的重定向有介绍 那个比较详细一些 

需要在一个前端公用的js中对ajax的升级函数进行预操作:

$.ajaxSetup( {//设置ajax请求结束后的执行动作    complete :        function(XMLHttpRequest, textStatus) {// 通过XMLHttpRequest取得响应头,sessionstatus            var sessionstatus = XMLHttpRequest.getResponseHeader("sessionstatus");            if (sessionstatus == "TIMEOUT") {                var win = window;                while (win != win.top){                    win = win.top;                }                win.location.href= XMLHttpRequest.getResponseHeader("CONTEXTPATH");            }        }});
退出过滤器一个道理;

<bean id="logoutFilter" class="main.java.com.jt.shiro.filter.SystemLogoutFilter"></bean>
public class SystemLogoutFilter extends LogoutFilter {    @Override    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        //在这里执行退出系统前需要清空的数据        Subject subject=getSubject(request,response);        //String redirectUrl=getRedirectUrl(request,response,subject);        ServletContext context= request.getServletContext();        try {            subject.logout();            context.removeAttribute("error");        }catch (SessionException e){            e.printStackTrace();        }        /*issueRedirect(request,response,redirectUrl);*/        HttpServletRequest httpServletRequest = (HttpServletRequest) request;        HttpServletResponse httpServletResponse = (HttpServletResponse) response;        Object sysUser = httpServletRequest.getSession().getAttribute("login");        if (null == sysUser && !StringUtils.contains(httpServletRequest.getRequestURI(), "/shiro/login.do")) {            HttpSession session = httpServletRequest.getSession();            session.removeAttribute("login");            String requestedWith = httpServletRequest.getHeader("X-Requested-With");            if (StringUtils.isNotEmpty(requestedWith) && StringUtils.equals(requestedWith, "XMLHttpRequest")) {//如果是ajax返回指定数据                // 重定向                String path = httpServletRequest.getContextPath();                String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";                System.out.println(path);                System.out.println(basePath);                // ajax请求                httpServletResponse.setHeader("SESSIONSTATUS", "TIMEOUT");                httpServletResponse.setHeader("CONTEXTPATH", basePath + "index.jsp");                httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);//403 禁止                return false;            } else {//不是ajax进行重定向处理                httpServletResponse.sendRedirect("/jt_bid/login.html");                return false;            }        }        return false;    }}
ok,到这里基本就结束了 以后还会更新一些其他用法






原创粉丝点击