前后端分离--基于SpringBoot集成Shiro安全验证

        写这篇博客,主要是让自己忘记的时候可以翻起来看看,毕竟人老多忘事。

        对于SpringBoot集成Shiro框架,也算是蛮了解的,但是之前搭的项目,前后端都是部署在一起的,所以就不存在跨域的问题,最近接到一个项目,需要前后端分开部署。刚开始想着用SpringBoot集成shiro安全验证,直接做个后端服务系统,应该是蛮简单的,所以就开搞了。

        Shiro工作原理是服务端将SessionID写入客户端浏览器的cookie中,客户端发起请求时携带cookie信息,服务端从cookie中读取sessionId,以此来维持会话。但是在前后端分离模式下,我们的后端服务不能将sessionId写到请求浏览器cookie中,而且存在跨域问题,基于安全方面原因,ajax请求也都不带cookie信息,后端程序没办法取得sessionId,也就无法验证登录。

        既然这样,那我们可不可以模拟这种工作方式,将SessionID返回前端做保存(写入cookie或localStage中),然后前端发起请求的时候,将Sessionid传给服务器,服务器获取这个SessionID,再取对应的session呢?事实证明,这是可以行的通的,网上也有好多大牛这么做过,只是自己在动手过程中,还是碰到了好些问题,现在就一一记录下来。

        后台登录接口,需要将ShiroSession的SessionId返回给前端,前端保存到cookie中,如下:

 

} catch (LockedAccountException e) {
    e.printStackTrace();
    retMap.put("msg", "登录失败,该用户已被冻结");
    return retMap;
} catch (AuthenticationException e) {
    e.printStackTrace();
    retMap.put("msg", "用户名或密码错误");
    return retMap;
} catch (Exception e) {
    e.printStackTrace();
    retMap.put("msg", "登录异常:"+e);
    return retMap;
}
saveLog(ConstantsUtil.OPT_TYPE_LOGIN,"用户登录-User Login","login()");
retMap.put("code", 200);
retMap.put("token", req.getSession().getId()); //返回SessionID
retMap.put("user", userLogin);
return retMap;

登录成功后,前端JS中接收sessionID(token),并保存到cookie中,

 

        $.ajax({

            url: config.apiRoot()+'/test/testlogin.do',

            type: 'POST',

            beforeSend: function(xhr) {

                xhr.withCredentials = true;

            },

            crossDomain: true,

            data: JSON.stringify(params),

            contentType: "application/json;charset=UTF-8",

            cache: 'false',

            success: function(ret) {

                if(ret!=null){

                    if(ret.code==200){

                        //登录成功

                        // 登录成功 返回登录前url

                        localStorage.setItem("admin",JSON.stringify(ret.user));

                        localStorage.setItem("token",ret.token); //保存SessionID

                        $.cookie("token",ret.token);              //保存SessionID       

                        window.location.href = "index.html";

                    }else{//未定义业务接收 方法

                        _log(ret.msg);

                    }

                }

            },

            error: function(jqXHR, textStatus, errorThrown) {

                //TODO:提示消息

                _log("登录失败!");

                //_log("请求错误:"+JSON.stringify(jqXHR) + "," + textStatus + "," + errorThrown);

            }

        });     

 

       第一,修改Shiro安全配置,重写SessionManager类,创建一个MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法,先从cookie获取SessionID,取不到再从requestHeader中获取,如取不到再从请求参数中获取,具体如下:        

 

