1. 程式人生 > 實用技巧 >Spring boot 整合shiro實現許可權管理

Spring boot 整合shiro實現許可權管理

1.maven整合所需關鍵jar包。

`

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 由於shiro很多地方都是靠aop的攔截器達到程式目的,所以需要引入AOP的包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 由於要實現redis儲存session會話,所以引入redis的包 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.8.24</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mysql 資料庫 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>

<!-- For log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

`

2.配置檔案

`

spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.3.5:3306/platform?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
redis:
host: 192.168.3.5
port: 6379
timeout: 360000
password: 123456


server:
port: 8081

`

3.shiro的機制主要有兩部分組成,ShiroConfig配置shiro的引數配置,Realm配置許可權校驗體系的規則,主要為兩個函式一個是認證一個是授權

`

package com.example.demo.shiro;

import com.example.demo.listener.ShiroSessionListener;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.timeout}")
private int redisTimeout;
@Value("${spring.redis.password}")
private String redisPassword;

private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);



@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//安全管理器配置
shiroFilterFactoryBean.setSecurityManager(securityManager);
//沒有登入的使用者請求需要登入的頁面時自動跳轉到登入頁面。
//shiroFilterFactoryBean.setLoginUrl("/login");
//沒有許可權預設跳轉的頁面,登入的使用者訪問了沒有被授權的資源自動跳轉到的頁面
//shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
//登入成功預設跳轉頁面,不配置則跳轉至”/”,可以不配置,直接通過程式碼進行處理
//shiroFilterFactoryBean.setSuccessUrl("/");


Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
//主要這行程式碼必須放在所有許可權設定的最後,不然會導致所有 url 都被攔截 剩餘的都需要認證
filterChainDefinitionMap.put("/**", "authc");
//filterChainDefinitions 配置過濾規則,從上到下的順序匹配
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;

}

/**
* 配置全域性事務管理
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//設定自定義realm
defaultSecurityManager.setRealm(myRealm());
//配置redis快取
defaultSecurityManager.setCacheManager(cacheManager());
//配置自定義session管理,使用ehcache 或redis
defaultSecurityManager.setSessionManager(sessionManager());
return defaultSecurityManager;
}

@Bean
public MyRealm myRealm() {
MyRealm myRealm = new MyRealm();
myRealm.setCachingEnabled(true);
//啟用身份驗證快取,即快取AuthenticationInfo資訊,預設false
myRealm.setAuthenticationCachingEnabled(true);
//快取AuthenticationInfo資訊的快取名稱 在ehcache-shiro.xml中有對應快取的配置
myRealm.setAuthenticationCacheName("authenticationCache");
//啟用授權快取,即快取AuthorizationInfo資訊,預設false
myRealm.setAuthorizationCachingEnabled(true);
//快取AuthorizationInfo資訊的快取名稱 在ehcache-shiro.xml中有對應快取的配置
myRealm.setAuthorizationCacheName("authorizationCache");
//配置自定義密碼比較器
//shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
return myRealm;
}

/**
* 配置會話管理器,設定會話超時及儲存
* @return
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager manager = new DefaultWebSessionManager();
manager.setCacheManager(new MemoryConstrainedCacheManager());// 加入快取管理器
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
listeners.add(sessionListener()); //配置監聽
manager.setSessionListeners(listeners);

manager.setSessionIdCookie(sessionIdCookie());
manager.setSessionDAO(sessionDAO());// 設定SessionDao
manager.setCacheManager(cacheManager());

manager.setGlobalSessionTimeout(1800000);// 設定全域性session超時時間,單位毫秒
manager.setDeleteInvalidSessions(true);// 刪除過期的session
//是否開啟定時排程器進行檢測過期session 預設為true
manager.setSessionValidationSchedulerEnabled(true);


return manager;
}

/**
* shiro快取管理器;
* 需要新增到securityManager中
* @return
*/
@Bean
public RedisCacheManager cacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}

/**
* 配置session監聽
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener(){
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}

/**
* 配置會話ID生成器
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}

/**
* SessionDAO的作用是為Session提供CRUD並進行持久化的一個shiro元件
* MemorySessionDAO 直接在記憶體中進行會話維護
* EnterpriseCacheSessionDAO 提供了快取功能的會話維護,預設情況下使用MapCache實現,內部使用ConcurrentHashMap儲存快取的會話。
* @return
*/
@Bean
public SessionDAO sessionDAO() {
EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
//使用redis快取
enterpriseCacheSessionDAO.setCacheManager(cacheManager());
//設定session快取的名字 預設為 shiro-activeSessionCache
enterpriseCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
//sessionId生成器
enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
return enterpriseCacheSessionDAO;
}

/**
* 配置儲存sessionId的cookie
* 注意:這裡的cookie 不是上面的記住我 cookie 記住我需要一個cookie session管理 也需要自己的cookie
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie(){
//這個引數是cookie的名稱
SimpleCookie simpleCookie = new SimpleCookie("sid");
//setcookie的httponly屬性如果設為true的話,會增加對xss防護的安全係數。設為true後,只能通過http訪問,javascript無法訪問,防止xss讀取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示瀏覽器關閉時失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}

/**
* 配置Redis伺服器
* @return
*/
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisHost);
redisManager.setPort(redisPort);
redisManager.setPassword(redisPassword);
redisManager.setTimeout(redisTimeout);
return redisManager;
}

/**
* 支援shiro註解許可權控制
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
sourceAdvisor.setSecurityManager(securityManager);
return sourceAdvisor;
}

}

`

