javascript
Spring Boot 整合 Shiro
雖然,直接用Spring Security和SpringBoot 進行“全家桶式”的合作是最好不過的,但現實總是欺負我們這些沒辦法決定架構類型的娃子。
Apache Shiro 也有其特殊之處滴。若需了解,可以轉戰到[Apache Shiro 簡介]
1. 添加Shiro依賴
shiro的版本,看個人喜好哈,本文的版本為:
<shiro.version>1.3.2</shiro.version> 復制代碼<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>${shiro.version}</version> </dependency> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>${shiro.version}</version> </dependency> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-aspectj</artifactId><version>${shiro.version}</version> </dependency> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>${shiro.version}</version> </dependency> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-quartz</artifactId><version>${shiro.version}</version> </dependency> <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version> </dependency> 復制代碼2. shiroRealm
授權認證具體實現之地。通過繼承 AuthorizingRealm 進而實現,對登錄時的賬號密碼校驗功能
4j public class ShiroRealm extends AuthorizingRealm {private ShiroPermissionRepository shiroPermissionRepository;/*** 授權** @param principalCollection 主要信息* @return 授權信息*/protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {if (log.isInfoEnabled()){log.info("Authorization begin");}String name= (String) principalCollection.getPrimaryPrincipal();List<String> role = shiroPermissionRepository.queryRoleByName(name);if (role.isEmpty()){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRoles(role);return simpleAuthorizationInfo;}return null;}/*** 認證** @param authenticationToken 認證token* @return 認證結果* @throws AuthenticationException 認證異常*/protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {if (log.isInfoEnabled()){log.info("Authentication begin");}UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;Object principal =token.getPrincipal();Object credentials = token.getCredentials();//校驗用戶名checkBlank(principal,"用戶名不能為空");//校驗密碼checkBlank(credentials,"密碼不能為空");//校驗姓名String username = (String) principal;UserPO userPO = shiroPermissionRepository.findAllByName(username);if (userPO == null){throw new AccountException("用戶名錯誤");}//校驗密碼String password = (String) credentials;if (!StringUtils.equals(password,userPO.getPassword())){throw new AccountException("密碼錯誤");}return new SimpleAuthenticationInfo(principal, password, getName());}private void checkBlank(Object obj,String message){if (obj instanceof String){if (StringUtils.isBlank((String) obj)){throw new AccountException(message);}}else if (obj == null){throw new AccountException(message);}} } 復制代碼3. 配置ShiroConfig
將ShiroConfig、SecurityManager、ShiroFilterFactoryBean交給Spring管理.
- ShiroRealm: 則上述所描述的ShiroRealm
- SecurityManager: 管理 所有用戶 的安全操作
- ShiroFilterFactoryBean: 配置Shiro的過濾器
3.1 Shiro 過濾器小插曲
shiro和security也有相似之處,都有自己的 filter chain。翻一番Shiro的源碼,追溯一下。發下以下:
3.1.1 ShiroFilterFactoryBean——createFilterChainManager
protected FilterChainManager createFilterChainManager() {DefaultFilterChainManager manager = new DefaultFilterChainManager();Map<String, Filter> defaultFilters = manager.getFilters();//apply global settings if necessary:for (Filter filter : defaultFilters.values()) {applyGlobalPropertiesIfNecessary(filter);}//Apply the acquired and/or configured filters:Map<String, Filter> filters = getFilters();if (!CollectionUtils.isEmpty(filters)) {for (Map.Entry<String, Filter> entry : filters.entrySet()) {String name = entry.getKey();Filter filter = entry.getValue();applyGlobalPropertiesIfNecessary(filter);if (filter instanceof Nameable) {((Nameable) filter).setName(name);}//'init' argument is false, since Spring-configured filters should be initialized//in Spring (i.e. 'init-method=blah') or implement InitializingBean:manager.addFilter(name, filter, false);}}//build up the chains:Map<String, String> chains = getFilterChainDefinitionMap();if (!CollectionUtils.isEmpty(chains)) {for (Map.Entry<String, String> entry : chains.entrySet()) {String url = entry.getKey();String chainDefinition = entry.getValue();manager.createChain(url, chainDefinition);}}return manager;} 復制代碼從源碼可以發現,shiro的過濾器鏈,添加順序是:
3.1.2 DefaultFilterChainManager —— addDefaultFilters
這里咱看看DefaultFilterChainManager 到底添加了那些默認過濾器鏈,可以看到主要的是:DefaultFilter
protected void addDefaultFilters(boolean init) {for (DefaultFilter defaultFilter : DefaultFilter.values()) {addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);} } 復制代碼3.1.3 DefaultFilter
anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class); 復制代碼4. 測它
由于設置對全局接口進行校驗,因此,預期結果就是不能夠訪問啦
map.put("/api/**",AUTHC_STR); 復制代碼4.1 IDAL
( SYSTEM_API +"shiro") public class ShiroIdal {private IShiroService iShiroService;public HttpEntity obtain(@RequestParam String name){return iShiroService.obtainUserByName(name);} } 復制代碼4.2 service
4j public class ShiroServiceImpl implements IShiroService {private ShiroPermissionRepository shiroPermissionRepository;public HttpEntity obtainUserByName(String name) {UserPO userPO = shiroPermissionRepository.findAllByName(name);return HttpResponseSupport.success(userPO);} } 復制代碼4.3 被劫持的情況
若沒 login.jsp,則會直接報錯,個人覺得太不和諧了,畢竟現在都是前后端分離的。
4.4 設置允許訪問
在URI過濾Map加入以下:
map.put("/api/shiro",ANON_STR); 復制代碼注意: 要在“全局Api劫持”前添加。而且不要使用“HashMap”,為什么?
4.4.1 HashMap
在說為什么前,先了解HashMap這貨是什么原理先。
for (Entry<String, String> entry : hashMap.entrySet()) {MessageFormat.format("{0}={1}",entry.getKey(),entry.getValue()); } 復制代碼HashMap散列圖是按“有利于隨機查找的散列(hash)的順序”。并非按輸入順序。遍歷時只能全部輸出,而沒有順序。甚至可以rehash()重新散列,來獲得更利于隨機存取的內部順序。
這會影響shiro哪里呢?
Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) {for (Map.Entry<String, String> entry : chains.entrySet()) {String url = entry.getKey();String chainDefinition = entry.getValue();manager.createChain(url, chainDefinition);} } 復制代碼ShiroFilterFactoryBean 中,在構建shiro的filter chain時,會對我們配置的FilterChainDefinitionMap 進行一次遍歷,并且將其添加到DefaultFilterChainManager中。
設想以下,若“全局API劫持”在最前面,那么只要在/api/*襠下的,都早早被劫持了。輪得到配置的 anon 么?若由于HashMap的散列排序導致“全局API劫持”在最前面,emmmm,那玩錘子。
4.4.2 LinkedHashMap
因此,建議使用LinkedHashMap,為啥子?擼源碼
static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}transient LinkedHashMap.Entry<K,V> head;transient LinkedHashMap.Entry<K,V> tail; 復制代碼內部類中多了兩個Entry,一個記錄前方entry,一個記錄后方entry,這樣的雙向鏈表結構保證了插入順序的有序。
LinkedHashMap底層是數組加單項鏈表加雙向鏈表 。
- 數組加單向鏈表就是HashMap的結構,記錄數據用,
- 雙向鏈表,存儲插入順序用。
有點跑偏了,這些大伙肯定都知道滴了......
轉載于:https://juejin.im/post/5ce51e735188253051636dfb
總結
以上是生活随笔為你收集整理的Spring Boot 整合 Shiro的全部內容,希望文章能夠幫你解決所遇到的問題。