springboot集成shiro遭遇自定義filter異常的解決
springboot集成shiro遭遇自定義filter異常
首先簡述springboot使用maven集成shiro
1、用maven添加shiro
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
2、配置shiro
import com.yuntu.intelligent.log.service.QueryPermissionService; import com.yuntu.intelligent.log.service.shiro.authc.AccountSubjectFactory; import com.yuntu.intelligent.log.service.shiro.filter.AuthenticatedFilter; import com.yuntu.intelligent.log.service.shiro.filter.QueryLimitFiter; import com.yuntu.intelligent.log.service.shiro.realm.AccountRealm; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.Cookie; import org.apache.shiro.web.servlet.ShiroHttpSession; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * shiro權(quán)限管理的配置 */ @Configuration public class ShiroConfig { @Bean public AccountSubjectFactory accountSubjectFactory() { return new AccountSubjectFactory(); } /** * 安全管理器 */ @Bean public DefaultWebSecurityManager securityManager(CookieRememberMeManager rememberMeManager, CacheManager cacheShiroManager, SessionManager sessionManager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(this.shiroAccountRealm()); securityManager.setCacheManager(cacheShiroManager); securityManager.setRememberMeManager(rememberMeManager); securityManager.setSessionManager(sessionManager); securityManager.setSubjectFactory(this.accountSubjectFactory()); return securityManager; } /** * session管理器(單機(jī)環(huán)境) */ @Bean public DefaultWebSessionManager defaultWebSessionManager(CacheManager cacheShiroManager) { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setCacheManager(cacheShiroManager); sessionManager.setSessionValidationInterval(1800 * 1000); sessionManager.setGlobalSessionTimeout(900 * 1000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionValidationSchedulerEnabled(true); Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME); cookie.setName("shiroCookie"); cookie.setHttpOnly(true); sessionManager.setSessionIdCookie(cookie); return sessionManager; } /** * 緩存管理器 使用Ehcache實(shí)現(xiàn) */ @Bean public CacheManager getCacheShiroManager() { return new MemoryConstrainedCacheManager(); } /** * 項(xiàng)目自定義的Realm */ @Bean public AccountRealm shiroAccountRealm() { return new AccountRealm(); } /** * rememberMe管理器, cipherKey生成見{@code Base64Test.java} */ @Bean public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) { CookieRememberMeManager manager = new CookieRememberMeManager(); manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA==")); manager.setCookie(rememberMeCookie); return manager; } /** * 記住密碼Cookie */ @Bean public SimpleCookie rememberMeCookie() { SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); simpleCookie.setHttpOnly(true); simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天 return simpleCookie; } /** * Shiro的過濾器鏈 */ @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager,CollectionPropertiesConfig collectionPropertiesConfig,QueryPermissionService queryPermissionService) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); /** * 默認(rèn)的登陸訪問url */ shiroFilter.setLoginUrl("/login"); /** * 登陸成功后跳轉(zhuǎn)的url */ shiroFilter.setSuccessUrl("/"); /** * 沒有權(quán)限跳轉(zhuǎn)的url */ shiroFilter.setUnauthorizedUrl("/error/reject.html"); /** * 覆蓋默認(rèn)的user攔截器(默認(rèn)攔截器解決不了ajax請(qǐng)求 session超時(shí)的問題,若有更好的辦法請(qǐng)及時(shí)反饋?zhàn)髡? */ HashMap<String, Filter> myFilters = new HashMap<>(); myFilters.put("query", new QueryLimitFiter(queryPermissionService)); myFilters.put("authc", new AuthenticatedFilter()); shiroFilter.setFilters(myFilters); /** * 配置shiro攔截器鏈 * * anon 不需要認(rèn)證 * authc 需要認(rèn)證 * user 驗(yàn)證通過或RememberMe登錄的都可以 * * 當(dāng)應(yīng)用開啟了rememberMe時(shí),用戶下次訪問時(shí)可以是一個(gè)user,但不會(huì)是authc,因?yàn)閍uthc是需要重新認(rèn)證的 * * 順序從上到下,優(yōu)先級(jí)依次降低 * */ Map<String, String> hashMap = new LinkedHashMap<>(); hashMap.put("/login", "anon"); hashMap.put("/", "authc"); hashMap.put("/user*", "authc"); hashMap.put("/user/**", "authc"); hashMap.put("/post/**", "authc"); hashMap.put("/admin", "authc,perms[admin]"); hashMap.put("/admin/**", "authc,perms[admin]"); for(String uri:collectionPropertiesConfig.getQueryLogUrls()){ hashMap.put(uri,"query"); } shiroFilter.setFilterChainDefinitionMap(hashMap); return shiroFilter; } /** * 在方法中 注入 securityManager,進(jìn)行代理控制 */ @Bean public MethodInvokingFactoryBean methodInvokingFactoryBean(DefaultWebSecurityManager securityManager) { MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean(); bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); bean.setArguments(new Object[]{securityManager}); return bean; } /** * Shiro生命周期處理器: * 用于在實(shí)現(xiàn)了Initializable接口的Shiro bean初始化時(shí)調(diào)用Initializable接口回調(diào)(例如:UserRealm) * 在實(shí)現(xiàn)了Destroyable接口的Shiro bean銷毀時(shí)調(diào)用 Destroyable接口回調(diào)(例如:DefaultSecurityManager) */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 啟用shrio授權(quán)注解攔截方式,AOP式方法級(jí)權(quán)限檢查 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
3、實(shí)現(xiàn)自定義的Realm、filter、SubjectFactory等
import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser; import org.apache.shiro.authc.SimpleAuthenticationInfo; public class AccountAuthenticationInfo extends SimpleAuthenticationInfo{ private static final long serialVersionUID = 3405356595200877071L; private OrganizationUser profile; public AccountAuthenticationInfo(){ } public AccountAuthenticationInfo(Object principal, Object credentials, String realmName){ super(principal, credentials, realmName); } public OrganizationUser getProfile() { return profile; } public void setProfile(OrganizationUser profile) { this.profile = profile; } } import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.web.subject.support.WebDelegatingSubject; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class AccountSubject extends WebDelegatingSubject{ private OrganizationUser profile; public AccountSubject(PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionEnabled, ServletRequest request, ServletResponse response, SecurityManager securityManager, OrganizationUser profile) { super(principals, authenticated, host, session, sessionEnabled, request, response, securityManager); this.profile = profile; } public String getUsername(){ return getPrincipal().toString(); } public OrganizationUser getProfile() { return profile; } } import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser; import com.yuntu.intelligent.log.service.UserService; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.mgt.SubjectFactory; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.subject.WebSubjectContext; import org.springframework.beans.factory.annotation.Autowired; public class AccountSubjectFactory implements SubjectFactory { @Autowired private UserService userService; @Override public Subject createSubject(SubjectContext context) { WebSubjectContext wsc = (WebSubjectContext) context; AuthenticationInfo info = wsc.getAuthenticationInfo(); OrganizationUser profile = null; AccountSubject subject = null; if (info instanceof AccountAuthenticationInfo) { profile = ((AccountAuthenticationInfo) info).getProfile(); subject = doCreate(wsc, profile); subject.getSession(true).setAttribute("profile", profile); }else{ Session session = wsc.getSession(); if(session != null){ profile = (OrganizationUser)session.getAttribute("profile"); } subject = doCreate(wsc, profile); boolean isRemembered = subject.isRemembered(); if (session == null) { wsc.setSessionCreationEnabled(true); subject.getSession(true); } if (isRemembered && profile == null) { Object username = subject.getPrincipal(); profile = userService.getUserByName((String) username); subject.getSession(true).setTimeout(30 * 60 * 1000); subject.getSession(true).setAttribute("profile", profile); } } return doCreate(wsc, profile); } private AccountSubject doCreate(WebSubjectContext wsc, OrganizationUser profile) { return new AccountSubject(wsc.resolvePrincipals(), wsc.resolveAuthenticated(), wsc.resolveHost(), wsc.resolveSession(), wsc.isSessionCreationEnabled(), wsc.resolveServletRequest(), wsc.resolveServletResponse(), wsc.resolveSecurityManager(), profile); } } import org.apache.shiro.authc.AuthenticationToken; public class AccountToken implements AuthenticationToken { private static final long serialVersionUID = 1L; private long id; private String username; public AccountToken() { } public AccountToken(long id, final String username) { this(id, username, username); } public AccountToken(long id, final String username, String nickname) { this.id = id; this.username = username; } @Override public Object getPrincipal() { return username; } @Override public Object getCredentials() { return null; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.servlet.OncePerRequestFilter; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Formatter; /** * @version 1.0.0 */ public class AuthenticatedFilter extends OncePerRequestFilter { private Logger LOG = LoggerFactory.getLogger(AuthenticatedFilter.class); private static final String JS = "<script type='text/javascript'>var wp=window.parent; if(wp!=null){while(wp.parent&&wp.parent!==wp){wp=wp.parent;}wp.location.href='%1$s';}else{window.location.href='%1$s';}</script>"; private String loginUrl = "/login"; @Override protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { LOG.info("開始權(quán)限驗(yàn)證"); Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { chain.doFilter(request, response); } else { identifyGuest(subject, request, response, chain); } } protected void identifyGuest(Subject subject, ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { redirectLogin(request, response); } protected void redirectLogin(ServletRequest request, ServletResponse response) throws IOException { WebUtils.saveRequest(request); String path = WebUtils.getContextPath((HttpServletRequest) request); String url = loginUrl; if (StringUtils.isNotBlank(path) && path.length() > 1) { url = path + url; } if (isAjaxRequest((HttpServletRequest) request)) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().print("您還沒有登錄!"); } else { response.getWriter().write(new Formatter().format(JS, url).toString()); } } public String getLoginUrl() { return loginUrl; } public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } /** * 判斷是否為Ajax請(qǐng)求 <功能詳細(xì)描述> * * @param request * @return 是true, 否false * @see [類、類#方法、類#成員] */ public static boolean isAjaxRequest(HttpServletRequest request) { String header = request.getHeader("X-Requested-With"); if (header != null && "XMLHttpRequest".equals(header)) return true; else return false; } } import com.yuntu.intelligent.log.service.QueryPermissionService; import org.apache.commons.lang.StringUtils; import org.apache.shiro.web.servlet.AbstractFilter; import org.apache.shiro.web.servlet.ServletContextSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; public class QueryLimitFiter extends ServletContextSupport implements Filter { private QueryPermissionService queryPermissionService; private static final transient Logger log = LoggerFactory.getLogger(AbstractFilter.class); protected FilterConfig filterConfig; public QueryLimitFiter(QueryPermissionService queryPermissionService) { this.queryPermissionService = queryPermissionService; } public FilterConfig getFilterConfig() { return this.filterConfig; } public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; this.setServletContext(filterConfig.getServletContext()); } protected String getInitParam(String paramName) { FilterConfig config = this.getFilterConfig(); return config != null? org.apache.shiro.util.StringUtils.clean(config.getInitParameter(paramName)):null; } public final void init(FilterConfig filterConfig) throws ServletException { this.setFilterConfig(filterConfig); try { this.onFilterConfigSet(); } catch (Exception var3) { if(var3 instanceof ServletException) { throw (ServletException)var3; } else { if(log.isErrorEnabled()) { log.error("Unable to start Filter: [" + var3.getMessage() + "].", var3); } throw new ServletException(var3); } } } protected void onFilterConfigSet() throws Exception { } public void destroy() { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if(isAccessAllowed(servletRequest,servletResponse)){ filterChain.doFilter(servletRequest,servletResponse); }else { servletResponse.setContentType("application/json;charset=UTF-8"); servletResponse.getWriter().print("不允許查詢!"); } } protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse) { HttpServletRequest request = (HttpServletRequest)servletRequest; String hallCode = request.getParameter("guanhao"); String uri = request.getRequestURI(); System.out.println(uri); // if (collectionPropertiesConfig.getQueryLogUrls().contains(uri)) { try { if (StringUtils.isEmpty(hallCode)) { servletResponse.setContentType("application/json;charset=UTF-8"); servletResponse.getWriter().print("需要輸入館號(hào)!"); return false; } if (queryPermissionService.permit(hallCode)) { return true; } else { servletResponse.setContentType("application/json;charset=UTF-8"); servletResponse.getWriter().print("你沒有權(quán)限查詢此館!"); return false; } }catch (Exception e){ e.printStackTrace(); } return false; } } import com.yuntu.intelligent.log.model.sysmodel.OrganizationPrivilege; import com.yuntu.intelligent.log.model.sysmodel.OrganizationResources; import com.yuntu.intelligent.log.model.sysmodel.OrganizationUser; import com.yuntu.intelligent.log.service.RoleService; import com.yuntu.intelligent.log.service.UserService; import com.yuntu.intelligent.log.service.shiro.authc.AccountAuthenticationInfo; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; 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.beans.factory.annotation.Autowired; import javax.annotation.Resource; import java.util.List; public class AccountRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService userRoleService; public AccountRealm() { super(new AllowAllCredentialsMatcher()); setAuthenticationTokenClass(UsernamePasswordToken.class); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.fromRealm(getName()).iterator().next(); if (username != null) { OrganizationUser user = userService.getUserByName(username); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); OrganizationPrivilege role = userRoleService.getRole(user.getPrivilegeId()); List<OrganizationResources> roleResources = userRoleService.getRoleResources(user.getPrivilegeId()); //賦予角色 info.addRole(role.getName()); //賦予權(quán)限 roleResources.forEach(resource -> info.addStringPermission(resource.getPermission())); return info; } } return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { OrganizationUser profile = getAccount(userService, token); if (profile.getStatus() == 0) { throw new LockedAccountException(profile.getUserId()); } AccountAuthenticationInfo info = new AccountAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName()); info.setProfile(profile); return info; } protected OrganizationUser getAccount(UserService userService, AuthenticationToken token) { UsernamePasswordToken upToken = (UsernamePasswordToken) token; return userService.login(upToken.getUsername(), String.valueOf(upToken.getPassword())); } }
4、重點(diǎn)記錄filter配置中出現(xiàn)的問題
如上代碼,我有2個(gè)自定義的過濾器,AuthenticatedFilter可以拋開spring運(yùn)行,而QueryLimitFiter想使用一些spring管理的bean,所以一開始QueryLimitFiter是使用@Component注釋并且在ShiroConfig的@Bean方法中將其注入并配置進(jìn)shiro的過濾器鏈。
在使用的時(shí)候,原定只是在個(gè)別uri觸發(fā)的QueryLimitFiter,所有uri都會(huì)觸發(fā)它,反而AuthenticatedFilter失效了。查找原因,找到spring的filter鏈如圖,這是借用別人的圖,我的圖是將accessTokenFilter替換為queryLimitFiter:
總之就是自定義的過濾器QueryLimitFiter居然在shiroFilter之外而且運(yùn)行在shiroFilter之前了。。。
5、解決方案
如上代碼,將QueryLimitFiter不交給spring托管,使用new的方式添加到shiro的過濾器鏈中就沒有問題了。出現(xiàn)原因可能是spring會(huì)自動(dòng)將我們自定義的filter加載到它的過濾器鏈中(待深究?。?/p>
shiro自定義異常無效
一定要看一下自己重寫AuthorizingRealm中doGetAuthenticationInfo方法時(shí)候拋出什么異常,這拋出得是AuthenticationToken;
if (userName == null) { throw new AuthenticationToken("用戶名不正確"); } else if (!userPwd.equals(password )) { throw new AuthenticationToken("密碼不正確"); }
而我在controller里面拋出得卻是UnknownAccountException,這樣能捕捉到想要得自定義異常才有鬼了!
// 執(zhí)行認(rèn)證登陸 try { subject.login(token); } catch (UnknownAccountException uae) { map.put("msg","賬號(hào)或密碼不對(duì)"); map.put("code","21000"); return map; }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java web實(shí)現(xiàn)網(wǎng)上手機(jī)銷售系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java web實(shí)現(xiàn)網(wǎng)上手機(jī)銷售系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08基于SpringMVC中的路徑參數(shù)和URL參數(shù)實(shí)例
這篇文章主要介紹了基于SpringMVC中的路徑參數(shù)和URL參數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02如何使用Idea中的 Deployment 實(shí)現(xiàn)打包自動(dòng)部署
這篇文章主要介紹了使用Idea中的 Deployment 實(shí)現(xiàn)打包自動(dòng)部署,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08Spring5源碼解析之Spring中的異步和計(jì)劃任務(wù)
本篇文章主要介紹了Spring5源碼解析之Spring中的異步和計(jì)劃任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-10-10SpringBoot整合Dubbo zookeeper過程解析
這篇文章主要介紹了SpringBoot整合Dubbo zookeeper過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02springboot多環(huán)境配置文件及自定義配置文件路徑詳解
這篇文章主要介紹了springboot多環(huán)境配置文件及自定義配置文件路徑,文中給大家介紹了classpath的基本概念講解及自定義springboot配置文件路徑的相關(guān)知識(shí),需要的朋友可以參考下2023-02-02mybatis xml注釋sql的注意事項(xiàng)及說明
這篇文章主要介紹了mybatis xml注釋sql的注意事項(xiàng)及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07關(guān)于Springboot打成JAR包后讀取外部配置文件的問題
這篇文章主要介紹了關(guān)于Springboot打成JAR包后讀取外部配置文件的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11基于FeignClient調(diào)用超時(shí)的處理方案
這篇文章主要介紹了基于FeignClient調(diào)用超時(shí)的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07