`

package com.example.demo.shiro;

import com.example.demo.model.Role;
import com.example.demo.model.User;
import com.example.demo.service.PermissionService;
import com.example.demo.service.RoleService;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MyRealm extends AuthorizingRealm {

@Autowired
private UserService userService;

@Autowired
private RoleService roleService;

@Autowired
private PermissionService permissionService;

/**
* 為當前登入成功的使用者授予許可權和分配角色。
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
// 獲取使用者名稱
String username = (String) principalCollection.getPrimaryPrincipal();
User user = userService.getUserByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

List<String> roleList = roleService.getRoleListByUserId(user.getId());
Set<String> roleSet = new HashSet<>();
roleSet.addAll(roleList);
authorizationInfo.setRoles(roleSet);

List<String> permsList = permissionService.getPermsListByUserId(user.getId());
System.out.println(permsList.toString());
Set<String> permsSet = new HashSet<>();
permsSet.addAll(permsList);
authorizationInfo.setStringPermissions(permsSet);

return authorizationInfo;
}


/**
* 驗證當前登入的使用者,獲取認證資訊
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

//獲取使用者名稱/密碼
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());

//從資料庫查詢使用者資訊
User user = userService.getUserByUsername(username);

//可以在這裡直接對使用者名稱校驗,或者呼叫 CredentialsMatcher 校驗
if (user == null) {
throw new UnknownAccountException("使用者名稱或密碼錯誤!");
}
//這裡將 密碼對比 登出掉,否則 無法鎖定 要將密碼對比 交給 密碼比較器
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("使用者名稱或密碼錯誤!");
}

//呼叫 CredentialsMatcher 校驗 還需要建立一個類 繼承CredentialsMatcher 如果在上面校驗了,這個就不需要了
//配置自定義許可權登入器 參考部落格:

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, user.getPassword(), getName());
return info;
}

/**
* 重寫方法,清除當前使用者的的 授權快取
* @param principals
*/
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}

/**
* 重寫方法,清除當前使用者的 認證快取
* @param principals
*/
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}

@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}

/**
* 自定義方法:清除所有 授權快取
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}

/**
* 自定義方法:清除所有 認證快取
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}

/**
* 自定義方法:清除所有的 認證快取 和 授權快取
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}


}

`

由於我們使用到了session的監聽機制,所以還需建一個listener類

`

package com.example.demo.listener;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import java.util.concurrent.atomic.AtomicInteger;


public class ShiroSessionListener implements SessionListener {

/**
* 統計線上人數
* juc包下執行緒安全自增
*/
private final AtomicInteger sessionCount = new AtomicInteger(0);

/**
* 會話建立時觸發
* @param session
*/
@Override
public void onStart(Session session) {
//會話建立,線上人數加一
sessionCount.incrementAndGet();
}

/**
* 退出會話時觸發
* @param session
*/
@Override
public void onStop(Session session) {
//會話退出,線上人數減一
sessionCount.decrementAndGet();
}

/**
* 會話過期時觸發
* @param session
*/
@Override
public void onExpiration(Session session) {
//會話過期,線上人數減一
sessionCount.decrementAndGet();
}
/**
* 獲取線上人數使用
* @return
*/
public AtomicInteger getSessionCount() {
return sessionCount;
}

}

`

到這裡,基本上springboot整合shiro的關鍵配置就結束了,最主要的是ShiroConfig和 MyRealm這兩個類,需要仔細的研究一下。其實多看幾遍也很簡單。

其他的service、serviceImpl、dao之類的類,就不再詳細貼了。