今天遇到一个问题,在springboot+shiro整合的项目中,单点登录,当登录过期时使用setLoginUrl 设置重定向地址,然后这个地址返回一个登录过期请重新登录的提示给前端,然后前端控制页面跳转到登录页面。
问题就出在重定向的时候,setLoginUrl 设置的地址总是会携带JSESSIONID,就一直报302错误
具体情况如下:
ShiroConfig配置类
//省略其他配置
/**
* Shiro基础配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 注意过滤器配置顺序不能颠倒
// 配置过滤:不会被拦截的链接
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/unauth", "anon");
filterChainDefinitionMap.put("/**", "authc");
//这个地址就是我配置的登录过期时重定向跳转的地址
shiroFilterFactoryBean.setLoginUrl("/user/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
LoginController登录
/**
* 登录
*/
@ApiOperation("登录")
@RequestMapping("/login")
@Log(operMethod = "用户登录",operInfo = "用户登录")
public ResultVo login(HttpServletRequest request,String userName,String password,String code){
//省略代码
return ResultVoUtil.success();
}
/**
* 未登录
*/
@ApiOperation("未登录")
@RequestMapping("/unauth")
public ResultVo unauth(){
Map<String,String> map = new HashMap<>();
map.put("code","1000");
map.put("message","用户未登录,请重新登录");
//这里返回未登录的提示给前端,由前端去判断重新跳转到登录页面
return ResultVoUtil.error(map);
}
登录过期后,重定向的地址应该是:http://localhost:8080/user/unauth
前端接收到的应该就是 { “code” : “1000” , “message” : “用户未登录,请进行登录” }
但实际上,地址却变成了:http://localhost/user/unauth;JSESSIONID=login_token_6d99b67d-1499-416f-8777-75fa890137c8
然后就会报302错误
我百思不得其解,一直想不通为啥地址会携带上JSESSIONID,之后我找到个方法放弃用shiroFilterFactoryBean.setLoginUrl("/user/unauth"); 这种方式重定向,而用FormAuthenticationFilter过滤器。
ShiroLoginFilter 过滤器
首先,新建 ShiroLoginFilter ,继承 FormAuthenticationFilter
package com.mh.common.shiro;
import com.alibaba.fastjson.JSONObject;
import com.mh.common.vo.ResultVo;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ShiroLoginFilter extends FormAuthenticationFilter {
/**
* 如果isAccessAllowed返回false 则执行onAccessDenied
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
/**
* 在访问controller前判断是否登录,返回json,不进行重定向。
* @param request
* @param response
* @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//这里是个坑,如果不设置的接受的访问源,那么前端都会报跨域错误,因为这里还没到corsConfig里面
httpServletResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest) request).getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
ResultVo resultVo = new ResultVo();
resultVo.setCode(1000);
resultVo.setMessage("用户未登录,请进行登录");
httpServletResponse.getWriter().write(JSONObject.toJSON(resultVo).toString());
return false;
}
}
ResultVo
/**
* 固定返回格式
*/
@Data
public class ResultVo {
/**
* 错误码
*/
private Integer code;
/**
* 提示信息
*/
private String message;
/**
* 具体的内容
*/
private Object data;
}
然后在 ShiroConfig 中配置 ShiroLoginFilter
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 注意过滤器配置顺序不能颠倒
// 配置过滤:不会被拦截的链接
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/unauth", "anon");
filterChainDefinitionMap.put("/**", "authc");
// 注释不用
// shiroFilterFactoryBean.setLoginUrl("/user/unauth");
// 获取filters
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
//将自定义 的 FormAuthenticationFilter 注入shiroFilter中
filters.put("authc", new ShiroLoginFilter());
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
这样的话,登录过期时,不进行重定向,而是直接返回请重新登录的提示给页面。