前言:
權限控制有 注解的方式,jsp shiro標簽的方式,還有url 動態控制的方式。這里我使用最后一種方式來控制權限
思路:
0.利用 PathMatchingFilter 攔截器
1.根據用戶名 來查詢角色,
2.根據角色查詢權限
3.獲取請求的url
4判斷 根據用戶名查詢的權限 是否包括 請求的url
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 配置類)
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定義說明
* 1、一個URL可以配置多個Filter,使用逗號分隔
* 2、當設置多個過濾器時,全部驗證通過,才視為通過
* 3、部分過濾器可指定參數,如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;
}
}