shiro 前后端分离的思路和部分实践

2 篇文章 0 订阅

前后端分离的优点:1:最大的好处就是前端JS可以做很大部分的数据处理工作,对服务器的压力减小到最小2:后台错误不会直接反映到前台,错误接秒较为友好3:由于后台是很难去探知前台页面的分布情况,而这又是JS的强项,而JS又是无法独立和服务器进行通讯的。所以单单用后台去控制整体页面,又或者只靠JS完成效果,都会难度加大,前后台各尽其职可以最大程度的减少开发难度。

思考:前后端分离我们会遇到哪些问题?

1、ajax跨域问题,因为浏览器的同源策略,导致无法在前端页面的域名下,无法访问后台域名下的接口。

2、最终要的就是会话,因为ajax请求每次过来都是无状态,导致用户前台登录后,访问敏感数据时,后台认为它没有登录。

一、跨域的解决方案

(1)jquery的jsop方式

这种方式是通过在文档中嵌入一个<script>标记来从另一个域中返回数据,这种方法拥有一个显著的缺点,那就是只支持GET操作,传输量小。

(2) CORS解决跨域

  cors解决跨域,推荐阅读cors跨域介绍

对比两种方式,推荐使用cors方式解决跨域。

二、会话的维护

在shiro框架中,我们通过看源码知道,shiro在DefaultWebSessionManager这个类中获取sessionId是从cookie中获取的,这个时候我们有了两个解决思路:

  1、第一个,重写获取sessionId的方法,从request请求头中获取sessionId.

  2、第二个,让ajax请求在提交的时候带有cookie信息,不用重写获取sessionId的方法

三、实现cors跨域+request请求头中获取sessionId

(1)、新建一个类,继承DefaultWebSessionManager 重写 getSessionId这个方法

package com.mcu.system.filter;

import java.io.Serializable;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

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

import com.mcu.common.utils.StringUtils;
/**
 * 从请求头中获取sessionid
 * @author 35168
 *
 */
public class CustomDefaultWebSessionManager extends DefaultWebSessionManager{

	private final String TOKEN = "token";
	/** 
	* 获取session id
	* 前后端分离将从请求头中获取jsesssionid
	*/
	@Override
	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
		// 从请求头中获取token
		String token = WebUtils.toHttp(request).getHeader(TOKEN);
		// 判断是否有值
		if (StringUtils.isNoneBlank(token)) {
			// 设置当前session状态
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url"); 
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);  
			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);  
			return token;
		}
		// 若header获取不到token则尝试从cookie中获取
		return super.getSessionId(request, response);
	}
}

sessionId在用户登录的时候,返回,例如:

@Log("登录")
	@PostMapping("/login")
	@ResponseBody
	R ajaxLogin(String username, String password,HttpServletRequest request) {
		//R 是一个HashMap
		UsernamePasswordToken token = new UsernamePasswordToken(username, password);
		Subject subject = SecurityUtils.getSubject();
		try {
			subject.login(token);
			R r = R.ok();
			r.put("token", subject.getSession().getId());
			
			return r;
		} catch (AuthenticationException e) {
			return R.error(e.getMessage());
		}
	}

在用户登录成功后,就可以从返回信息中得到token,并在后面的请求中,都在请求头中添加上

(2)cors解决跨域问题

   在推荐文章中,我们可以了解到,要想跨域重要的是后台,返回信息,并且还有简单请求和非简单请求,我们应该在后台添加一个过滤器,例如

package com.mcu.system.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 跨域资源共享  解决前后端分离
 * @author 35168
 *
 */
public class CorsFilter extends OncePerRequestFilter{
	private static Logger logger = LoggerFactory.getLogger(CorsFilter.class);

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String origin = request.getHeader("Origin");
		if(null != origin && !"".equals(origin)){
			logger.debug("跨域资源共享  解决前后端分离Access-Control-Allow-Origin"+origin);
			response.addHeader("Access-Control-Allow-Origin",origin);//允许这个域名可以跨域
			response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with,token");//允许携带的请求头
			response.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");//支持的请求方法
			if(request.getMethod().equals(RequestMethod.OPTIONS.name())){
				response.setStatus(200);
				response.setHeader("Access-Control-Max-Age", "86400");//如果是预检请求,设置一个有效期,在有效期内就不会再有预检请求了
				return;
			}
		}
	    filterChain.doFilter(request,response);
	}

}

三、实现cors跨域+ajax请求在提交的时候带有cookie

这样我们不用重写shiro虎丘sessionId的方法,只要ajax请求带有cookie就可以了,ajax要想带有cookie信息,需要两个方面的工作

(1)、后台允许前台携带cookie

package com.mcu.system.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 跨域资源共享  解决前后端分离
 * @author 35168
 *
 */
public class CorsFilter extends OncePerRequestFilter{
	private static Logger logger = LoggerFactory.getLogger(CorsFilter.class);

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String origin = request.getHeader("Origin");
		if(null != origin && !"".equals(origin)){
			logger.debug("跨域资源共享  解决前后端分离Access-Control-Allow-Origin"+origin);
			response.addHeader("Access-Control-Allow-Origin",origin);
			response.addHeader("Access-Control-Allow-Credentials","true");//允许携带cookie
			response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with");
			response.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
			if(request.getMethod().equals(RequestMethod.OPTIONS.name())){
				response.setStatus(200);
				response.setHeader("Access-Control-Max-Age", "86400");
				return;
			}
		}
	    filterChain.doFilter(request,response);
	}

}

(2)、是前台提交的时候,要指定ajax可以携带cookie

$.post({
   url: '',
   dataType: "json",
   timeout:300000,
   xhrFields:{withCredentials: true},
   success: function (result) {
      
   },
   error: function () {

   }
});

这样,前台指定携带cookie,后台也允许携带cookie,那ajax在提交的时候就可以携带cookie,shiro框架也就可以从cookie中获取session

四、一些细节的问题

shiro框架,封装了很多的东西,在前后端分离的时候,我们需要考虑的,比如shiro框架实现了退出方法,然后跳转到登录页面,这个在我们前后端分离中就不太合适了,需要修改

package com.mcu.system.filter;

import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * 自定义退出的拦截器,退出返回json
 * @author 35168
 *
 */
public class SystemLogoutFilter extends LogoutFilter{
	 private static final Logger log = LoggerFactory.getLogger(SystemLogoutFilter.class);
	 
	 
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        try {
            subject.logout();
        } catch (SessionException ise) {
            log.debug("Encountered session exception during logout.  This can generally safely be ignored.", ise);
        }
        response.setCharacterEncoding("UTF-8");
        PrintWriter out = response.getWriter();
        JSONObject result = new JSONObject();
        result.put("code", 0);
        result.put("msg", "成功");
        out.println(result);
        out.flush();
        out.close();
        return false;
    }
}

重写shiro的退出过滤器,在注册的时候注册我们自己写的这个过滤器,其他的有返回页面的也都是类似的处理,保证给前台统一返回的都是json数据

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值