最近在项目上线的时候遇到了个问题,就是SpringBoot整合Shiro前后端分离在Https环境下登陆失效的问题。返回的结果是302。
错误的原因是,在https的环境内,限制http,而在Shiro框架内置的用户登陆校验,在校验失败后会保存当前请求,然后重定向到登陆页面(因为是前后端分离,我这里跳转的登录页直接就是401的JSON,通知前端进行跳转)。Shiro内部重定向的请求是http,所以请求异常。具体源码在下面:
FormAuthenticationFilter:
AccessControlFilter:
具体是不是这个兼容HTTP1.0的默认值的问题,还不好说,因为重写这个方法改为false也没有效果。
所以,下面提供了两个解决方案:
这里因为是前后端分离,所以原本就有一个解决涉及到预请求OPTIONS没有权限影响到跨域的过滤器。所以就直接在这个过滤器上进行改动了,如果没有的,原本没有过滤器的可以添加一个。
通过重写onAccessDenied方法。
解决方案1:直接抛出异常,让我们全局的异常捕捉,返回登陆提示。
解决方案2:直接返回登陆提示。不进行后续的转发操作。
public class CorsAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
boolean allowed = super.isAccessAllowed(request, response, mappedValue);
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
if (!allowed) {
// 判断请求是否是options请求
String method = WebUtils.toHttp(request).getMethod();
if (StringUtils.equalsIgnoreCase("OPTIONS", method)) {
return true;
}
}
return allowed;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
return executeLogin(request, response);
} else {
return true;
}
} else {
//解决方案1:直接抛出异常,异常会被捕获处理
//throw new UnauthenticatedException();
//解决方案2:在这里进行返回,不抛出异常,也不进行跳转
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
//获取会话
Subject subject = getSubject(request, response);
if (subject.getPrincipal() == null) {
//写回给客户端
PrintWriter out = response.getWriter();
out.write(JSONUtil.objectToJson(RestStatus.createByError(ResultCodeEnum.INVALID_USER)));
//刷新和关闭输出流
out.flush();
out.close();
}
return false;
}
}
}
然后配置下这个过滤器,这个过滤器,只作用于需要登陆的接口。
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 修改调整的登录页面
shiroFilterFactoryBean.setLoginUrl("/page/401");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/page/index");
// 未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/page/403");
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
filters.put("shiroCorsFilter", new CorsAuthenticationFilter());
//拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//带api路由的所有请求全部要求登陆
filterChainDefinitionMap.put("/api/**", "shiroCorsFilter");
//其他的所有请求放行
filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
这样就解决了问题。如果能给诸位带来帮助,麻烦点个赞。