springboot集成shiro遭遇自定义filter异常的解决
目录
- springboot集成shiro遭遇自定义filter异常
- 1、用maven添加shiro
- 2、配置shiro
- 3、实现自定义的Realm、filter、SubjectFactory等
- 4、重点记录filter配置中出现的问题
- 5、解决方案
- shiro自定义异常无效
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权限管理的配置 */ @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管理器(单机环境) */ @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实现 */ @Bean public CacheManager getCacheShiroManager() { return new MemoryConstrainedCacheManager(); } /** * 项目自定义的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); /** * 默认的登陆访问url */ shiroFilter.setLoginUrl("/login"); /** * 登陆成功后跳转的url */ shiroFilter.setSuccessUrl("/"); /** * 没有权限跳转的url */ shiroFilter.setUnauthorizedUrl("/error/reject.html"); /** * 覆盖默认的user拦截器(默认拦截器解决不了ajax请求 session超时的问题,若有更好的办法请及时反馈作者) */ HashMap<String, Filter> myFilters = new HashMap<>(); myFilters.put("query", new QueryLimitFiter(queryPermissionService)); myFilters.put("authc", new AuthenticatedFilter()); shiroFilter.setFilters(myFilters); /** * 配置shiro拦截器链 * * anon 不需要认证 * authc 需要认证 * user 验证通过或RememberMe登录的都可以 * * 当应用开启了rememberMe时,用户下次访问时可以是一个user,但不会是authc,因为authc是需要重新认证的 * * 顺序从上到下,优先级依次降低 * */ 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,进行代理控制 */ @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生命周期处理器: * 用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调(例如:UserRealm) * 在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调(例如:DefaultSecurityManager) */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 启用shrio授权注解拦截方式,AOP式方法级权限检查 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
3、实现自定义的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("开始权限验证"); 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请求 <功能详细描述> * * @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("需要输入馆号!"); return false; } if (queryPermissionService.permit(hallCode)) { return true; } else { servletResponse.setContentType("application/json;charset=UTF-8"); servletResponse.getWriter().print("你没有权限查询此馆!"); 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()); //赋予权限 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、重点记录filter配置中出现的问题
如上代码,我有2个自定义的过滤器,AuthenticatedFilter可以抛开spring运行,而QueryLimitFiter想使用一些spring管理的bean,所以一开始QueryLimitFiter是使用@Component注释并且在ShiroConfig的@Bean方法中将其注入并配置进shiro的过滤器链。
在使用的时候,原定只是在个别uri触发的QueryLimitFiter,所有uri都会触发它,反而AuthenticatedFilter失效了。查找原因,找到spring的filter链如图,这是借用别人的图,我的图是将accessTokenFilter替换为queryLimitFiter:
总之就是自定义的过滤器QueryLimitFiter居然在shiroFilter之外而且运行在shiroFilter之前了。。。
5、解决方案
如上代码,将QueryLimitFiter不交给spring托管,使用new的方式添加到shiro的过滤器链中就没有问题了。出现原因可能是spring会自动将我们自定义的filter加载到它的过滤器链中(待深究!)。
shiro自定义异常无效
一定要看一下自己重写AuthorizingRealm中doGetAuthenticationInfo方法时候抛出什么异常,这抛出得是AuthenticationToken;
if (userName == null) { throw new AuthenticationToken("用户名不正确"); } else if (!userPwd.equals(password )) { throw new AuthenticationToken("密码不正确"); }
而我在controller里面抛出得却是UnknownAccountException,这样能捕捉到想要得自定义异常才有鬼了!
// 执行认证登陆 try { subject.login(token); } catch (UnknownAccountException uae) { map.put("msg","账号或密码不对"); map.put("code","21000"); return map; }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。
【转自:http://www.nextecloud.cn/jap.html 欢迎转载】