在前后端分离的项目中,后台使用shiro框架时,怎样使用它的会话管理系统(session),从而实现权限控制

原创 2017年06月19日 10:17:08

在前后端分离的项目中,ajax跨域和保存用户信息是其中的重点和难点。

如果在后台使用shiro框架来进行权限控制,就需要用到cookie+session的模式来保存用户的信息。

在前一篇文章《在前后端分离的项目中,ajax跨域请求怎样附带cookie》中,我具体写了怎样在ajax跨域的情况下携带cookie,使用该方法使跨域请求携带cookie便可以在前后端分离的项目中使用shrio的session(会话管理系统)。

但是由于那种方法近乎与取巧的将Access-Control-Allow-Origin由*改为"null"不是所有的前端ajax框架所公认的,我们需要一种更好的模式来使用session。

在传统的前后端分离模式中,我们通常是在请求头中增加一个请求头Authorization,它的值是一串加密的信息或者密钥,在后台通过对这个请求头值的读取,获取用户的信息。

而在这样的模式中,通常都是开发者自己设计的session或者加密方式来读取和保存用户信息,而在shiro中,集成了权限控制和用户管理在它的session系统中,这就意味着我们只能通过他所规定的session+cookie来保存用户信息,在这种情况下,该以什么方式在前后端分离的项目中使用shiro?

通过资料的查询,和对shiro设计模式的解读,我发现shiro和servlet一样实在cookie中存储一个session会话的id然后在每次请求中读取该session的id并获取session,这样就可以获取指定session中储存的用户信息。

我的想法就是通过重写shiro中获取cookie中的sessionId的方法来获取请求头Authorization中的密钥,而密钥储存的便是登录是返回的sessionId,从而实现在前后端分离的项目中使用shiro框架。

接下来就是代码演示(使用SpringMVC+Shiro),只贴出核心代码:

首先是登录的代码:

    @ResponseBody
    @RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
    public String login(
            @RequestParam(required = false) String username,
            @RequestParam(required = false) String password
    ) {

        JSONObject jsonObject = new JSONObject();

        Subject subject = SecurityUtils.getSubject();

        password = MD5Tools.MD5(password);

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            // 登录,即身份验证
        subject.login(token);
        onlineSessionManager.addOnlineSession(subject.getSession().getId());
        User user = userService.getUserByLoginName(token.getUsername());
        // 在session中存放用户信息
        subject.getSession().setAttribute("userLogin", user);
        jsonObject.put("error", 0);
        jsonObject.put("msg", "登录成功");
        // 返回sessionId作为token
        jsonObject.put("token",subject.getSession().getId());
        } catch (IncorrectCredentialsException e) {
            throw new JsonException("用户名或密码错误", 405);
        } catch (LockedAccountException e) {
            throw new JsonException("登录失败,该用户已被冻结", 405);
        } catch (AuthenticationException e) {
            throw new JsonException("用户名或密码错误", 405);
        }
        return jsonObject.toString();
    }
然后重写DefaultWebSessionManager的getSessionId方法

package com.cangshi.shiro.ssesion;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;

/**
 * Created by Palerock
 */
