一. 說明
Shiro是一個(gè)安全框架,項(xiàng)目中主要用它做認(rèn)證,授權(quán),加密,以及用戶的會(huì)話管理,雖然Shiro沒有SpringSecurity功能更豐富,但是它輕量,簡(jiǎn)單,在項(xiàng)目中通常業(yè)務(wù)需求Shiro也都能勝任.
二. 項(xiàng)目環(huán)境
MyBatis-Plus版本: 3.1.0
SpringBoot版本:2.1.5
JDK版本:1.8
Shiro版本:1.4
Shiro-redis插件版本:3.1.0
數(shù)據(jù)表(SQL文件在項(xiàng)目中):數(shù)據(jù)庫(kù)中測(cè)試號(hào)的密碼進(jìn)行了加密,密碼皆為123456
Maven依賴如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- AOP依賴,一定要加,否則權(quán)限攔截驗(yàn)證不生效 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- mybatisPlus 核心庫(kù) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 引入阿里數(shù)據(jù)庫(kù)連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Shiro 核心依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- StringUitlS工具 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
配置如下:
# 配置端口
server:
port: 8764
spring:
# 配置數(shù)據(jù)源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
# Redis數(shù)據(jù)源
redis:
host: localhost
port: 6379
timeout: 6000
password: 123456
jedis:
pool:
max-active: 1000 # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
max-wait: -1 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
max-idle: 10 # 連接池中的最大空閑連接
min-idle: 5 # 連接池中的最小空閑連接
# mybatis-plus相關(guān)配置
mybatis-plus:
# xml掃描,多個(gè)目錄用逗號(hào)或者分號(hào)分隔(告訴 Mapper 所對(duì)應(yīng)的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默認(rèn)值,可以不設(shè)置
global-config:
db-config:
#主鍵類型 AUTO:"數(shù)據(jù)庫(kù)ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數(shù)字類型唯一ID)", UUID:"全局唯一ID UUID";
id-type: auto
#字段策略 IGNORED:"忽略判斷" NOT_NULL:"非 NULL 判斷") NOT_EMPTY:"非空判斷"
field-strategy: NOT_EMPTY
#數(shù)據(jù)庫(kù)類型
db-type: MYSQL
configuration:
# 是否開啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫(kù)列名到Java屬性駝峰命名的類似映射
map-underscore-to-camel-case: true
# 如果查詢結(jié)果中包含空值的列账忘,則 MyBatis 在映射的時(shí)候志膀,不會(huì)映射這個(gè)字段
call-setters-on-nulls: true
# 這個(gè)配置會(huì)將執(zhí)行的sql打印出來,在開發(fā)或測(cè)試的時(shí)候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
三. 編寫項(xiàng)目基礎(chǔ)類
用戶實(shí)體,Dao,Service等在這里省略,請(qǐng)參考源碼
編寫Exception類來處理Shiro權(quán)限攔截異常
/**
* @Description 自定義異常
* @Author Sans
* @CreateTime 2019/6/15 22:56
*/
@ControllerAdvice
public class MyShiroException {
/**
* 處理Shiro權(quán)限攔截異常
* 如果返回JSON數(shù)據(jù)格式請(qǐng)加上 @ResponseBody注解
* @Author Sans
* @CreateTime 2019/6/15 13:35
* @Return Map<Object> 返回結(jié)果集
*/
@ResponseBody
@ExceptionHandler(value = AuthorizationException.class)
public Map<String,Object> defaultErrorHandler(){
Map<String,Object> map = new HashMap<>();
map.put("403","權(quán)限不足");
return map;
}
}
創(chuàng)建SHA256Util加密工具
/**
* @Description Sha-256加密工具
* @Author Sans
* @CreateTime 2019/6/12 9:27
*/
public class SHA256Util {
/** 私有構(gòu)造器 **/
private SHA256Util(){};
/** 加密算法 **/
public final static String HASH_ALGORITHM_NAME = "SHA-256";
/** 循環(huán)次數(shù) **/
public final static int HASH_ITERATIONS = 15;
/** 執(zhí)行加密-采用SHA256和鹽值加密 **/
public static String sha256(String password, String salt) {
return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();
}
}
創(chuàng)建Spring工具
/**
* @Description Spring上下文工具類
* @Author Sans
* @CreateTime 2019/6/17 13:40
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
/**
* Spring在bean初始化后會(huì)判斷是不是ApplicationContextAware的子類
* 如果該類是,setApplicationContext()方法,會(huì)將容器中ApplicationContext作為參數(shù)傳入進(jìn)去
* @Author Sans
* @CreateTime 2019/6/17 16:58
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
/**
* 通過Name返回指定的Bean
* @Author Sans
* @CreateTime 2019/6/17 16:03
*/
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
創(chuàng)建Shiro工具
/**
* @Description Shiro工具類
* @Author Sans
* @CreateTime 2019/6/15 16:11
*/
public class ShiroUtils {
/** 私有構(gòu)造器 **/
private ShiroUtils(){}
private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
/**
* 獲取當(dāng)前用戶Session
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity 用戶信息
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
/**
* 用戶登出
* @Author Sans
* @CreateTime 2019/6/17 17:23
*/
public static void logout() {
SecurityUtils.getSubject().logout();
}
/**
* 獲取當(dāng)前用戶信息
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity 用戶信息
*/
public static SysUserEntity getUserInfo() {
return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}
/**
* 刪除用戶緩存信息
* @Author Sans
* @CreateTime 2019/6/17 13:57
* @Param username 用戶名稱
* @Param isRemoveSession 是否刪除Session
* @Return void
*/
public static void deleteCache(String username, boolean isRemoveSession){
//從緩存中獲取Session
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
SysUserEntity sysUserEntity;
Object attribute = null;
for(Session sessionInfo : sessions){
//遍歷Session,找到該用戶名稱對(duì)應(yīng)的Session
attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (attribute == null) {
continue;
}
sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
if (sysUserEntity == null) {
continue;
}
if (Objects.equals(sysUserEntity.getUsername(), username)) {
session=sessionInfo;
}
}
if (session == null||attribute == null) {
return;
}
//刪除session
if (isRemoveSession) {
redisSessionDAO.delete(session);
}
//刪除Cache鳖擒,在訪問受限接口時(shí)會(huì)重新授權(quán)
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
Authenticator authc = securityManager.getAuthenticator();
((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
}
}
創(chuàng)建Shiro的SessionId生成器
/**
* @Description 自定義SessionId生成器
* @Author Sans
* @CreateTime 2019/6/11 11:48
*/
public class ShiroSessionIdGenerator implements SessionIdGenerator {
/**
* 實(shí)現(xiàn)SessionId生成
* @Author Sans
* @CreateTime 2019/6/11 11:54
*/
@Override
public Serializable generateId(Session session) {
Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
return String.format("login_token_%s", sessionId);
}
}
四. 編寫Shiro核心類
創(chuàng)建Realm用于授權(quán)和認(rèn)證
/**
* @Description Shiro權(quán)限匹配和賬號(hào)密碼匹配
* @Author Sans
* @CreateTime 2019/6/15 11:27
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
/**
* 授權(quán)權(quán)限
* 用戶進(jìn)行權(quán)限驗(yàn)證時(shí)候Shiro會(huì)去緩存中找,如果查不到數(shù)據(jù),會(huì)執(zhí)行這個(gè)方法去查權(quán)限,并放入緩存中
* @Author Sans
* @CreateTime 2019/6/12 11:44
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
//獲取用戶ID
Long userId =sysUserEntity.getUserId();
//這里可以進(jìn)行授權(quán)和處理
Set<String> rolesSet = new HashSet<>();
Set<String> permsSet = new HashSet<>();
//查詢角色和權(quán)限(這里根據(jù)業(yè)務(wù)自行查詢)
List<SysRoleEntity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);
for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {
rolesSet.add(sysRoleEntity.getRoleName());
List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
for (SysMenuEntity sysMenuEntity :sysMenuEntityList) {
permsSet.add(sysMenuEntity.getPerms());
}
}
//將查到的權(quán)限和角色分別傳入authorizationInfo中
authorizationInfo.setStringPermissions(permsSet);
authorizationInfo.setRoles(rolesSet);
return authorizationInfo;
}
/**
* 身份認(rèn)證
* @Author Sans
* @CreateTime 2019/6/12 12:36
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//獲取用戶的輸入的賬號(hào).
String username = (String) authenticationToken.getPrincipal();
//通過username從數(shù)據(jù)庫(kù)中查找 User對(duì)象溉浙,如果找到進(jìn)行驗(yàn)證
//實(shí)際項(xiàng)目中,這里可以根據(jù)實(shí)際情況做緩存,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
SysUserEntity user = sysUserService.selectUserByName(username);
//判斷賬號(hào)是否存在
if (user == null) {
throw new AuthenticationException();
}
//判斷賬號(hào)是否被凍結(jié)
if (user.getState()==null||user.getState().equals("PROHIBIT")){
throw new LockedAccountException();
}
//進(jìn)行驗(yàn)證
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用戶名
user.getPassword(), //密碼
ByteSource.Util.bytes(user.getSalt()), //設(shè)置鹽值
getName()
);
//驗(yàn)證成功開始踢人(清除緩存和Session)
ShiroUtils.deleteCache(username,true);
return authenticationInfo;
}
}
創(chuàng)建SessionManager類
/**
* @Description 自定義獲取Token
* @Author Sans
* @CreateTime 2019/6/13 8:34
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
//定義常量
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
//重寫構(gòu)造器
public ShiroSessionManager() {
super();
this.setDeleteInvalidSessions(true);
}
/**
* 重寫方法實(shí)現(xiàn)從請(qǐng)求頭獲取Token便于接口統(tǒng)一
* 每次請(qǐng)求進(jìn)來,Shiro會(huì)去從請(qǐng)求頭找Authorization這個(gè)key對(duì)應(yīng)的Value(Token)
* @Author Sans
* @CreateTime 2019/6/13 8:47
*/
@Override
public Serializable getSessionId(ServletRequest request, ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果請(qǐng)求頭中存在token 則從請(qǐng)求頭中獲取token
if (!StringUtils.isEmpty(token)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return token;
} else {
//否則按默認(rèn)規(guī)則從cookie取token
return super.getSessionId(request, response);
}
}
}
創(chuàng)建ShiroConfig配置類
/**
* @Description Shiro配置類
* @Author Sans
* @CreateTime 2019/6/10 17:42
*/
@Configuration
public class ShiroConfig {
private final String CACHE_KEY = "shiro:cache:";
private final String SESSION_KEY = "shiro:session:";
private final int EXPIRE = 1800;
//Redis配置
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* 開啟Shiro-aop注解支持
* @Attention 使用代理方式所以需要開啟代碼支持
* @Author Sans
* @CreateTime 2019/6/12 8:38
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro基礎(chǔ)配置
* @Author Sans
* @CreateTime 2019/6/12 8:42
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 注意過濾器配置順序不能顛倒
// 配置過濾:不會(huì)被攔截的鏈接
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/userLogin/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
// 配置shiro默認(rèn)登錄界面地址,前后端分離中登錄界面跳轉(zhuǎn)應(yīng)由前端路由控制蒋荚,后臺(tái)僅返回json數(shù)據(jù)
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 安全管理器
* @Author Sans
* @CreateTime 2019/6/12 10:34
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定義Ssession管理
securityManager.setSessionManager(sessionManager());
// 自定義Cache實(shí)現(xiàn)
securityManager.setCacheManager(cacheManager());
// 自定義Realm驗(yàn)證
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 身份驗(yàn)證器
* @Author Sans
* @CreateTime 2019/6/12 10:37
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/**
* 憑證匹配器
* 將密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理,在這里做匹配配置
* @Author Sans
* @CreateTime 2019/6/12 10:48
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:這里使用SHA256算法;
shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
// 散列的次數(shù)戳稽,比如散列兩次,相當(dāng)于 md5(md5(""));
shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
return shaCredentialsMatcher;
}
/**
* 配置Redis管理器
* @Attention 使用的是shiro-redis開源插件
* @Author Sans
* @CreateTime 2019/6/12 11:06
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* 配置Cache管理器
* 用于往Redis存儲(chǔ)權(quán)限和角色標(biāo)識(shí)
* @Attention 使用的是shiro-redis開源插件
* @Author Sans
* @CreateTime 2019/6/12 12:37
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置緩存的話要求放在session里面的實(shí)體類必須有個(gè)id標(biāo)識(shí)
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* SessionID生成器
* @Author Sans
* @CreateTime 2019/6/12 13:12
*/
@Bean
public ShiroSessionIdGenerator sessionIdGenerator(){
return new ShiroSessionIdGenerator();
}
/**
* 配置RedisSessionDAO
* @Attention 使用的是shiro-redis開源插件
* @Author Sans
* @CreateTime 2019/6/12 13:44
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(expire);
return redisSessionDAO;
}
/**
* 配置Session管理器
* @Author Sans
* @CreateTime 2019/6/12 14:25
*/
@Bean
public SessionManager sessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
return shiroSessionManager;
}
}
五. 實(shí)現(xiàn)權(quán)限控制
Shiro可以用代碼或者注解來控制權(quán)限,通常我們使用注解控制,不僅簡(jiǎn)單方便,而且更加靈活.Shiro注解一共有五個(gè):
一般情況下我們?cè)陧?xiàng)目中做權(quán)限控制,使用最多的是RequiresPermissions和RequiresRoles,允許存在多個(gè)角色和權(quán)限,默認(rèn)邏輯是AND,也就是同時(shí)擁有這些才可以訪問方法,可以在注解中以參數(shù)的形式設(shè)置成OR
示例:
//擁有一個(gè)角色就可以訪問
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
//擁有所有權(quán)限才可以訪問
@RequiresPermissions(value={"sys:user:info","sys:role:info"},logical = Logical.AND)
使用順序:Shiro注解是存在順序的,當(dāng)多個(gè)注解在一個(gè)方法上的時(shí)候,會(huì)逐個(gè)檢查,知道全部通過為止,默認(rèn)攔截順序是:RequiresRoles->RequiresPermissions->RequiresAuthentication->
RequiresUser->RequiresGuest
示例:
//擁有ADMIN角色同時(shí)還要有sys:role:info權(quán)限
@RequiresRoles(value={"ADMIN")
@RequiresPermissions("sys:role:info")
創(chuàng)建UserRoleController角色攔截測(cè)試類
/**
* @Description 角色測(cè)試
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/role")
public class UserRoleController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* 管理員角色測(cè)試接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getAdminInfo")
@RequiresRoles("ADMIN")
public Map<String,Object> getAdminInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","這里是只有管理員角色能訪問的接口");
return map;
}
/**
* 用戶角色測(cè)試接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getUserInfo")
@RequiresRoles("USER")
public Map<String,Object> getUserInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","這里是只有用戶角色能訪問的接口");
return map;
}
/**
* 角色測(cè)試接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getRoleInfo")
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
@RequiresUser
public Map<String,Object> getRoleInfo(){
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","這里是只要有ADMIN或者USER角色能訪問的接口");
return map;
}
/**
* 登出(測(cè)試登出)
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getLogout")
@RequiresUser
public Map<String,Object> getLogout(){
ShiroUtils.logout();
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","登出");
return map;
}
}
創(chuàng)建UserMenuController權(quán)限攔截測(cè)試類
/**
* @Description 權(quán)限測(cè)試
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/menu")
public class UserMenuController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* 獲取用戶信息集合
* @Author Sans
* @CreateTime 2019/6/19 10:36
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getUserInfoList")
@RequiresPermissions("sys:user:info")
public Map<String,Object> getUserInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
return map;
}
/**
* 獲取角色信息集合
* @Author Sans
* @CreateTime 2019/6/19 10:37
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getRoleInfoList")
@RequiresPermissions("sys:role:info")
public Map<String,Object> getRoleInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
return map;
}
/**
* 獲取權(quán)限信息集合
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getMenuInfoList")
@RequiresPermissions("sys:menu:info")
public Map<String,Object> getMenuInfoList(){
Map<String,Object> map = new HashMap<>();
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* 獲取所有數(shù)據(jù)
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/getInfoAll")
@RequiresPermissions("sys:info:all")
public Map<String,Object> getInfoAll(){
Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* 添加管理員角色權(quán)限(測(cè)試動(dòng)態(tài)權(quán)限更新)
* @Author Sans
* @CreateTime 2019/6/19 10:39
* @Param username 用戶ID
* @Return Map<String,Object> 返回結(jié)果
*/
@RequestMapping("/addMenu")
public Map<String,Object> addMenu(){
//添加管理員角色權(quán)限
SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();
sysRoleMenuEntity.setMenuId(4L);
sysRoleMenuEntity.setRoleId(1L);
sysRoleMenuService.save(sysRoleMenuEntity);
//清除緩存
String username = "admin";
ShiroUtils.deleteCache(username,false);
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","權(quán)限添加成功");
return map;
}
}
創(chuàng)建UserLoginController登錄類
/**
* @Description 用戶登錄
* @Author Sans
* @CreateTime 2019/6/17 15:21
*/
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {
@Autowired
private SysUserService sysUserService;
/**
* 登錄
* @Author Sans
* @CreateTime 2019/6/20 9:21
*/
@RequestMapping("/login")
public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){
Map<String,Object> map = new HashMap<>();
//進(jìn)行身份驗(yàn)證
try{
//驗(yàn)證身份和登陸
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
//驗(yàn)證成功進(jìn)行登錄操作
subject.login(token);
}catch (IncorrectCredentialsException e) {
map.put("code",500);
map.put("msg","用戶不存在或者密碼錯(cuò)誤");
return map;
} catch (LockedAccountException e) {
map.put("code",500);
map.put("msg","登錄失敗期升,該用戶已被凍結(jié)");
return map;
} catch (AuthenticationException e) {
map.put("code",500);
map.put("msg","該用戶不存在");
return map;
} catch (Exception e) {
map.put("code",500);
map.put("msg","未知異常");
return map;
}
map.put("code",0);
map.put("msg","登錄成功");
map.put("token",ShiroUtils.getSession().getId().toString());
return map;
}
/**
* 未登錄
* @Author Sans
* @CreateTime 2019/6/20 9:22
*/
@RequestMapping("/unauth")
public Map<String,Object> unauth(){
Map<String,Object> map = new HashMap<>();
map.put("code",500);
map.put("msg","未登錄");
return map;
}
}
六. POSTMAN測(cè)試
登錄成功后會(huì)返回TOKEN,因?yàn)槭菃吸c(diǎn)登錄,再次登陸的話會(huì)返回新的TOKEN,之前Redis的TOKEN就會(huì)失效了
當(dāng)?shù)谝淮卧L問接口后我們可以看到緩存中已經(jīng)有權(quán)限數(shù)據(jù)了,在次訪問接口的時(shí)候,Shiro會(huì)直接去緩存中拿取權(quán)限,注意訪問接口時(shí)候要設(shè)置請(qǐng)求頭.
ADMIN這個(gè)號(hào)現(xiàn)在沒有sys:info:all這個(gè)權(quán)限的,所以無(wú)法訪問getInfoAll接口,我們要?jiǎng)討B(tài)分配權(quán)限后,要清掉緩存,在訪問接口時(shí)候,Shiro會(huì)去重新執(zhí)行授權(quán)方法,之后再次把權(quán)限和角色數(shù)據(jù)放入緩存中
訪問添加權(quán)限測(cè)試接口,因?yàn)槭菧y(cè)試,我把增加權(quán)限的用戶ADMIN寫死在里面了,權(quán)限添加后,調(diào)用工具類清掉緩存,我們可以發(fā)現(xiàn),Redis中已經(jīng)沒有緩存了
再次訪問getInfoAll接口,因?yàn)榫彺嬷袥]有數(shù)據(jù),Shiro會(huì)重新授權(quán)查詢權(quán)限,攔截通過