使用shiro-redis 認(rèn)證和授權(quán)

shiro 對(duì)標(biāo)spring security簇宽,但從api上來講,相對(duì)比security簡(jiǎn)單吧享,學(xué)習(xí)成本較低钞它。

注意點(diǎn):
1须揣、shiro 底層仍要依賴servlet,不能與springcloud gateway集成(webflux模式疯汁,與servlet有沖突)

1幌蚊、增加maven依賴

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- shiro-redis緩存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.3.1</version>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.github.jedis-lock</groupId>
            <artifactId>jedis-lock</artifactId>
            <version>1.0.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2蜒简、兩個(gè)關(guān)鍵類ShiroConfig搓茬、AuthorizingRealm

(1)ShiroConfig :接入者進(jìn)行代碼編寫卷仑,實(shí)現(xiàn)訪問過濾器Filter(匿名訪問控制锡凝、登錄頁窜锯、成功后的首頁等)锚扎、授權(quán)管理器WebSecurityManager尸饺、緩存管理器(shiro-redis開源插件)助币,完成Shiro的配置
(2)自定義Realm迹栓,繼承AuthorizingRealm克伊,完成訪問授權(quán)和登錄認(rèn)證
doGetAuthenticationInfo:登錄認(rèn)證愿吹,查詢用戶犁跪、校驗(yàn)
doGetAuthorizationInfo:權(quán)限信息認(rèn)證(包括角色以及權(quán)限)是用戶訪問controller的時(shí)候才進(jìn)行驗(yàn)證(redis存儲(chǔ)的此處權(quán)限信息)

ShiroConfig

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
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.DefaultWebSecurityManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.shiro.filters.CustomShiroFilterFactoryBean;
import org.jeecg.config.shiro.filters.JwtFilter;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.util.StringUtils;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.*;


@Slf4j
@Configuration
public class ShiroConfig {

    @Value("${jeecg.shiro.excludeUrls}")
    private String excludeUrls;
    @Resource
    LettuceConnectionFactory lettuceConnectionFactory;
    @Autowired
    private Environment env;


    /**
     * Filter Chain定義說明
     *
     * 1、一個(gè)URL可以配置多個(gè)Filter枫耳,使用逗號(hào)分隔
     * 2迁杨、當(dāng)設(shè)置多個(gè)過濾器時(shí),全部驗(yàn)證通過扔役,才視為通過
     * 3、部分過濾器可指定參數(shù)预皇,如perms,roles
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        CustomShiroFilterFactoryBean shiroFilterFactoryBean = new CustomShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 攔截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        if(oConvertUtils.isNotEmpty(excludeUrls)){
            String[] permissionUrl = excludeUrls.split(",");
            for(String url : permissionUrl){
                filterChainDefinitionMap.put(url,"anon");
            }
        }
        // 配置不會(huì)被攔截的鏈接 順序判斷
        filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas驗(yàn)證登錄
        filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登錄驗(yàn)證碼接口排除
        filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登錄驗(yàn)證碼接口排除
        filterChainDefinitionMap.put("/sys/login", "anon"); //登錄接口排除
        filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登錄接口排除
        filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
        filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登錄
        filterChainDefinitionMap.put("/sys/getEncryptedString", "anon"); //獲取加密串
        filterChainDefinitionMap.put("/sys/sms", "anon");//短信驗(yàn)證碼
        filterChainDefinitionMap.put("/sys/phoneLogin", "anon");//手機(jī)登錄
        filterChainDefinitionMap.put("/sys/user/checkOnlyUser", "anon");//校驗(yàn)用戶是否存在
        filterChainDefinitionMap.put("/sys/user/register", "anon");//用戶注冊(cè)
        filterChainDefinitionMap.put("/sys/user/querySysUser", "anon");//根據(jù)手機(jī)號(hào)獲取用戶信息
        filterChainDefinitionMap.put("/sys/user/phoneVerification", "anon");//用戶忘記密碼驗(yàn)證手機(jī)號(hào)
        filterChainDefinitionMap.put("/sys/user/passwordChange", "anon");//用戶更改密碼
        filterChainDefinitionMap.put("/auth/2step-code", "anon");//登錄驗(yàn)證碼
        filterChainDefinitionMap.put("/sys/common/static/**", "anon");//圖片預(yù)覽 &下載文件不限制token
        filterChainDefinitionMap.put("/sys/common/pdf/**", "anon");//pdf預(yù)覽
        filterChainDefinitionMap.put("/generic/**", "anon");//pdf預(yù)覽需要文件
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");
        filterChainDefinitionMap.put("/**/*.js", "anon");
        filterChainDefinitionMap.put("/**/*.css", "anon");
        filterChainDefinitionMap.put("/**/*.html", "anon");
        filterChainDefinitionMap.put("/**/*.svg", "anon");
        filterChainDefinitionMap.put("/**/*.pdf", "anon");
        filterChainDefinitionMap.put("/**/*.jpg", "anon");
        filterChainDefinitionMap.put("/**/*.png", "anon");
        filterChainDefinitionMap.put("/**/*.ico", "anon");