/**
     * 重写获取sessionId的方法调用当前Manager的获取方法
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        return this.getReferencedSessionId(request, response);
    }

    /**
     * 获取sessionId从请求中
     *
     * @param request
     * @param response
     * @return
     */
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
        String id = this.getSessionIdCookieValue(request, response);       //此方法copy自父类
        if (id == null) {
            // 获取请求头中的sessionId
            HttpServletRequest req = (HttpServletRequest) request;
//                id = WebUtils.toHttp(request).getHeader(this.authorization);
            id = req.getHeader(this.authorization);
            if (id == null) {
                id = req.getParameter(this.authorization);
                if (id == null) {
                    id = this.getUriPathSegmentParamValue(request, "SID"); //此方法copy自父类
                    if (id == null) {
                        String name = this.getSessionIdName();             //此方法copy自父类
                        id = request.getParameter(name);
                        if (id == null) {
                            id = request.getParameter(name.toLowerCase());
                        }
                    }
                }
            }
        }
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            return super.getSessionId(request, response);
        }
    }

       第二,修改Shiro配置中的session管理类,配置为我们的MySessionManager类,这里用到了redis作为缓存管理器,

 

 //配置核心安全事务管理器
    @Bean(name="securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
        System.err.println("--------------shiro已经加载----------------");
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        //注入缓存管理器;  
        manager.setCacheManager(redisCacheManager());//这个如果执行多次,也是同样的一个对象;
        manager.setSessionManager(sessionManager());
        manager.setRealm(authRealm);
        return manager;
    }
/**
     *
     * @描述:sessionManager添加session缓存操作DAO
     * @创建人:
     * @创建时间:2018年4月24日 下午8:13:52
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        MySessionManager sessionManager = new MySessionManager();//使用我们的MySessionManager管理类
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }
/**
 * 配置shiro redisManager
 *
 * @return
 */
@ConfigurationProperties(prefix = "spring.redis")
public RedisManager redisManager() {
    RedisManager redisManager = new RedisManager();
    return redisManager;
}
/**
 * cacheManager 缓存 redis实现
 *
 * @return
 */
public RedisCacheManager redisCacheManager() {
    RedisCacheManager redisCacheManager = new RedisCacheManager();
    redisCacheManager.setRedisManager(redisManager());
    return redisCacheManager;
}

/**
 * RedisSessionDAO shiro sessionDao层的实现 通过redis
 * <p>
 * 使用的是shiro-redis开源插件
 */
@Bean
public RedisSessionDAO redisSessionDAO() {
    RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
    redisSessionDAO.setRedisManager(redisManager());
    return redisSessionDAO;
}

        第三,在项目中添加一个过滤器类CrosFilter,处理跨域问题,其中,OPTIONS预处理请求,要返回200,表示成功,否则ajax发起post,get等请求,服务器不能响应。另外,Access-Control-Allow-Headers配置项中,要设置Authorization,而且,Access-Control-Allow-Credentials 要设置成true.

 

/**
 * 〈一句话功能简述〉跨域过滤器<br>
 * 〈〉
 *
 * @author lenovo
 * @create 2018/7/4
 * @since 1.0.0
 */
@Component
public class CorsFilter implements Filter {
    final static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CorsFilter.class);

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest)req;
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Origin,Authorization, X-File-Name,Cookie,Accept,Connection,User-agent");//
        response.setHeader("Access-Control-Allow-Credentials", "true"); //设置允许携带cookie信息
        System.out.println("*********************************过滤器被使用**************************");
        if (request.getMethod().equals( "OPTIONS" )) { //这里很重要,options请求,直接返回200,表示成功
            response.setStatus(200);
            return;
        }
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}

}

        最后,前端ajax发起请求时,需要添加请求头信息,requestHeader添加Authorization项,值为登录时返回的sessionID($.cookie("token")),设置crossDomain为true。具体如下:

 

        var param={};

param.tocken= localStorage.getItem("token");

param.radom = Math.random();

param.currentTime= new Date();

$.ajax({

// 地址格式

url: getApiRoot()+"/checkActiveTT.do",

type: 'post',

beforeSend: function(xhr) {

                xhr.withCredentials = true;

                xhr.setRequestHeader("Authorization", $.cookie("token"));

            },

            crossDomain: true,

contentType: 'application/json;charset=UTF-8',

data: JSON.stringify(param),

cache: 'false',

async: false,

success: function (data) {

if (data.code=="200") {

}

else {

toLogin();

}

},

error: function (e) {

toLogin();

}

});

 

        经验证,前后端可以正常交互。这个问题,搞了两天,头壳疼。。

      【转载请注明出处——大道迷途】

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值