Springboot+Shiro 基於URL 動態控制權限

前言:css

   權限控制有 註解的方式,jsp shiro標籤的方式,還有url 動態控制的方式。這裏我使用最後一種方式來控制權限前端

思路:java

0.利用  PathMatchingFilter 攔截器web

1.根據用戶名 來查詢角色,redis

2.根據角色查詢權限算法

3.獲取請求的url spring

4判斷 根據用戶名查詢的權限 是否包括 請求的urlapache

5.若是包括 則 放行,不包括重定向到 未受權界面緩存

package com.example.springboot.shiro.core.shiro.filter;

import com.example.springboot.shiro.common.utils.SpringContextUtil;
import com.example.springboot.shiro.core.shiro.token.TokenManager;
import com.example.springboot.shiro.user.entity.Upermission;
import com.example.springboot.shiro.user.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.List;

/**
 *  權限 攔截策略
 */
public class URLPathMatchingFilter extends PathMatchingFilter {
    @Autowired
    LoginService loginService;

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {

          if (loginService==null){
              loginService= SpringContextUtil.getContext().getBean(LoginService.class);
          }
        //請求的url
        String requestURL = getPathWithinApplication(request);
        System.out.println("請求的url :"+requestURL);
        Subject subject = SecurityUtils.getSubject();
        if (!subject.isAuthenticated()){
            // 若是沒有登陸, 直接返回true 進入登陸流程
            return  true;
        }
        String email = TokenManager.getEmail();
        List<Upermission> permissions = loginService. upermissions(email);

        boolean hasPermission = false;
        for (Upermission url : permissions) {

              if (url.getUrl().equals(requestURL)){
                  hasPermission = true;
                  break;
              }
        }
        if (hasPermission){
            return true;
        }else {
            UnauthorizedException ex = new UnauthorizedException("當前用戶沒有訪問路徑" + requestURL + "的權限");
            subject.getSession().setAttribute("ex",ex);
            WebUtils.issueRedirect(request, response, "/unauthorized");
            return false;

        }

    }
}

 配置 : ShiroConfiguration(shiro 配置類) springboot

package com.example.springboot.shiro.core.shiro.config;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import com.example.springboot.shiro.core.shiro.filter.KickoutSessionControlFilter;
import com.example.springboot.shiro.core.shiro.filter.SessionControlInterceptor;
import com.example.springboot.shiro.core.shiro.filter.SessionFilter;
import com.example.springboot.shiro.core.shiro.filter.URLPathMatchingFilter;
import com.example.springboot.shiro.core.shiro.token.SampleRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.crazycake.shiro.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.Filter;

@Configuration    //Shiro配置類
public class ShiroConfiguration {
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")

    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;

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


    /**
     * ShiroFilterFactoryBean 處理攔截資源文件問題。
     * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,由於在
     * 初始化ShiroFilterFactoryBean的時候須要注入:SecurityManager
     * <p>
     * Filter Chain定義說明
     * 一、一個URL能夠配置多個Filter,使用逗號分隔
     * 二、當設置多個過濾器時,所有驗證經過,才視爲經過
     * 三、部分過濾器可指定參數,如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 必須設置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登陸成功後要跳轉的連接(沒用,在js中跳轉了)
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未受權界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        //攔截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //自定義攔截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        //限制同一賬號同時在線的個數。
        filtersMap.put("kickout", kickoutSessionControlFilter());
        //訪問權限配置
        filtersMap.put("requestURL", getURLPathMatchingFilter());

        shiroFilterFactoryBean.setFilters(filtersMap);
       /* 配置映射關係*/
        //authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/index", "authc");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/updateSelf", "authc");
        filterChainDefinitionMap.put("/updatePswd", "authc");
        filterChainDefinitionMap.put("/mypermission", "authc");
        filterChainDefinitionMap.put("/kickout", "anon");
        filterChainDefinitionMap.put("/list", "authc");
        filterChainDefinitionMap.put("/online", "authc");
        filterChainDefinitionMap.put("/role", "authc");
        filterChainDefinitionMap.put("/Roleassignment", "authc");
        filterChainDefinitionMap.put("/permissionlist", "authc");
        filterChainDefinitionMap.put("/PermissionAssignment", "authc");

