今天遇到一个问题,在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;
 }

这样的话,登录过期时,不进行重定向,而是直接返回请重新登录的提示给页面。