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