/* * Copyright 2017-2018 the original author(https://github.com/wj596) * * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * </p> */ package org.jsets.shiro.config; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; import javax.servlet.Filter; import org.apache.commons.collections.MapUtils; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.CodecSupport; import org.apache.shiro.mgt.RememberMeManager; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.session.SessionListener; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.apache.shiro.web.servlet.Cookie; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.jsets.shiro.api.CaptchaProvider; import org.jsets.shiro.api.PasswordProvider; import org.jsets.shiro.api.ShiroAccountProvider; import org.jsets.shiro.api.ShiroCustomizer; import org.jsets.shiro.api.ShiroFilteRulesProvider; import org.jsets.shiro.api.ShiroStatelessAccountProvider; import org.jsets.shiro.cache.CacheDelegator; import org.jsets.shiro.cache.MapCacheManager; import org.jsets.shiro.cache.RedisCacheManager; import org.jsets.shiro.config.internal.DefaultCaptchaProvider; import org.jsets.shiro.config.internal.DefaultPasswordProvider; import org.jsets.shiro.config.internal.DefaultShiroAccountProvider; import org.jsets.shiro.config.internal.DefaultShiroStatelessAccountProvider; import org.jsets.shiro.filter.ForceLogoutFilter; import org.jsets.shiro.filter.JcaptchaFilter; import org.jsets.shiro.filter.JsetsFormAuthenticationFilter; import org.jsets.shiro.filter.JsetsLogoutFilter; import org.jsets.shiro.filter.JsetsPermissionsAuthorizationFilter; import org.jsets.shiro.filter.JsetsRolesAuthorizationFilter; import org.jsets.shiro.filter.JsetsUserFilter; import org.jsets.shiro.filter.KeepOneUserFilter; import org.jsets.shiro.filter.stateless.HmacAuthcFilter; import org.jsets.shiro.filter.stateless.HmacPermsFilter; import org.jsets.shiro.filter.stateless.HmacRolesFilter; import org.jsets.shiro.filter.stateless.JwtAuthcFilter; import org.jsets.shiro.filter.stateless.JwtPermsFilter; import org.jsets.shiro.filter.stateless.JwtRolesFilter; import org.jsets.shiro.listener.AuthListenerManager; import org.jsets.shiro.listener.DefaultSessionListener; import org.jsets.shiro.listener.PasswdRetryLimitListener; import org.jsets.shiro.model.AuthorizeRule; import org.jsets.shiro.model.CustomRule; import org.jsets.shiro.model.RolePermRule; import org.jsets.shiro.realm.BooleanMatcher; import org.jsets.shiro.realm.HmacRealm; import org.jsets.shiro.realm.JwtRealm; import org.jsets.shiro.realm.UsernamePasswordRealm; import org.jsets.shiro.realm.UsernameRealm; import org.jsets.shiro.util.CommonUtils; import org.jsets.shiro.util.RedisUtils; import org.springframework.cache.ehcache.EhCacheCacheManager; import com.google.common.collect.Maps; /** * shiro配置 * * @author wangjie (https://github.com/wj596) * @date 2016年6月31日 */ public class ShiroConfig { private ShiroProperties properties; private ShiroCustomizer customizer; private org.springframework.cache.CacheManager springCacheManager; private DefaultWebSessionManager sessionManager; private RememberMeManager rememberMeManager; private CacheManager cacheManager; private CacheDelegator cacheDelegator; private PasswordProvider passwordProvider; private CaptchaProvider captchaProvider; private ShiroAccountProvider accountProvider; private ShiroStatelessAccountProvider statelessAccountProvider; private ShiroFilteRulesProvider rulesProvider; private PasswdRetryLimitListener limitListener; private final CredentialsMatcher booleanMatcher = new BooleanMatcher(); private final Map<String,Realm> realms = Maps.newHashMap(); private final Map<String, Filter> filters = Maps.newHashMap(); private final Map<String, String> staticFilteRules = Maps.newLinkedHashMap(); private final Map<String, String> dynamicFilteRules = Maps.newLinkedHashMap(); private final Object reloadMonitor = new Object(); protected void afterPropertiesSet() { if(Objects.isNull(this.customizer)) { this.customizer = new ShiroCustomizer(); } this.captchaProvider = this.customizer.getCaptchaProvider(); if(Objects.isNull(this.captchaProvider)) { this.captchaProvider = new DefaultCaptchaProvider(); } this.passwordProvider = this.customizer.getPasswordProvider(); if(Objects.isNull(this.passwordProvider)) { this.passwordProvider = new DefaultPasswordProvider(this.properties); } this.accountProvider = this.customizer.getShiroAccountProvider(); if(Objects.isNull(this.accountProvider)) this.accountProvider = new DefaultShiroAccountProvider(); this.statelessAccountProvider = this.customizer.getShiroStatelessAccountProvider(); if(Objects.isNull(this.statelessAccountProvider)) this.statelessAccountProvider = new DefaultShiroStatelessAccountProvider(this.accountProvider); this.limitListener = this.customizer.getPasswdRetryLimitListener(); this.rulesProvider = this.customizer.getShiroFilteRulesProvider(); biuldSessionManager(); biuldRememberMeManager(); biuldCacheManager(); biuldRealms(); biuldFilters(); biuldFilteRules(); } private void biuldSessionManager() { SessionDAO sessionDAO = this.customizer.getSessionDAO(); if(Objects.isNull(sessionDAO)) sessionDAO = new EnterpriseCacheSessionDAO(); List<SessionListener> sessionListeners = this.customizer.getSessionListeners(); DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(this.properties.getSessionTimeout()); sessionManager.setSessionIdUrlRewritingEnabled(Boolean.FALSE); sessionManager.setSessionValidationInterval(this.properties.getSessionValidationInterval()); sessionManager.setSessionDAO(sessionDAO); sessionManager.getSessionListeners().addAll(sessionListeners); sessionManager.getSessionListeners().add(new DefaultSessionListener()); this.sessionManager = sessionManager; } private void biuldRememberMeManager() { Cookie rememberMeCookie = this.customizer.getRememberMeCookie(); if(Objects.isNull(rememberMeCookie)) { rememberMeCookie = new SimpleCookie(); rememberMeCookie.setName(CommonUtils.REMEMBERME_COOKIE_NAME); rememberMeCookie.setHttpOnly(Boolean.TRUE); rememberMeCookie.setMaxAge(this.properties.getRemembermeMaxAge()); } CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); rememberMeManager.setCipherKey(CodecSupport.toBytes(this.properties.getRemembermeSecretKey())); rememberMeManager.setCookie(rememberMeCookie); this.rememberMeManager = rememberMeManager; } private void biuldCacheManager() { this.cacheManager = this.customizer.getCacheManager(); if(Objects.isNull(this.cacheManager)) { if(Objects.isNull(springCacheManager)) { this.cacheManager = new MapCacheManager(); }else { if (springCacheManager instanceof EhCacheCacheManager) { EhCacheManager ehCacheManager = new EhCacheManager(); ehCacheManager.setCacheManager(((EhCacheCacheManager) springCacheManager).getCacheManager()); this.cacheManager = ehCacheManager; } if (springCacheManager instanceof org.springframework.data.redis.cache.RedisCacheManager) { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisTemplate(RedisUtils.imitateRedisTemplate()); this.cacheManager = redisCacheManager; } } } this.cacheDelegator = new CacheDelegator(this.cacheManager); } private void biuldRealms() { Map<String,Realm> customizedRealms = this.customizer.getRealms(); UsernamePasswordRealm usernamePasswordRealm = new UsernamePasswordRealm( this.properties,this.cacheDelegator,this.accountProvider,this.limitListener); usernamePasswordRealm.setCredentialsMatcher(booleanMatcher); if (this.properties.isAuthCacheEnabled()) { usernamePasswordRealm.setAuthorizationCacheName(ShiroProperties.CACHE_NAME_AUTHORIZATION); usernamePasswordRealm.setAuthenticationCacheName(ShiroProperties.CACHE_NAME_AUTHENTICATION); usernamePasswordRealm.setCachingEnabled(Boolean.TRUE); usernamePasswordRealm.setAuthenticationCachingEnabled(Boolean.TRUE); usernamePasswordRealm.setAuthorizationCachingEnabled(Boolean.TRUE); } else { usernamePasswordRealm.setCachingEnabled(Boolean.FALSE); } this.realms.put("usernamePasswordRealm", usernamePasswordRealm); if (this.properties.isHmacEnabled()) { HmacRealm hmacRealm = new HmacRealm( this.properties,this.cacheDelegator,this.statelessAccountProvider); hmacRealm.setCredentialsMatcher(booleanMatcher); hmacRealm.setCachingEnabled(Boolean.FALSE); this.realms.put("hmacRealm", hmacRealm); } if (this.properties.isJwtEnabled()) { JwtRealm jwtRealm = new JwtRealm( this.properties,this.cacheDelegator,this.statelessAccountProvider); jwtRealm.setCredentialsMatcher(booleanMatcher); jwtRealm.setCachingEnabled(Boolean.FALSE); this.realms.put("jwtRealm", jwtRealm); } if (this.properties.isFreePasswordEnabled()||this.properties.isJssoClient()) { UsernameRealm usernameRealm = new UsernameRealm(this.properties,this.accountProvider); usernameRealm.setCredentialsMatcher(booleanMatcher); usernameRealm.setCachingEnabled(Boolean.FALSE); this.realms.put("usernameRealm", usernameRealm); } if(MapUtils.isNotEmpty(customizedRealms)) { boolean errorExist = customizedRealms.values().stream() .filter(r-> (r instanceof AuthenticatingRealm)) .anyMatch(r->Objects.isNull(((AuthenticatingRealm)r).getCredentialsMatcher())); if(errorExist) throw new IllegalConfigException("Realm 必须有对应的CredentialsMatcher"); customizedRealms.forEach((k,v)->this.realms.put(k, v)); } } private void biuldFilters() { Map<String,Filter> customizedFilters = this.customizer.getFilters(); AuthListenerManager authListenerManager = this.customizer.getAuthListenerManager(); if (this.properties.isJcaptchaEnable()) { JcaptchaFilter jcaptchaFilter = new JcaptchaFilter(this.captchaProvider); this.filters.putIfAbsent(CommonUtils.FILTER_JCAPTCHA, jcaptchaFilter); } if (this.properties.isKeepOneEnabled()) { KeepOneUserFilter keepOneFilter = new KeepOneUserFilter(properties,cacheDelegator,sessionManager,authListenerManager); this.filters.putIfAbsent(CommonUtils.FILTER_KEEP_ONE, keepOneFilter); } if (this.properties.isForceLogoutEnable()) { ForceLogoutFilter forceFilter = new ForceLogoutFilter(this.properties,authListenerManager); this.filters.putIfAbsent(CommonUtils.FILTER_FORCE_LOGOUT, forceFilter); } if (this.properties.isHmacEnabled()) { HmacAuthcFilter hmacFilter = new HmacAuthcFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_HMAC, hmacFilter); HmacRolesFilter hmacRolesFilter = new HmacRolesFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_HMAC_ROLES, hmacRolesFilter); HmacPermsFilter hmacPermsFilter = new HmacPermsFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_HMAC_PERMS, hmacPermsFilter); } if (this.properties.isJwtEnabled()) { JwtAuthcFilter jwtFilter = new JwtAuthcFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_JWT, jwtFilter); JwtRolesFilter jwtRolesFilter = new JwtRolesFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_JWT_ROLES, jwtRolesFilter); JwtPermsFilter jwtPermsFilter = new JwtPermsFilter(); this.filters.putIfAbsent(CommonUtils.FILTER_JWT_PERMS, jwtPermsFilter); } this.filters.putIfAbsent(CommonUtils.FILTER_AUTHC, new JsetsFormAuthenticationFilter(this.properties,this.captchaProvider,authListenerManager)); this.filters.putIfAbsent(CommonUtils.FILTER_LOGOUT, new JsetsLogoutFilter(authListenerManager)); this.filters.putIfAbsent(CommonUtils.FILTER_ROLES, new JsetsRolesAuthorizationFilter(authListenerManager)); this.filters.putIfAbsent(CommonUtils.FILTER_PERMS, new JsetsPermissionsAuthorizationFilter()); this.filters.putIfAbsent(CommonUtils.FILTER_USER, new JsetsUserFilter(this.accountProvider)); if(MapUtils.isNotEmpty(customizedFilters)) customizedFilters.forEach((k,v)->this.filters.put(k, v)); } private void biuldFilteRules() { this.biuldStaticFilteRules(); this.biuldDynamicFilteRules(); } private void biuldStaticFilteRules() { ShiroProperties.DEFAULT_IGNORED .forEach(ignored -> this.staticFilteRules.put(ignored, CommonUtils.FILTER_ANON)); if(CommonUtils.hasLen(this.properties.getKickoutUrl())) this.staticFilteRules.put(properties.getKickoutUrl(), CommonUtils.FILTER_ANON); if(CommonUtils.hasLen(properties.getForceLogoutUrl())) this.staticFilteRules.put(properties.getForceLogoutUrl(), CommonUtils.FILTER_ANON); if (this.properties.isJcaptchaEnable()) this.staticFilteRules.put(CommonUtils.JCAPTCHA_URL, CommonUtils.FILTER_JCAPTCHA); if(!this.properties.getFilteRules().isEmpty()) this.properties.getFilteRules().forEach(rule->{ if(rule.split("-->").length!=2) throw new IllegalConfigException("过滤规则配置不正确,格式:url->filters"); Stream.of(rule.split("-->")[0].split(",")) .forEach(url->this.staticFilteRules.put(url, rule.split("-->")[1])); }); } private void attachFilters(StringBuilder filterChain){ if (this.properties.isJssoClient()) filterChain.append(","+CommonUtils.FILTER_JSSO_CLIENT); filterChain.append(","+CommonUtils.FILTER_USER); if (this.properties.isKeepOneEnabled()) filterChain.append(","+CommonUtils.FILTER_KEEP_ONE); if (this.properties.isForceLogoutEnable()) filterChain.append(","+CommonUtils.FILTER_FORCE_LOGOUT); } private void biuldDynamicFilteRules() { if(Objects.nonNull(this.rulesProvider)) { this.dynamicFilteRules.clear(); List<RolePermRule> rolePermRules = this.rulesProvider.loadRolePermRules(); if(null != rolePermRules) rolePermRules.forEach(rule -> { rule.setType(AuthorizeRule.RULE_TYPE_DEF); StringBuilder filterChain = rule.toFilterChain(); if(null != filterChain){ this.attachFilters(filterChain); this.dynamicFilteRules.putIfAbsent(rule.getUrl(), filterChain.toString()); } }); List<RolePermRule> hmacRules = rulesProvider.loadHmacRules(); if(null != hmacRules) hmacRules.forEach(rule -> { rule.setType(AuthorizeRule.RULE_TYPE_HMAC); StringBuilder filterChain = rule.toFilterChain(); if(null != filterChain) this.dynamicFilteRules.putIfAbsent(rule.getUrl(), filterChain.toString()); }); List<RolePermRule> jwtRules = rulesProvider.loadJwtRules(); if(null != jwtRules) jwtRules.forEach(rule -> { rule.setType(AuthorizeRule.RULE_TYPE_JWT); StringBuilder filterChain = rule.toFilterChain(); if(null != filterChain) this.dynamicFilteRules.putIfAbsent(rule.getUrl(), filterChain.toString()); }); List<CustomRule> customRules = rulesProvider.loadCustomRules(); if(null != customRules) customRules.forEach(rule -> { rule.setType(AuthorizeRule.RULE_TYPE_CUSTOM); StringBuilder filterChain = rule.toFilterChain(); if(null != filterChain){ this.attachFilters(filterChain); this.dynamicFilteRules.putIfAbsent(rule.getUrl(), filterChain.toString()); } }); } } public void reloadFilterRules(ShiroFilterFactoryBean factoryBean) { synchronized (this.reloadMonitor) { AbstractShiroFilter abstractShiroFilter = null; try { abstractShiroFilter = (AbstractShiroFilter) factoryBean.getObject(); PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) abstractShiroFilter.getFilterChainResolver(); DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager(); filterChainManager.getFilterChains().clear(); factoryBean.getFilterChainDefinitionMap().clear(); this.biuldDynamicFilteRules(); factoryBean.setFilterChainDefinitionMap(this.getRules()); factoryBean.getFilterChainDefinitionMap().forEach((k,v) -> filterChainManager.createChain(k, v)); } catch (Exception e) { throw new RuntimeException(e.getMessage(),e); } } } protected void setProperties(ShiroProperties properties) { this.properties = properties; } protected void setCustomizer(ShiroCustomizer customizer) { this.customizer = customizer; } protected void setSpringCacheManager(org.springframework.cache.CacheManager springCacheManager) { this.springCacheManager = springCacheManager; } public DefaultWebSessionManager getSessionManager() { return this.sessionManager; } public RememberMeManager getRememberMeManager() { return this.rememberMeManager; } public CacheManager getCacheManager() { return this.cacheManager; } public Collection<Realm> getRealms() { return this.realms.values(); } public Map<String, Filter> getFilters() { return this.filters; } public Map<String, String> getRules() { Map<String, String> rules = Maps.newLinkedHashMap(); rules.putAll(Collections.unmodifiableMap(this.dynamicFilteRules)); rules.putAll(Collections.unmodifiableMap(this.staticFilteRules)); return rules; } public PasswordProvider getPasswordProvider() { return this.passwordProvider; } public CacheDelegator getCacheDelegator() { return this.cacheDelegator; } }