shiro整合springboot,前后端分离项目踩坑记录

一、重写过滤器

shiro自己帮我们封装了一系列过滤器,对于常见的一些操作,比如登陆,登出,认证不通过等,shiro会通过我们配置的url,找到相应过滤器,做完操作会跳转到我们配置的跳转url中。但是对于前后端分离项目,前端通过ajax调用接口,获取返回值展示后再跳。比如展示“暂无权限”等提示语,友好一些。所以要对shiro已有的过滤器进行继承重写。

登出过滤器:

public class UserLogoutFilter extends LogoutFilter {

    private Logger logger = LoggerFactory.getLogger(UserLogoutFilter.class);

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        //在这里执行退出系统前需要清空的数据
        Subject subject = getSubject(request, response);
        try {
            subject.logout();
        } catch (SessionException ise) {
            logger.error("logout session Exception", ise);
        }
        //给前端返回结果
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization,X-TOKEN,XMLHttpRequest");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
        HttpUtil.writeResult(httpServletResponse, RevertResult.ok());
        //返回false表示不执行后续的过滤器
        return false;
    }
}

认证过滤器:

public class UserAccessFilter extends FormAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (this.isLoginRequest(request, response)) {
            return true;
        } else {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization,X-TOKEN,XMLHttpRequest");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
            HttpUtil.writeResult(response, RevertResult.failResult(ErrorCodes.USER_NO_LOGIN));
            //返回false表示不执行后续的过滤器
            return false;
        }
    }

}

shiro配置文件:


    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //获取filters
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("userAccessFilter", new UserAccessFilter());
        filters.put("logout", new UserLogoutFilter());

        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的 url
        shiroFilterFactoryBean.setLoginUrl(loginUrl);

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 设置免认证 url
        String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(anonUrl, ",");
        for (String url : anonUrls) {
            filterChainDefinitionMap.put(url, "anon");
        }
        filterChainDefinitionMap.put(logoutUrl, "logout");

        filterChainDefinitionMap.put("/**", "userAccessFilter");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

httpUtil:

public class HttpUtil {

    /**
     * response输出方法
     *
     * @param response
     * @param revertResult
     */
    public static void writeResult(ServletResponse response, RevertResult revertResult) {
        //重置response
        //设置编码格式
        PrintWriter writer = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html; charset=utf-8");

            writer = response.getWriter();
            writer.write(JSON.toJSONString(revertResult));
            writer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != writer) {
                writer.close();
            }
        }
    }
}

二、跨域问题

由于是前后端分离项目,前后端部署在不同服务器上,不同主域情况下,以上通过response写出的返回值会被拦截。需要在basefilter进行配置。ps:spring拦截器是在shiro过滤器后执行的,所以不能通过拦截器配置。

@Component
@WebFilter(urlPatterns = "/*", filterName = "baseFilter")
public class BaseFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter初始化中");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String headerOrigin = httpServletRequest.getHeader("origin");
        if (headerOrigin != null) {
            httpServletResponse.setHeader("Access-Control-Allow-Origin", headerOrigin);
        }
        httpServletResponse.setHeader("Access-Control-Expose-Headers", "page, per_page,status,total_count");
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");

        if (httpServletRequest.getMethod().equals("OPTIONS")) {
            httpServletResponse.setStatus(200);
            return;
        }

        //调用该方法后,表示过滤器经过原来的url请求处理方法
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("Filter销毁中");
    }
}
Access-Control-Allow-Origin这一项,不可为"*",而是取前端传的主域。

三、全局异常拦截

由于shiro在鉴权不通过时,会直接抛出exception,我们需要封装一层友好的提示返回前端。

@Slf4j
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {

    @ExceptionHandler(value = UnauthorizedException.class)
    public RevertResult handleUnauthorizedException(UnauthorizedException e) {
        log.error("UnauthorizedException", e);
        return RevertResult.failResult(ErrorCodes.NO_ACCESS);
    }

    /**
     * 登录认证异常
     */
    @ExceptionHandler({UnauthenticatedException.class, AuthenticationException.class})
    public RevertResult authenticationException(Exception e) {
        log.error("UnauthenticatedException", e);
        return RevertResult.failResult(ErrorCodes.USER_NO_LOGIN);
    }
}

四、缓存中间件

shiro默认使用sessionId+本地缓存来做登录状态,但分布式部署时sessionId需要存储在统一中间件中,所以配置redis集成shiro

五、permissionstrings不可为null

在doAuthorization时,存入permissionStrings时要过滤掉数据库里permission为空的菜单。

六、超级坑爹缓存名称不一致

存authenticationCache时,传入的对象是userId。存arthorizationCache时,传入的对象是user对象

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值