先說下爲何寫這篇文章,由於實際項目須要,須要對咱們如今項目頁面小到每一個部件都要作權限控制,而後查了下網上經常使用的權限框架,一個是shrio,一個是spring security,看了下對比,都說shrio比較輕量,比較好用,而後我也就選擇了shrio來作整個項目的權限框架,同時結合網上大佬作過的一些spring boot+shrio整合案例,只能說你們圖都畫的挺好的....,看着你們的功能流程圖仔細想一想是那麼回事,而後本身再實踐就走不動了,各類坑都有啊。。。,迴歸到具體實現真的是步步都是坑。在實踐的過程當中想了下面幾種方案,有些要麼是還沒開始coding就已經想着走不通了,有些就是代碼敲了一半了發現行不通了,在本項目中我也參考了RCBA權限設計模型。
java
一、將shrio和網關gateway放在同一個服務中,可是這就帶來一個問題,衆所周知,shrio的數據中心realm須要用到用戶服務當中的數據(查詢用戶、角色、權限之間的關係及數據),所以這裏shrio就須要使用服務發現組件(我這裏用的dubbo)去發現用戶服務,可是用戶服務中的登陸又須要用到shrio的認證,到這裏可能有人要說了,能夠在用戶服務中再去遠程調用shrio服務啊,若是這種方法能夠的話你們就能夠用這種方法就不用往下看了....**因此這就形成兩個服務耦合在一起去了,這種方法直接pass掉。**

