前言: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; } }