最近开发一个小项目采用springboot2+shiro前后端分离的方式进行。由于访问使用https证书形式。结果在上线时遇到登录信息过期后shiro设置的跳转接口时重定向为http。从而https访问http报错。网上找了很多都没有一个很好的解决办法。

    一开始想通过redirectHttp10Compatible:解决https环境下使用redirect重定向地址变为http的协议,无法访问服务的问题              设置为false,即关闭了对http1.0协议的兼容支持 ,实际测试不管用。只能从shiro源码进行分析如下:

    Java代码 

publicboolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

可以发现他是调用的isAccessAllowed方法和onAccessDenied方法,只要两者有一个可以就可以了,从名字中我们也可以理解,他的逻辑是这样:先调用isAccessAllowed,如果返回的是true,则直接放行执行后面的filter和servlet,如果返回的是false,则继续执行后面的onAccessDenied方法,如果后面返回的是true则也可以有权限继续执行后面的filter和servelt。

只有两个函数都返回false才会阻止后面的filter和servlet的执行。

isAccessAllowed方法在这个类中都是抽象的,依靠实现类实现。onAccessDenied方法不是抽象的,但是调用了另一个抽象的方法:

org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(ServletRequest, ServletResponse)

这个方法忽略了之前配置的param参数。

这个类中还有其他的属性,比如getLoginUrl,这个很容易猜测,是当没有登录的时候重定向到登录界面的,这个方法就是获得登录界面的位置,默认是/login.jsp,如果我们的登录界面不是这个的话就要重写这个方法。

还有一个特别好使的方法

saveRequestAndRedirectToLogin(ServletRequest, ServletResponse),源码如下:

Java代码
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}

显示保存了当前的request,然后重定向。

源码如下:

Java代码
protected void saveRequest(ServletRequest request) {
WebUtils.saveRequest(request);//关于webutils在别的博客中。
}
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
String loginUrl = getLoginUrl();//重定向的界面就是到登录页面。
WebUtils.issueRedirect(request, response, loginUrl); //关于webutils在别的博客中。
}

我们如果在其他类中需要重定向的话就可以直接使用它的WebUtils.issueRedirect(request,response,loginUrl)方法了。
找到办法了只要在onAccessDenied返回未登录状态就好了。

方法一:重写FormAuthenticationFilter
原理:
假设在shiro.xml中配置了 /** = authc,而默认authc对应org.apache.shiro.web.filter.authc.FormAuthenticationFilter过滤器则表示所有路径都被此过滤器拦截。

第一步:新建一个MyFormAuthenticationFilter类extends FormAuthenticationFilter代码如下:

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {

private static final Logger log = LoggerFactory.getLogger(MyFormAuthenticationFilter.class);

@Override
protected boolean onAccessDenied(ServletRequest request,
                                 ServletResponse response) throws IOException {

    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    Subject subject = getSubject(request, response);
    if (subject.getPrincipal() == null) {
        //设置响应头
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json");
        //设置返回的数据
        ResultMess resultMess = new ResultMess();
        resultMess.setCode(-10000);
        resultMess.setMsg("未登录");
        resultMess.setSuccess(false);
        Gson gson =  new Gson();
        String result = gson.toJson(resultMess);
        //写回给客户端
        PrintWriter out = httpResponse.getWriter();
        out.write(result);
        //刷新和关闭输出流
        out.flush();
        out.close();
    } else {
        //设置响应头
        System.out.println("=========onAccessDenied==========返回json====>>>>");
        httpResponse.setCharacterEncoding("UTF-8");
        httpResponse.setContentType("application/json");
        //设置返回的数据
        ResultMess resultMess = new ResultMess();
        resultMess.setCode(-10000);
        resultMess.setMsg("未登录");
        resultMess.setSuccess(false);
        Gson gson =  new Gson();
        String s = gson.toJson(resultMess);
        //写回给客户端
        PrintWriter out = httpResponse.getWriter();
        out.write(s);
        //刷新和关闭输出流
        out.flush();
        out.close();
    }
    return false;
}

}
第二步:ShiroConfig 类中将重写的MyFormAuthenticationFilter赋予ShiroFilterFactoryBean 代码如下:

@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
// shiroFilterFactoryBean.setLoginUrl("/adminUser/unauth");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/adminUser/unauth");
Map filtersMap = new HashMap<>();
MyFormAuthenticationFilter authFilter = new MyFormAuthenticationFilter();
filtersMap.put("authc", authFilter);
shiroFilterFactoryBean.setFilters(filtersMap);

    // 配置数据库中的resource
    Map filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

第三步:

将所以的需要拦截的标注为authc,次处的authc和filtersMap.put("authc", authFilter);设置的对应。

这样shiro判断到未登录就进入次拦截器,返回json数据给前端避免了重定向url.