二、在每個服務中都共享一個shrio配置模塊,這種方式一樣也有問題,和上面出現的問題相似,如今shrio是個單獨的模塊,須要用到用戶服務,可使用dubbo遠程調用,而用戶服務須要將shrio配置模塊經過maven導入進來,如今啓動用戶服務,確定會報錯:在shrio配置模塊中沒有找到服務的提供者。所以這種方案也能夠pass掉了。mysql
相信上面兩種方案確定不止我一我的這麼作過,只能說shrio仍是適合單體架構啊....固然,也不是說shrio不能作微服務的權限控制,在通過我長達一週的鑽研和嘗試以後,終於仍是發現微服務用shrio怎樣作權限設計了,下面說一下個人方案。、nginx
結合上面兩種行不通的方法,咱們取長補短,新的方案以下。
方案一git
既然用戶服務和shrio模塊須要分開可是二者又是須要互相依賴,咱們能夠針對用戶服務專門配置一個shrio模塊,其餘服務共享一個shrio模塊。固然這兩個shrio模塊須要共享session會話web
、redis
示例項目使用springboot+mysql+mybatis-plus實現,服務發現和註冊工具採用dubbo+zookeeper(這裏我主要是想學習下這兩個組件的用法,你們也可使用eureka+feign)。spring
common模塊:整個項目的公共模塊,common-core就包含了其餘微服務須要的一些常量數據、返回值、異常,common-cache模塊中包含了全部微服務須要的shrio緩存配置,除了用戶服務其餘服務須要的受權模塊common-auth。
gateway-service服務:網關服務,全部其餘服務的入口。
user-api:用戶服務定義的數據接口。
user-provider-service:用戶服務接口的實現,用戶服務的提供者。
user-consumer-service:用戶服務的最外層,供nginx訪問調用的服務,用戶服務的消費者。
video-api:同用戶服務api。
video-provider:同用戶服務provider。
video-consumer:同用戶服務consumer。sql
先說一下咱們爲何須要共享session會話,由於咱們的項目是由多個微服務組成,當用戶服務接收到用戶的登陸請求並登陸成功時咱們給用戶返回一個sessionId並保存在用戶的瀏覽器中的cookie裏,用戶此時再請求用戶服務就會攜帶cookie當中的sessionId而服務器端就能夠根據用戶攜帶的sessionId取出保存在服務器的用戶信息,可是此時若是用戶去請求視頻服務就不能取出保存在服務器的用戶信息,由於視頻服務根本就不知道你是否登陸過,因此這就須要咱們將登陸成功的用戶信息進行共享而不只僅是用戶服務才能夠訪問。數據庫
咱們在寫shrio的相關配置時,都知道須要自定義shrio的安全管理器,也就是重寫DefaultWebSecurityManager,咱們看一下實例化這個安全管理器類中間有哪些組件會被初始化。
首先是DefaultWebSecurityManager的構造器。apache
public DefaultWebSecurityManager() { super(); ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator()); this.sessionMode = HTTP_SESSION_MODE; setSubjectFactory(new DefaultWebSubjectFactory()); setRememberMeManager(new CookieRememberMeManager()); setSessionManager(new ServletContainerSessionManager()); }
進入DefaultWebSecurityManager的父類DefaultSecurityManager,查看DefaultSecurityManager的構造器。
public DefaultSecurityManager() { super(); this.subjectFactory = new DefaultSubjectFactory(); this.subjectDAO = new DefaultSubjectDAO(); }
進入DefaultSecurityManager的父類SessionsSecurityManager,查看SessionsSecurityManager的構造器。
public SessionsSecurityManager() { super(); this.sessionManager = new DefaultSessionManager(); applyCacheManagerToSessionManager(); }
在這個構造器中咱們看到了實例化了一個默認的session管理器DefaultSessionManager。咱們點進去看看。能夠看到DefaultSessionManager中默認的就是使用的是內存來保存session(MemorySessionDAO就是對session進行操做的類)。
public SessionsSecurityManager() { super(); this.sessionManager = new DefaultSessionManager(); applyCacheManagerToSessionManager(); }
根據上面咱們的分析,若是要想在各個微服務中共享session就不能把session放在某個微服務所在服務器的內存中,須要把session單獨拿出來共享,所以咱們就須要寫一個自定義的SessionDAO來覆蓋默認的MemorySessionDAO,下面來看看怎麼實現自定義的SessionDAO。
根據上面sessionDAO關係圖咱們能夠知道,AbstractSessionDAO主要有兩個子類,一個是已經實現好的EnterpriseCacheSessionDAO,另外一個就是MemorySessionDAO,如今咱們須要替換默認的MemorySessionDAO,要麼咱們繼承AbstractSessionDAO實現其中的讀寫session的方法,要麼直接使用它已經給咱們實現好的EnterpriseCacheSessionDAO。在這裏我選擇直接使用EnterpriseCacheSessionDAO類。
public EnterpriseCacheSessionDAO() { setCacheManager(new AbstractCacheManager() { @Override protected Cache<Serializable, Session> createCache(String name) throws CacheException { return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>()); } }); }
不過在上面類的構造方法中咱們能夠發現它默認是給咱們new了一個AbstractCacheManager緩存管理器,而且使用的是ConcurrentHashMap來保存會話session,所以若是咱們要用這個EnterpriseCacheSessionDAO類來實現緩存操做,那麼咱們就須要須要寫一個自定義的CacheManager來覆蓋它默認的CacheManager。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--導入shrio相關--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> </dependencies>
@Component("myCacheManager") public class MyCacheManager implements CacheManager { @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { return new MyCache(); } }
public class JedisClient { private static Logger logger = LoggerFactory.getLogger(JedisClient.class); protected static final ThreadLocal<Jedis> threadLocalJedis = new ThreadLocal<Jedis>(); private static JedisPool jedisPool; private static final String HOST = "localhost"; private static final int PORT = 6379; private static final String PASSWORD = "1234"; //控制一個pool最多有多少個狀態爲idle(空閒的)的jedis實例,默認值也是8。 private static int MAX_IDLE = 16; //可用鏈接實例的最大數目,默認值爲8; //若是賦值爲-1,則表示不限制;若是pool已經分配了maxActive個jedis實例,則此時pool的狀態爲exhausted(耗盡)。 private static int MAX_ACTIVE = -1; //超時時間 private static final int TIMEOUT = 1000 * 5; //等待可用鏈接的最大時間,單位毫秒,默認值爲-1。表示用不超時 private static int MAX_WAIT = 1000 * 5; // 鏈接數據庫(0-15) private static final int DATABASE = 2; static { initialPool(); } public static JedisPool initialPool() { JedisPool jp = null; try { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(MAX_IDLE); config.setMaxTotal(MAX_ACTIVE); config.setMaxWaitMillis(MAX_WAIT); config.setTestOnCreate(true); config.setTestWhileIdle(true); config.setTestOnReturn(true); jp = new JedisPool(config, HOST, PORT, TIMEOUT, PASSWORD, DATABASE); jedisPool = jp; threadLocalJedis.set(getJedis()); } catch (Exception e) { e.printStackTrace(); logger.error("redis服務器異常", e); } return jp; } /** * 獲取jedis實例 * * @return jedis */ public static Jedis getJedis() { boolean success = false; Jedis jedis = null; int i = 0; while (!success) { i++; try { if (jedisPool != null) { jedis = threadLocalJedis.get(); if (jedis == null) { jedis = jedisPool.getResource(); } else { if (!jedis.isConnected() && !jedis.getClient().isBroken()) { threadLocalJedis.set(null); jedis = jedisPool.getResource(); } return jedis; } } else { throw new RuntimeException("redis鏈接池初始化失敗"); } } catch (Exception e) { logger.error(Thread.currentThread().getName() + "第" + i + "次獲取失敗"); success = false; e.printStackTrace(); logger.error("redis服務器異常", e); } if (jedis != null) { success = true; } if (i >= 10 && i < 20) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } if (i >= 20 && i < 30) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } if (i >= 30 && i < 40) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } if (i >= 40) { System.out.println("redis完全連不上了~~~~(>_<)~~~~"); return null; } } if (threadLocalJedis.get() == null) { threadLocalJedis.set(jedis); } return jedis; } /** * 設置key-value * * @param key * @param value */ public static void setValue(byte[] key, byte[] value) { Jedis jedis = null; try { jedis = getJedis(); jedis.set(key, value); } catch (Exception e) { threadLocalJedis.set(null); logger.error("redis服務器異常", e); throw new RuntimeException("redis服務器異常"); } finally { if (jedis != null) { close(jedis); } } } /** * 設置key-value,過時時間 * * @param key * @param value * @param seconds */ public static void setValue(byte[] key, byte[] value, int seconds) { Jedis jedis = null; try { jedis = getJedis(); jedis.setex(key, seconds, value); } catch (Exception e) { threadLocalJedis.set(null); logger.error("redis服務器異常", e); throw new RuntimeException("redis服務器異常"); } finally { if (jedis != null) { close(jedis); } } } public static byte[] getValue(byte[] key) { Jedis jedis = null; try { jedis = getJedis(); if (jedis == null || !jedis.exists(key)) { return null; } return jedis.get(key); } catch (Exception e) { threadLocalJedis.set(null); logger.error("redis服務器異常", e); throw new RuntimeException("redis服務器異常"); } finally { if (jedis != null) { close(jedis); } } } public static long delkey(byte[] key) { Jedis jedis = null; try { jedis = getJedis(); if (jedis == null || !jedis.exists(key)) { return 0; } return jedis.del(key); } catch (Exception e) { threadLocalJedis.set(null); logger.error("redis服務器異常", e); throw new RuntimeException("redis服務器異常"); } finally { if (jedis != null) { close(jedis); } } } public static void close(Jedis jedis) { if (threadLocalJedis.get() == null && jedis != null) { jedis.close(); } } public static void clear() { if (threadLocalJedis.get() == null) { return; } Set<String> keys = threadLocalJedis.get().keys("*"); keys.forEach(key -> delkey(key.getBytes())); } }
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.session.mgt.SimpleSession; import java.io.*; import java.time.Duration; import java.util.Collection; import java.util.Set; public class MyCache<S, V> implements Cache<Object, Object> { //設置緩存的過時時間(30分鐘) private Duration cacheExpireTime = Duration.ofMinutes(30); /** * 根據對應的key獲取值value * * @param s * @return * @throws CacheException */ @Override public Object get(Object s) throws CacheException { System.out.println("get()方法...."); byte[] bytes = JedisClient.getValue(objectToBytes(s)); return bytes == null ? null : (SimpleSession) bytesToObject(bytes); } /** * 將K-V保存到redis中 * 注意:保存的value是string類型 * * @param s * @param o * @return * @throws CacheException */ @Override public Object put(Object s, Object o) throws CacheException { JedisClient.setValue(objectToBytes(s), objectToBytes(o), (int) cacheExpireTime.getSeconds()); return s; } public byte[] objectToBytes(Object object) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); byte[] bytes = null; try { ObjectOutputStream op = new ObjectOutputStream(outputStream); op.writeObject(object); bytes = outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return bytes; } public Object bytesToObject(byte[] bytes) { ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); Object object = null; try { ObjectInputStream ois = new ObjectInputStream(inputStream); object = ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return object; } /** * 刪除緩存,根據key * * @param s * @return * @throws CacheException */ @Override public Object remove(Object s) throws CacheException { return JedisClient.delkey(objectToBytes(s)); } /** * 清空全部的緩存 * * @throws CacheException */ @Override public void clear() throws CacheException { JedisClient.clear(); } /** * 緩存的個數 * * @return */ @Override public int size() { return JedisClient.getJedis().dbSize().intValue(); // return redisTemplate.getConnectionFactory().getConnection().dbSize().intValue(); } @Override public Set keys() { return JedisClient.getJedis().keys("*"); } @Override public Collection values() { return null; } }
注意上面objectToBytes和bytesToObject方法是先將session轉換成字節數組而後再存到redis中,從redis拿出來也是將字節數組轉換成session對象,不然會報錯。這是由於shrio使用的是本身包的simpleSession類,而這個類中的字段都是transient,不能直接序列化,須要咱們本身將每一個對象轉成字節數組才能夠進行操做。 固然,若是咱們使用的是RedisTemplate,在配置的時候咱們就不用寫這兩個方法了,直接使用默認的JDK序列化方式便可。
private transient Serializable id; private transient Date startTimestamp; private transient Date stopTimestamp; private transient Date lastAccessTime; private transient long timeout; private transient boolean expired; private transient String host; private transient Map<Object, Object> attributes;
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qzwang.common.cache.config.MyCacheManager
<dependencies> <dependency> <groupId>com.qzwang</groupId> <artifactId>user-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--dubbo--> <dependency> <groupId>com.gitee.reger</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.1.3</version> </dependency> <!--加入共享會話緩存模塊--> <dependency> <groupId>com.qzwang</groupId> <artifactId>common-cache</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
import com.alibaba.dubbo.config.annotation.Reference; import com.qzwang.user.api.service.UserService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class UserRealm extends AuthorizingRealm { @Reference(version = "0.0.1") private UserService userService; // 受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //獲取用戶名 String userName = (String) principalCollection.getPrimaryPrincipal(); SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo(); System.out.println("username=" + userName); //給用戶設置角色 authenticationInfo.setRoles(userService.selectRolesByUsername(userName)); //給用戶設置權限 authenticationInfo.setStringPermissions(userService.selectPermissionByUsername(userName)); return authenticationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return null; } }
import com.qzwang.common.cache.config.MyCacheManager; import com.qzwang.common.cache.config.MySessionDao; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; 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.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { // ShiroFilterFactoryBean @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 攔截 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); //shiroFilterFactoryBean.setLoginUrl("/user/index"); // 設置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; } // DefaultWebSecurityManager // @Qualifier中能夠直接是bean的方法名,也能夠給bean設置一個name,好比@Bean(name="myRealm"),在@Qulifier中就能夠經過name來獲取這個bean @Bean(name = "SecurityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm, @Qualifier("myDefaultWebSessionManager") DefaultWebSessionManager defaultWebSessionManager, @Qualifier("myCacheManager") MyCacheManager myCacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 關聯UserRealm securityManager.setRealm(userRealm); securityManager.setSessionManager(defaultWebSessionManager); securityManager.setCacheManager(myCacheManager); return securityManager; } // 建立Realm對象, 須要自定義類 @Bean public UserRealm userRealm() { return new UserRealm(); } /** * 下面DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor必須定義, * 不然不能使用@RequiresRoles和@RequiresPermissions * * @return */ @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 設置自定義session管理器 */ @Bean public DefaultWebSessionManager myDefaultWebSessionManager(SimpleCookie simpleCookie) { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); defaultWebSessionManager.setSessionIdCookie(simpleCookie); defaultWebSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO()); return defaultWebSessionManager; } @Bean public SimpleCookie simpleCookie() { SimpleCookie simpleCookie = new SimpleCookie("myCookie"); simpleCookie.setPath("/"); simpleCookie.setMaxAge(30); return simpleCookie; } }
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.qzwang</groupId> <artifactId>user-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- dubbo+zookeeper+zkclient --> <dependency> <groupId>com.gitee.reger</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.2</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency> <!--導入緩存管理--> <dependency> <groupId>com.qzwang</groupId> <artifactId>common-cache</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
import com.alibaba.dubbo.config.annotation.Reference; import com.qzwang.user.api.model.User; import com.qzwang.user.api.service.UserService; 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.springframework.stereotype.Component; @Component public class UserRealm extends AuthorizingRealm { @Reference(version = "0.0.1") private UserService userService; // 受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //獲取用戶名 String userName = (String) principalCollection.getPrimaryPrincipal(); System.out.println("userName=" + userName); SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo(); //給用戶設置角色 authenticationInfo.setRoles(userService.selectRolesByUsername(userName)); //給用戶設置權限 authenticationInfo.setStringPermissions(userService.selectPermissionByUsername(userName)); return authenticationInfo; } // 認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String userName = (String) authenticationToken.getPrincipal(); User user = userService.selectByUsername(userName); if (user != null) { AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm"); return authenticationInfo; } return null; } }
import com.qzwang.common.cache.config.MyCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; 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.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { // ShiroFilterFactoryBean @Bean(name = "shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 設置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); // 添加shiro的內置過濾器 /* anon: 無需認證就能訪問 authc: 必須認證了才能訪問 UserController: 必須擁有 記住我 功能才能訪問 perms: 擁有某個資源權限才能訪問 role: 擁有某個角色權限才能訪問 */ // 攔截 Map<String, String> filterMap = new LinkedHashMap<>(); // 受權 // filterMap.put("/UserController/add", "perms[UserController:add]"); filterMap.put("/user/testFunc", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); // 設置未受權頁面 shiroFilterFactoryBean.setUnauthorizedUrl("/user/unAuth"); // 設置登陸的請求 // shiroFilterFactoryBean.setLoginUrl("/user/index"); return shiroFilterFactoryBean; } // DefaultWebSecurityManager // @Qualifier中能夠直接是bean的方法名,也能夠給bean設置一個name,好比@Bean(name="myRealm"),在@Qulifier中就能夠經過name來獲取這個bean @Bean(name = "SecurityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm, @Qualifier("myDefaultWebSessionManager") DefaultWebSessionManager defaultWebSessionManager, @Qualifier("myCacheManager") MyCacheManager myCacheManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 關聯UserRealm securityManager.setRealm(userRealm); securityManager.setCacheManager(myCacheManager); securityManager.setSessionManager(defaultWebSessionManager); return securityManager; } /** * 下面DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor必須定義, * 不然不能使用@RequiresRoles和@RequiresPermissions * * @return */ @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 設置自定義session管理器 */ @Bean public DefaultWebSessionManager myDefaultWebSessionManager(SimpleCookie simpleCookie) { DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager(); defaultWebSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO()); defaultWebSessionManager.setSessionIdCookie(simpleCookie); return defaultWebSessionManager; } @Bean public SimpleCookie simpleCookie() { SimpleCookie simpleCookie = new SimpleCookie("myCookie"); simpleCookie.setPath("/"); simpleCookie.setMaxAge(30); return simpleCookie; } }
import com.qzwang.common.core.config.ExceptionConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.Properties; @Configuration public class AuthorizationExceptionConfig { Logger logger = LoggerFactory.getLogger(ExceptionConfig.class); /** * 捕獲未認證的方法 * * @return */ @Bean public SimpleMappingExceptionResolver simpleMappingExceptionResolver() { SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver(); Properties properties = new Properties(); properties.setProperty("org.apache.shiro.authz.AuthorizationException", "/user/unAuth"); simpleMappingExceptionResolver.setExceptionMappings(properties); return simpleMappingExceptionResolver; } } ``` - 用戶登陸接口以下: ```java @RestController @RequestMapping("/user") public class UserController { @Reference(version = "0.0.1") private UserService userService; @RequestMapping(value = "/login", method = RequestMethod.POST) public R login(@RequestBody User user) { UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); return R.ok(); } catch (Exception e) { e.printStackTrace(); return R.failed(); } } @RequestMapping(value = "/unAuth", method = RequestMethod.GET) public R unAuth() { return R.failed("該用戶未受權!"); } @RequiresRoles("admin") @RequestMapping(value = "/testFunc", method = RequestMethod.GET) public R testFunc() { return R.ok("yes success!!!"); } }
一、用戶先登陸。
二、訪問/user/testFunc接口,注意此接口須要admin角色,可是如今數據庫中zhangsan用戶並無該角色,所以也就沒有權限訪問該接口。
三、如今在數據庫中給zhangsan添加一個admin角色,再進行測試。
這個服務我主要測試一下是否能夠實現共享session會話,實現權限控制。
<dependencies> <dependency> <groupId>com.qzwang</groupId> <artifactId>common-auth</artifactId> <version>0.0.1</version> </dependency> <!-- dubbo+zookeeper+zkclient --> <dependency> <groupId>com.gitee.reger</groupId> <artifactId>spring-boot-starter-dubbo</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.2</version> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.11</version> </dependency> </dependencies>
@RestController @RequestMapping("/video") public class VideoController { @RequestMapping("/getVideo") public R getVideo() { return R.ok(); } }
能夠看到它跳到shrio默認的登陸頁面去了。下面咱們再測試登陸成功以後在訪問該接口。
能夠看到,用戶的會話信息是實現共享了,下面再測試給該接口加權限試試。
@RestController @RequestMapping("/video") public class VideoController { @RequestMapping("/getVideo") @RequiresRoles("admin") public R getVideo() { return R.ok(); } }
在zhangsan沒有權限的狀況下是不能訪問該接口的。
因爲上面配置的未受權接口/user/unAuth是在用戶服務中,提示找不到該接口,這裏須要給這些微服務配置一個網關gateway(這裏就不展開怎麼配置了,這不是本篇的重點)。上面當用戶有admin角色時訪問該接口測試以下。
所以通過測試公共模塊common-Auth實現了用戶會話和權限realm數據的redis共享,簡直完美!!!
原文見個人CSDN原博客連接https://blog.csdn.net/to10086/article/details/109573948。