springboot集成shiro遭遇自定义filter异常的解决

编辑: admin 分类: java 发布时间: 2021-12-04 来源:互联网
目录
  • 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 欢迎转载】