        /*加入自定義過濾器*/
        filterChainDefinitionMap.put("/**", "kickout");
        //下面的配置路徑 都須要在上面配置 authc 不然訪問不到filter
        filterChainDefinitionMap.put("/online","requestURL");
        filterChainDefinitionMap.put("/list", "requestURL");
        filterChainDefinitionMap.put("/role", "requestURL");
        filterChainDefinitionMap.put("/Roleassignment", "requestURL");
        filterChainDefinitionMap.put("/permissionlist", "requestURL");
        filterChainDefinitionMap.put("/PermissionAssignment", "requestURL");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;

    }

    /**
     *   訪問 權限 攔截器
     * @return
     */
    public URLPathMatchingFilter getURLPathMatchingFilter() {
        return new URLPathMatchingFilter();
    }

    /**
     * 自定義域
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //設置realm.
        securityManager.setRealm(getDatabaseRealm());

        // 自定義緩存實現 使用redis
        securityManager.setCacheManager(cacheManager());
        securityManager.setSessionManager(sessionManager());
        //注入記住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 受權&認證
     *
     * @return
     */
    @Bean
    public SampleRealm getDatabaseRealm() {
        SampleRealm myShiroRealm = new SampleRealm();
        System.out.println("myShiroRealm");
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }


    /**
     * 憑證匹配器
     * (因爲咱們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
     * 因此咱們須要修改下doGetAuthenticationInfo中的代碼;
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這裏使用MD5算法;
        //  hashedCredentialsMatcher.setHashIterations(2);//散列的次數,好比散列兩次,至關於 md5(md5(""));

        return hashedCredentialsMatcher;
    }


    /**
     * 開啓shiro aop註解支持.
     * 使用代理方式;因此須要開啓代碼支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * cacheManager 緩存 redis實現
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }


    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis開源插件
     *
     * @return
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(1800);// 配置緩存過時時間
        redisManager.setTimeout(timeout);
        // redisManager.setPassword(password);
        return redisManager;
    }

    /**
     * Session Manager
     * 使用的是shiro-redis開源插件
     */
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao層的實現 經過redis
     * 使用的是shiro-redis開源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();

        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * cookie管理對象;記住我功能
     *
     * @return
     */
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

    /**
     * cookie對象;
     *
     * @return
     */
    public SimpleCookie rememberMeCookie() {
        //這個參數是cookie的名稱,對應前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 記住我cookie生效時間30天 ,單位秒;-->
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    /**
     * 限制同一帳號登陸同時登陸人數控制
     *
     * @return
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter() {
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        //使用cacheManager獲取相應的cache來緩存用戶登陸的會話;用於保存用戶—會話之間的關係的;
        //這裏咱們仍是用以前shiro使用的redisManager()實現的cacheManager()緩存管理
        //也能夠從新另寫一個,從新配置緩存時間之類的自定義緩存屬性
        kickoutSessionControlFilter.setCacheManager(cacheManager());
        //用於根據會話ID,獲取會話進行踢出操做的;
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        //是否踢出後來登陸的,默認是false;即後者登陸的用戶踢出前者登陸的用戶;踢出順序。
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一個用戶最大的會話數,默認1;好比2的意思是同一個用戶容許最多同時兩我的登陸;
        kickoutSessionControlFilter.setMaxSession(1);
        //被踢出後重定向到的地址;
        kickoutSessionControlFilter.setKickoutUrl("kickout");
        return kickoutSessionControlFilter;
    }
}

未受權 異常捕獲:

package com.example.springboot.shiro.common.exception;

import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.ModelAndView;

/**
 *   未受權異常 捕獲
 */
@ControllerAdvice
public class DefaultExceptionHandler {
    @ExceptionHandler({UnauthorizedException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("ex", e);
        mv.setViewName("unauthorized");
        return mv;
    }
}
相關文章
相關標籤/搜索