項目中使用了 Shiro 進行驗證和授權,下面是 Shiro 配置類給予參考。
后來並沒有使用 Shiro,感覺使用 JWT 還是自己寫攔截器比較靈活,使用 Shiro 后各種地方需要魔改,雖然功能也能實現,但感覺把簡單問題復雜化了,如果單單只使用 Shiro 授權這一塊可以嘗試。
package com.nwgdk.ums.config.shiro;
import com.nwgdk.ums.config.shiro.filter.AccessTokenFilter;
import com.nwgdk.ums.config.shiro.listener.CustomSessionListener;
import com.nwgdk.ums.config.shiro.realm.AdminRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.mgt.DefaultWebSessionStorageEvaluator;
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.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.*;
/**
* @author nwgdk
*/
@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfiguartion.class)
public class ShiroConfiguration {
/**
* Hash迭代次數
*/
@Value("${ums.config.hash.hash-iterations}")
private Integer hashIterations;
/**
* WEB 過濾器鏈
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 設置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager());
// 注冊自定義過濾器
Map<String, Filter> filterMap = new LinkedHashMap<>(8);
filterMap.put("authc", new AccessTokenFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// 定義過濾鏈
Map<String, String> filterChains = new LinkedHashMap<>(8);
filterChains.put("/v1/admin/login", "anon");
filterChains.put("/**", "authc");
// 設置過濾器鏈
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChains);
return shiroFilterFactoryBean;
}
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設置 Session 管理器
securityManager.setSessionManager(sessionManager());
// 設置 Realm
securityManager.setRealm(adminRealm());
// 關閉 RememberMe
securityManager.setRememberMeManager(null);
// 設置自定義 Subject
securityManager.setSubjectFactory(statelessDefaultSubjectFactory());
// 設置 SubjectDao
securityManager.setSubjectDAO(defaultSubjectDAO());
return securityManager;
}
/**
* 自定義 Subject 工廠, 禁止使用 Session
*/
@Bean("subjectFactory")
public StatelessDefaultSubjectFactory statelessDefaultSubjectFactory() {
return new StatelessDefaultSubjectFactory();
}
@Bean
public DefaultSubjectDAO defaultSubjectDAO() {
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
// 設置會話存儲調度器
subjectDAO.setSessionStorageEvaluator(defaultWebSessionStorageEvaluator());
return subjectDAO;
}
/**
* 會話存儲器
*/
@Bean
public DefaultWebSessionStorageEvaluator defaultWebSessionStorageEvaluator() {
DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator();
// 禁用會話存儲
evaluator.setSessionStorageEnabled(false);
return evaluator;
}
/**
* Session 管理器
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 設置 Cookie
sessionManager.setSessionIdCookie(simpleCookie());
// 啟用 Session Id Cookie,默認啟用
sessionManager.setSessionIdCookieEnabled(false);
// 設置全局超時時間,默認30分鍾
sessionManager.setGlobalSessionTimeout(1800000L);
// 設置會話監聽器
sessionManager.setSessionListeners(customSessionListener());
// 禁用 Session 驗證調度器
sessionManager.setSessionValidationSchedulerEnabled(false);
return sessionManager;
}
/**
* 會話監聽器
*/
@Bean
public Collection<SessionListener> customSessionListener() {
List<SessionListener> listeners = new ArrayList<>();
listeners.add(new CustomSessionListener());
return listeners;
}
/**
* Session Cookie
*/
@Bean
public SimpleCookie simpleCookie() {
SimpleCookie cookie = new SimpleCookie();
// Session Cookie 名稱
cookie.setName("SID");
// Session 存活時間
cookie.setMaxAge(10);
// 設置 Cookie 只讀
cookie.setHttpOnly(true);
return cookie;
}
/**
* 憑證匹配器
*/
@Bean("credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列次數
hashedCredentialsMatcher.setHashIterations(hashIterations);
// 使用 HEX 編碼
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 領域對象
*/
@Bean("adminRealm")
public AdminRealm adminRealm() {
AdminRealm adminRealm = new AdminRealm();
// 設置密碼匹配器
adminRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return adminRealm;
}
/**
* 開啟注解 (如 @RequiresRoles, @RequiresPermissions),
* 需借助 SpringAOP 掃描使用 Shiro 注解的類,並在必要時進行安全邏輯驗證
* 配置以下兩個 Bean:
* DefaultAdvisorAutoProxyCreator(可選) 和 AuthorizationAttributeSourceAdvisor 即可實現此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
package com.nwgdk.ums.config.shiro;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author nwgdk
*/
@Configuration
public class ShiroLifecycleBeanPostProcessorConfiguartion {
/**
* Shiro 生命周期處理器
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
package com.nwgdk.ums.config.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
/**
* 自定義 Subject
*
* @author nwgdk
*/
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 禁止 Subject 創建會話
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}