前後端分離項目中,由於跨域,會導致複雜請求,即會發送preflighted request,這樣會導致在GET/POST等請求之前會先發一個OPTIONS請求,但OPTIONS請求並不帶shiro的’Authorization’字段(shiro的Session),即OPTIONS請求不能通過shiro驗證,會返回未認證的信息。
解決方案爲繼承shiro的DefaultWebSessionManager類重寫獲取session的方法
public class MySessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public MySessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果請求頭中有 Authorization 則其值爲sessionId
if (!StrKit.isEmpty(id)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
} else {
//否則按默認規則從cookie取sessionId
return super.getSessionId(request, response);
}
}
}
然後在shiro配置類中將我們自定義的session管理類註冊進去
public class ShiroConfiguration {
@Autowired
private IResourceService resourceService;
@Bean("lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroFilterFactoryBean 處理攔截資源文件問題。
* 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,因爲在
* 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager
* <p>
* Filter Chain定義說明
* 1、一個URL可以配置多個Filter,使用逗號分隔
* 2、當設置多個過濾器時,全部驗證通過,才視爲通過
* 3、部分過濾器可指定參數,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
Map<String, Filter> filters = new HashMap<String, Filter>();
filters.put("admin", new AdminFormAuthenticationFilter());
shiroFilterFactoryBean.setFilters(filters);
//攔截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了
filterChainDefinitionMap.put("/assets/**", "anon");
filterChainDefinitionMap.put("/**/favicon.ico", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/websocket/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/**", "admin");
List<Resource> menuList = resourceService.selectAll();
for (Resource menu : menuList) {
if (!StringUtils.isEmpty(menu.getUrl()) && !StringUtils.isEmpty(menu.getCode())) {
String permission = "authc,perms[" + menu.getCode() + "]";
filterChainDefinitionMap.put(menu.getUrl(), permission);
}
}
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
Map<String, Realm> shiroAuthenticatorRealms = new HashMap<>();
shiroAuthenticatorRealms.put(LoginType.ADMIN.getType(), dBShiroRealm());
CustomModularRealmAuthenticator customModularRealmAuthenticator = new CustomModularRealmAuthenticator();
customModularRealmAuthenticator.setDefinedRealms(shiroAuthenticatorRealms);
customModularRealmAuthenticator.setAuthenticationStrategy(authenticationStrategy());
securityManager.setAuthenticator(customModularRealmAuthenticator);
ModularRealmAuthorizer customModularRealmAuthorizer = new ModularRealmAuthorizer();
customModularRealmAuthorizer.setRealms(shiroAuthenticatorRealms.values());
securityManager.setAuthorizer(customModularRealmAuthorizer);
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(shiroRedisCacheManager());
return securityManager;
}
@Bean
public DBShiroRealm dBShiroRealm() {
DBShiroRealm dbShiroRealm = new DBShiroRealm();
dbShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
dbShiroRealm.setCacheManager(shiroRedisCacheManager());
dbShiroRealm.setCachingEnabled(true);
return dbShiroRealm;
}
/**
* Shiro默認提供了三種 AuthenticationStrategy 實現:
* AtLeastOneSuccessfulStrategy :其中一個通過則成功。
* FirstSuccessfulStrategy :其中一個通過則成功,但只返回第一個通過的Realm提供的驗證信息。
* AllSuccessfulStrategy :凡是配置到應用中的Realm都必須全部通過。
* authenticationStrategy
*
* @return
*/
@Bean(name = "authenticationStrategy")
public AuthenticationStrategy authenticationStrategy() {
System.out.println("ShiroConfiguration.authenticationStrategy()");
return new FirstSuccessfulStrategy();
}
/**
* 憑證匹配器
* (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
* 所以我們需要修改下doGetAuthenticationInfo中的代碼;
* )
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//散列算法:這裏使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次數,比如散列兩次,相當於 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
/**
* 開啓shiro aop註解支持.
* 使用代理方式;所以需要開啓代碼支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRedisCacheManager shiroRedisCacheManager() {
return new ShiroRedisCacheManager();
}
/**
* shiro session的管理
*/
@Bean("sessionManager")
public MySessionManager sessionManager() {
MySessionManager sessionManager = new MySessionManager();
sessionManager.setGlobalSessionTimeout(1800000 * 2);
return sessionManager;
}
}