最近开发一个小项目采用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<String, Filter> filtersMap = new HashMap<>(); MyFormAuthenticationFilter authFilter = new MyFormAuthenticationFilter(); filtersMap.put("authc", authFilter); shiroFilterFactoryBean.setFilters(filtersMap);
// 配置数据库中的resource
Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
第三步:
将所以的需要拦截的标注为authc,次处的authc和filtersMap.put("authc", authFilter);设置的对应。
这样shiro判断到未登录就进入次拦截器,返回json数据给前端避免了重定向url.