        filterChainDefinitionMap.put("/**/*.ttf", "anon");
        filterChainDefinitionMap.put("/**/*.woff", "anon");
        filterChainDefinitionMap.put("/**/*.woff2", "anon");

        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");


        // update-begin--Author:sunjianlei Date:20210510 for:排除消息通告查看詳情頁面(用于第三方APP)
        filterChainDefinitionMap.put("/sys/annountCement/show/**", "anon");
        // update-end--Author:sunjianlei Date:20210510 for:排除消息通告查看詳情頁面(用于第三方APP)

        //積木報(bào)表排除
        filterChainDefinitionMap.put("/jmreport/**", "anon");
        filterChainDefinitionMap.put("/**/*.js.map", "anon");
        filterChainDefinitionMap.put("/**/*.css.map", "anon");
        //大屏設(shè)計(jì)器排除
        filterChainDefinitionMap.put("/bigscreen/**", "anon");

        //測(cè)試示例
        filterChainDefinitionMap.put("/test/bigScreen/**", "anon"); //大屏模板例子
        //filterChainDefinitionMap.put("/test/jeecgDemo/rabbitMqClientTest/**", "anon"); //MQ測(cè)試
        //filterChainDefinitionMap.put("/test/jeecgDemo/html", "anon"); //模板頁面
        //filterChainDefinitionMap.put("/test/jeecgDemo/redis/**", "anon"); //redis測(cè)試

        //websocket排除
        filterChainDefinitionMap.put("/websocket/**", "anon");//系統(tǒng)通知和公告
        filterChainDefinitionMap.put("/newsWebsocket/**", "anon");//CMS模塊
        filterChainDefinitionMap.put("/vxeSocket/**", "anon");//JVxeTable無痕刷新示例


        //性能監(jiān)控  TODO 存在安全漏洞泄露TOEKN(durid連接池也有)
        filterChainDefinitionMap.put("/actuator/**", "anon");

        // 添加自己的過濾器并且取名為jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        //如果cloudServer為空 則說明是單體 需要加載跨域配置
        Object cloudServer = env.getProperty(CommonConstant.CLOUD_SERVER_KEY);
        filterMap.put("jwt", new JwtFilter(cloudServer==null));
        shiroFilterFactoryBean.setFilters(filterMap);
        // <!-- 過濾鏈定義,從上向下順序執(zhí)行治唤,一般將/**放在最為下邊
        filterChainDefinitionMap.put("/**", "jwt");

