本文主要是介绍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 {@Beanpublic AccountSubjectFactory accountSubjectFactory() {return new AccountSubjectFactory();}/*** 安全管理器*/@Beanpublic 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管理器(单机环境)*/@Beanpublic 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实现*/@Beanpublic CacheManager getCacheShiroManager() {return new MemoryConstrainedCacheManager();}/*** 项目自定义的Realm*/@Beanpublic AccountRealm shiroAccountRealm() {return new AccountRealm();}/*** rememberMe管理器, cipherKey生成见{@code Base64Test.java}*/@Beanpublic CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {CookieRememberMeManager manager = new CookieRememberMeManager();manager.setCipherKey(Base64.decode("Z3VucwAAAAAAAAAAAAAAAA=="));manager.setCookie(rememberMeCookie);return manager;}/*** 记住密码Cookie*/@Beanpublic SimpleCookie rememberMeCookie() {SimpleCookie simpleCookie = new SimpleCookie("rememberMe");simpleCookie.setHttpOnly(true);simpleCookie.setMaxAge(7 * 24 * 60 * 60);//7天return simpleCookie;}/*** Shiro的过滤器链*/@Beanpublic 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,进行代理控制*/@Beanpublic 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)*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 启用shrio授权注解拦截方式,AOP式方法级权限检查*/@Beanpublic 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 {@Autowiredprivate UserService userService;@Overridepublic 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;}@Overridepublic Object getPrincipal() {return username;}@Overridepublic 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";@Overrideprotected 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;elsereturn 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() {}@Overridepublic 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 {@Autowiredprivate UserService userService;@Autowiredprivate RoleService userRoleService;public AccountRealm() {super(new AllowAllCredentialsMatcher());setAuthenticationTokenClass(UsernamePasswordToken.class);}@Overrideprotected 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;}@Overrideprotected 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加载到它的过滤器链中(待深究!)。
这篇关于springboot集成shiro遭遇自定义filter异常的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!