public class SessionManager extends DefaultWebSessionManager {
    private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class);
    private String authorization = "Authorization";

    /**
     * 重写获取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);
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
        } else {
            id = this.getUriPathSegmentParamValue(request, "JSESSIONID");
            if (id == null) {
                // 获取请求头中的session
                id = WebUtils.toHttp(request).getHeader(this.authorization);
                if (id == null) {
                    String name = this.getSessionIdName();
                    id = request.getParameter(name);
                    if (id == null) {
                        id = request.getParameter(name.toLowerCase());
                    }
                }
            }
            if (id != null) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url");
            }
        }

        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        }

        return id;
    }

    // copy super
    private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
        if (!this.isSessionIdCookieEnabled()) {
            log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
            return null;
        } else if (!(request instanceof HttpServletRequest)) {
            log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
            return null;
        } else {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            return this.getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
        }
    }

    // copy super
    private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
        if (!(servletRequest instanceof HttpServletRequest)) {
            return null;
        } else {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String uri = request.getRequestURI();
            if (uri == null) {
                return null;
            } else {
                int queryStartIndex = uri.indexOf(63);
                if (queryStartIndex >= 0) {
                    uri = uri.substring(0, queryStartIndex);
                }

                int index = uri.indexOf(59);
                if (index < 0) {
                    return null;
                } else {
                    String TOKEN = paramName + "=";
                    uri = uri.substring(index + 1);
                    index = uri.lastIndexOf(TOKEN);
                    if (index < 0) {
                        return null;
                    } else {
                        uri = uri.substring(index + TOKEN.length());
                        index = uri.indexOf(59);
                        if (index >= 0) {
                            uri = uri.substring(0, index);
                        }

                        return uri;
                    }
                }
            }
        }
    }

    // copy super
    private String getSessionIdName() {
        String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null;
        if (name == null) {
            name = "JSESSIONID";
        }

        return name;
    }
}
在重写这个方法的时候,调用了一个父类的私有方法,然而私有方法是不能重写的,所以重写的目的在于调用子类的同名私有方法也就是getReferencedSessionId方法,在这个方法中就是获取sessionId的步骤,在里面调用了三个父类的私有方法,getSessionIdCookieValue、getUriPathSegmentParamValue、getSessionIdName,也就是我使用copy super标注的部分,这部分是我复制父类的方法,让在不使用ajax的情况下能够正常使用cookie+session模式,而我们在步骤中加入从请求头中获取sessionId的部分也就是:

// 获取请求头中的session
id = WebUtils.toHttp(request).getHeader(this.authorization);
当该id 存在就不从cookie中获取id,自然达到了通过Authorization请求头获取sessionId并获取session的目的。

接下来就是Spring中xml中的配置会话管理器

<!-- 会话管理器 -->
<bean id="sessionManager" class="com.cangshi.shiro.ssesion.SessionManager">
    <!--其它相关设置-->
</bean>
这样我们在跨域的ajax请求中登录获取token,然后在接下来的请求的请求头中加上Authorization:[token]就可以使用shrio所自带的会话管理器,从而使用shiro的权限控制。




在前后端分离的SpringBoot项目中集成Shiro权限框架

项目背景       公司在几年前就采用了前后端分离的开发模式,前端所有请求都使用ajax。这样的项目结构在与CAS单点登录等权限管理框架集成时遇到了很多问题,使得权限部分的代码冗长丑陋,CAS的各种...
  • u013615903
  • u013615903
  • 2017年12月12日 14:13
  • 6127

前后端分离项目shiro验证

公司新项目,要做前后端分离项目,在开发过程中,需要使用shiro来做会话管理。由于是前后端分离 ,前端是通过ajax请求后端地址,后端程序没办法将sessionId写到请求浏览器cookie中,所以需...
  • lf_breeze
  • lf_breeze
  • 2017年11月29日 13:48
  • 1452

Shiro 管理多个realm 实现前后台分离

使用shiro 由于公司的业务上的需求前后台公用的一张表,要实现前台用户和后台用户的分离拦截需要书写多个realm 用来验证前台用户还是后台用户。直接上代码 1.书写一个自定的token User...
  • liiuijkiuu
  • liiuijkiuu
  • 2016年12月30日 17:16
  • 8638

shiro 前后端分离框架 用户登录解决方案

介绍 最近公司要原本springmvc+shiro 权限控制的一个项目,改为前后端分离的,使前端人员能有更多时间来做前端的交互工作,提升用户体验。 但是这个问题就来了,原来没有分离的情况下,shi...
  • qq_14965807
  • qq_14965807
  • 2018年01月17日 16:02
  • 308

Shiro拦截AJAX的解决方案

Shiro拦截AJAX的解决方案最近在springboot项目上使用了shiro,但是shiro配置好后都是默认页面重定向处理。然而前后端分离后,静态页面都是部署在nginx上,统一都通过ajax进行...
  • u014042146
  • u014042146
  • 2017年06月02日 10:09
  • 7782

基于shiro前后端分离分布式权限管理

  • 2017年07月20日 11:31
  • 21KB
  • 下载

java web网站中使用shiro实现前后台登录功能

年前公司网站需要重新开发一个新版本,在架构设计上废了一些劲,我项目架构大概是这样:Spring作为容器,Mybatis做数据持久层,SpringMVC做控制层,Shiro做为安全框架,页面使用tomd...
  • u013482947
  • u013482947
  • 2016年03月29日 01:02
  • 11503

Shiro+Cas微服务化及前后端完全分离

shiro+cas微服务化笔记1.Spring Boot 配置有如下两个配置文件:ShiroBaseConfig.java import lombok.extern.log4j.Log4j; i...
  • crowhyc
  • crowhyc
  • 2017年07月10日 13:51
  • 4506

从 MVC 到前后端分离

本文首先描述 MVC 模式是什么,然后针对 MVC 的不足发表了作者的个人观点,随后引出了基于 REST 架构实现前后端分离的方案,最后使用了 Java 的 Spring 框架搭建了一个简单的 RES...
  • oLeiChang
  • oLeiChang
  • 2016年01月22日 10:30
  • 2403

【Web】JavaWeb项目为什么我们要放弃jsp?为什么要前后端解耦?为什么要前后端分离?2.0版,为分布式架构打基础。

前戏 前后端分离已成为互联网项目开发的业界标准使用方式,通过nginx+tomcat的方式(也可以中间加一个nodejs)有效的进行解耦, 并且前后端分离会为以后的大型分布式架构、弹性计算架构、微...
  • zsq520520
  • zsq520520
  • 2017年04月12日 16:32
  • 4225
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:在前后端分离的项目中,后台使用shiro框架时,怎样使用它的会话管理系统(session),从而实现权限控制
举报原因:
原因补充:

(最多只允许输入30个字)