        // 未授權(quán)界面返回JSON
        shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
        shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);

        /*
         * 關(guān)閉shiro自帶的session宾添,詳情見文檔
         * http://shiro.apache.org/session-management.html#SessionManagement-
         * StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        //自定義緩存實(shí)現(xiàn),使用redis
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    /**
     * 下面的代碼是添加注解支持
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        /**
         * 解決重復(fù)代理問題 github#994
         * 添加前綴判斷 不匹配 任何Advisor
         */
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        defaultAdvisorAutoProxyCreator.setAdvisorBeanNamePrefix("_no_advisor");
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * cacheManager 緩存 redis實(shí)現(xiàn)
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisCacheManager redisCacheManager() {
        log.info("===============(1)創(chuàng)建緩存管理器RedisCacheManager");
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        //redis中針對(duì)不同用戶緩存(此處的id需要對(duì)應(yīng)user實(shí)體中的id字段,用于唯一標(biāo)識(shí))
        redisCacheManager.setPrincipalIdFieldName("id");
        //用戶權(quán)限信息緩存時(shí)間
        redisCacheManager.setExpire(200000);
        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    @Bean
    public IRedisManager redisManager() {
        log.info("===============(2)創(chuàng)建RedisManager,連接Redis..");
        IRedisManager manager;
        // redis 單機(jī)支持疙挺,在集群為空铐然,或者集群無機(jī)器時(shí)候使用 add by jzyadmin@163.com
        if (lettuceConnectionFactory.getClusterConfiguration() == null || lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().isEmpty()) {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(lettuceConnectionFactory.getHostName());
            redisManager.setPort(lettuceConnectionFactory.getPort());
            redisManager.setDatabase(lettuceConnectionFactory.getDatabase());
            redisManager.setTimeout(0);
            if (!StringUtils.isEmpty(lettuceConnectionFactory.getPassword())) {
                redisManager.setPassword(lettuceConnectionFactory.getPassword());
            }
            manager = redisManager;
        }else{
            // redis集群支持,優(yōu)先使用集群配置
            RedisClusterManager redisManager = new RedisClusterManager();
            Set<HostAndPort> portSet = new HashSet<>();
            lettuceConnectionFactory.getClusterConfiguration().getClusterNodes().forEach(node -> portSet.add(new HostAndPort(node.getHost() , node.getPort())));
            //update-begin--Author:scott Date:20210531 for:修改集群模式下未設(shè)置redis密碼的bug issues/I3QNIC
            if (oConvertUtils.isNotEmpty(lettuceConnectionFactory.getPassword())) {
                JedisCluster jedisCluster = new JedisCluster(portSet, 2000, 2000, 5,
                    lettuceConnectionFactory.getPassword(), new GenericObjectPoolConfig());
                redisManager.setPassword(lettuceConnectionFactory.getPassword());
                redisManager.setJedisCluster(jedisCluster);
            } else {
                JedisCluster jedisCluster = new JedisCluster(portSet);
                redisManager.setJedisCluster(jedisCluster);
            }
            //update-end--Author:scott Date:20210531 for:修改集群模式下未設(shè)置redis密碼的bug issues/I3QNIC
            manager = redisManager;
        }
        return manager;
    }

}

ShiroRealm

import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.jeecg.common.api.CommonAPI;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;

@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
    @Lazy
    @Resource
    private CommonAPI commonAPI;

    @Lazy
    @Resource
    private RedisUtil redisUtil;

    /**
     * 必須重寫此方法,不然Shiro會(huì)報(bào)錯(cuò)
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 權(quán)限信息認(rèn)證(包括角色以及權(quán)限)是用戶訪問controller的時(shí)候才進(jìn)行驗(yàn)證(redis存儲(chǔ)的此處權(quán)限信息)
     * 觸發(fā)檢測(cè)用戶權(quán)限時(shí)才會(huì)調(diào)用此方法樟氢,例如checkRole,checkPermission
     *
     * @param principals 身份信息
     * @return AuthorizationInfo 權(quán)限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("===============Shiro權(quán)限認(rèn)證開始============ [ roles埠啃、permissions]==========");
        String username = null;
        if (principals != null) {
            LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
            username = sysUser.getUsername();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 設(shè)置用戶擁有的角色集合毅该,比如“admin,test”
        Set<String> roleSet = commonAPI.queryUserRoles(username);
        System.out.println(roleSet.toString());
        info.setRoles(roleSet);

        // 設(shè)置用戶擁有的權(quán)限集合眶掌,比如“sys:role:add,sys:user:add”
        Set<String> permissionSet = commonAPI.queryUserAuths(username);
        info.addStringPermissions(permissionSet);
        System.out.println(permissionSet);
        log.info("===============Shiro權(quán)限認(rèn)證成功==============");
        return info;
    }

    /**
     * 用戶信息認(rèn)證是在用戶進(jìn)行登錄的時(shí)候進(jìn)行驗(yàn)證(不存redis)
     * 也就是說驗(yàn)證用戶輸入的賬號(hào)和密碼是否正確朴爬,錯(cuò)誤拋出異常
     *
     * @param auth 用戶登錄的賬號(hào)密碼信息
     * @return 返回封裝了用戶信息的 AuthenticationInfo 實(shí)例
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        log.debug("===============Shiro身份認(rèn)證開始============doGetAuthenticationInfo==========");
        String token = (String) auth.getCredentials();
        if (token == null) {
            log.info("————————身份認(rèn)證失敗——————————IP地址:  "+ oConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
            throw new AuthenticationException("token為空!");
        }
        // 校驗(yàn)token有效性
        LoginUser loginUser = this.checkUserTokenIsEffect(token);
        return new SimpleAuthenticationInfo(loginUser, token, getName());
    }

    /**
     * 校驗(yàn)token的有效性
     *
     * @param token
     */
    public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
        // 解密獲得username,用于和數(shù)據(jù)庫進(jìn)行對(duì)比
        String username = JwtUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException("token非法無效!");
        }

        // 查詢用戶信息
        log.debug("———校驗(yàn)token是否有效————checkUserTokenIsEffect——————— "+ token);
        LoginUser loginUser = commonAPI.getUserByName(username);
        if (loginUser == null) {
            throw new AuthenticationException("用戶不存在!");
        }
        // 判斷用戶狀態(tài)
        if (loginUser.getStatus() != 1) {
            throw new AuthenticationException("賬號(hào)已被鎖定,請(qǐng)聯(lián)系管理員!");
        }
        // 校驗(yàn)token是否超時(shí)失效 & 或者賬號(hào)密碼是否錯(cuò)誤
        if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
            throw new AuthenticationException("Token失效逸爵,請(qǐng)重新登錄!");
        }

        return loginUser;
    }

    /**
     * JWTToken刷新生命周期 (實(shí)現(xiàn): 用戶在線操作不掉線功能)
     * 1痊银、登錄成功后將用戶的JWT生成的Token作為k溯革、v存儲(chǔ)到cache緩存里面(這時(shí)候k致稀、v值一樣),緩存有效期設(shè)置為Jwt有效時(shí)間的2倍
     * 2萎攒、當(dāng)該用戶再次請(qǐng)求時(shí)矛绘,通過JWTFilter層層校驗(yàn)之后會(huì)進(jìn)入到doGetAuthenticationInfo進(jìn)行身份驗(yàn)證
     * 3耍休、當(dāng)該用戶這次請(qǐng)求jwt生成的token值已經(jīng)超時(shí),但該token對(duì)應(yīng)cache中的k還是存在货矮,則表示該用戶一直在操作只是JWT的token失效了羊精,程序會(huì)給token對(duì)應(yīng)的k映射的v值重新生成JWTToken并覆蓋v值,該緩存生命周期重新計(jì)算
     * 4囚玫、當(dāng)該用戶這次請(qǐng)求jwt在生成的token值已經(jīng)超時(shí)喧锦,并在cache中不存在對(duì)應(yīng)的k读规,則表示該用戶賬戶空閑超時(shí),返回用戶信息已失效燃少,請(qǐng)重新登錄阵具。
     * 注意: 前端請(qǐng)求Header中設(shè)置Authorization保持不變,校驗(yàn)有效性以緩存中的token為準(zhǔn)。
     *       用戶過期時(shí)間 = Jwt有效時(shí)間 * 2越庇。
     *
     * @param userName
     * @param passWord
     * @return
     */
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
        if (oConvertUtils.isNotEmpty(cacheToken)) {
            // 校驗(yàn)token有效性
            if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                String newAuthorization = JwtUtil.sign(userName, passWord);
                // 設(shè)置超時(shí)時(shí)間
                redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
                redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
                log.debug("——————————用戶在線操作,更新token保證不掉線—————————jwtTokenRefresh——————— "+ token);
            }
            return true;
        }
        return false;
    }

    /**
     * 清除當(dāng)前用戶的權(quán)限認(rèn)證緩存
     *
     * @param principals 權(quán)限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

}

完成第二點(diǎn)操作后,就可以使用shiro了。如果需要自定義的攔截器,請(qǐng)看第三點(diǎn)

3兑徘、攔截器介紹

3.1 shiro中自帶的攔截器如下
認(rèn)攔截器類型 攔截器類 說明(括號(hào)里的表示默認(rèn)值)
身份驗(yàn)證相關(guān)的
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 基于表單的攔截器;如“/**=authc”,如果沒有登錄會(huì)跳到相應(yīng)的登錄頁面登錄锈麸;主要屬性:usernameParam:表單提交的用戶名參數(shù)名( username); passwordParam:表單提交的密碼參數(shù)名(password); rememberMeParam:表單提交的密碼參數(shù)名(rememberMe); loginUrl:登錄頁面地址(/login.jsp)但荤;successUrl:登錄成功后的默認(rèn)重定向地址南蓬; failureKeyAttribute:登錄失敗后錯(cuò)誤信息存儲(chǔ)key(shiroLoginFailure)
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter Basic HTTP身份驗(yàn)證攔截器,主要屬性: applicationName:彈出登錄框顯示的信息(application)
logout org.apache.shiro.web.filter.authc.LogoutFilter 退出攔截器,主要屬性:redirectUrl:退出成功后重定向的地址(/)
user org.apache.shiro.web.filter.authc.UserFilter 用戶攔截器鳖悠,用戶已經(jīng)身份驗(yàn)證/記住我登錄的都可卡辰;
anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名攔截器萌朱,即不需要登錄即可訪問;一般用于靜態(tài)資源過濾;示例“/static/**=anon”
授權(quán)相關(guān)的
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色授權(quán)攔截器,驗(yàn)證用戶是否擁有所有角色;主要屬性: loginUrl:登錄頁面地址(/login.jsp);unauthorizedUrl:未授權(quán)后重定向的地址;示例“/admin/**=roles[admin]”
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 權(quán)限授權(quán)攔截器,驗(yàn)證用戶是否擁有所有權(quán)限;屬性和roles一樣牡辽;示例“/user/**=perms["user:create"]”
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter rest風(fēng)格攔截器挺尿,自動(dòng)根據(jù)請(qǐng)求方法構(gòu)建權(quán)限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)構(gòu)建權(quán)限字符串熟史;示例“/users=rest[user]”馁害,會(huì)自動(dòng)拼出“user:read,user:create,user:update,user:delete”權(quán)限字符串進(jìn)行權(quán)限匹配(所有都得匹配昆烁,isPermittedAll)
port org.apache.shiro.web.filter.authz.PortFilter 端口攔截器,主要屬性:port(80):可以通過的端口;示例“/test= port[80]”校读,如果用戶訪問該頁面是非80轧膘,將自動(dòng)將請(qǐng)求端口改為80并重定向到該80端口太援,其他路徑/參數(shù)等都一樣
ssl org.apache.shiro.web.filter.authz.SslFilter SSL攔截器碱蒙,只有請(qǐng)求協(xié)議是https才能通過后雷;否則自動(dòng)跳轉(zhuǎn)會(huì)https端口(443)藕筋;其他和port攔截器一樣
3.2 shiro中使用自定義的攔截器

創(chuàng)建類,集成AccessControlFilter梳码,分別重寫isAccessAllowed和onAccessDenied方法

package org.jeecg.config.shiro.filters;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author Scott
 * @create 2019-02-01 15:56
 * @desc 鑒權(quán)請(qǐng)求URL訪問權(quán)限攔截器
 */
@Slf4j
public class ResourceCheckFilter extends AccessControlFilter {

    private String errorUrl;

    public String getErrorUrl() {
        return errorUrl;
    }

    public void setErrorUrl(String errorUrl) {
        this.errorUrl = errorUrl;
    }

    /**
     * 表示是否允許訪問 ,如果允許訪問返回true符匾,否則false;
     *
     * @param servletRequest
     * @param servletResponse
     * @param o               表示寫在攔截器中括號(hào)里面的字符串 mappedValue 就是 [urls] 配置中攔截器參數(shù)部分
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = getSubject(servletRequest, servletResponse);
        String url = getPathWithinApplication(servletRequest);
        log.info("當(dāng)前用戶正在訪問的 url => " + url);
        return subject.isPermitted(url);
    }

    /**
     * onAccessDenied:表示當(dāng)訪問拒絕時(shí)是否已經(jīng)處理了; 如果返回 true 表示需要繼續(xù)處理; 如果返回 false
     * 表示該攔截器實(shí)例已經(jīng)處理了,將直接返回即可撤卢。
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        log.info("當(dāng) isAccessAllowed 返回 false 的時(shí)候考赛,才會(huì)執(zhí)行 method onAccessDenied ");

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.sendRedirect(request.getContextPath() + this.errorUrl);

        // 返回 false 表示已經(jīng)處理惕澎,例如頁面跳轉(zhuǎn)啥的,表示不在走以下的攔截器了(如果還有配置的話)
        return false;
    }

}

在ShiroConfig中添加自己的過濾器颜骤,例如:

// 添加自己的過濾器并且取名為jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        //如果cloudServer為空 則說明是單體 需要加載跨域配置
        Object cloudServer = env.getProperty(CommonConstant.CLOUD_SERVER_KEY);
        filterMap.put("jwt", new JwtFilter(cloudServer==null));
        // 添加第二個(gè)
        filterMap.put("resource",new ResourceCheckFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        // <!-- 過濾鏈定義唧喉,從上向下順序執(zhí)行,一般將/**放在最為下邊
        // 最關(guān)鍵的代碼忍抽,否則自定義的過濾器不會(huì)被執(zhí)行八孝,當(dāng)有多個(gè)時(shí),使用逗號(hào)分隔
        filterChainDefinitionMap.put("/**", "jwt,resource");

附:
shiro的源碼摘自:https://github.com/jeecgboot/jeecg-boot

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸠项,一起剝皮案震驚了整個(gè)濱河市干跛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祟绊,老刑警劉巖楼入,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哥捕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浅辙,警方通過查閱死者的電腦和手機(jī)扭弧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來记舆,“玉大人鸽捻,你說我怎么就攤上這事≡笕” “怎么了御蒲?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)诊赊。 經(jīng)常有香客問我厚满,道長(zhǎng),這世上最難降的妖魔是什么碧磅? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任碘箍,我火速辦了婚禮,結(jié)果婚禮上鲸郊,老公的妹妹穿的比我還像新娘丰榴。我一直安慰自己,他們只是感情好秆撮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布四濒。 她就那樣靜靜地躺著,像睡著了一般职辨。 火紅的嫁衣襯著肌膚如雪盗蟆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天舒裤,我揣著相機(jī)與錄音喳资,去河邊找鬼。 笑死腾供,一個(gè)胖子當(dāng)著我的面吹牛骨饿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播台腥,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宏赘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了黎侈?” 一聲冷哼從身側(cè)響起察署,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峻汉,沒想到半個(gè)月后贴汪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脐往,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年扳埂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了业簿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阳懂,死狀恐怖梅尤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岩调,我是刑警寧澤巷燥,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站号枕,受9級(jí)特大地震影響缰揪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葱淳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一钝腺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赞厕,春花似錦艳狐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喷斋。三九已至唁毒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間星爪,已是汗流浹背浆西。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顽腾,地道東北人近零。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像抄肖,于是被迫代替她去往敵國和親久信。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容