前后端分离验证码存储,session处理

问题描述

前后端分离,验证码存储到session中,postman测试可以获取到session中的验证码,但是在另一台电脑上获取session中的验证码却始终为null,试了各种跨域、携带cookie都不管用。

前言

一个之前完成的项目说要加些新需求要我们做,我们肯定不能拒绝啊,开干。

打开项目,运行,浏览器输入地址,输入用户名、密码、验证码,点击登录,???验证码已失效???啥情况?断点一看,好家伙,获取存储在session中的验证码一直是null,再将存储验证码的session和登录时从请求获取的session一对比,根本不是同一个session,它就一直在变!然后我自己在postman中测试是没有问题的。

哦,懂了,是前后端分离跨域的问题,设置好后,再试,还是不行!设置允许携带cookie,再试,依旧不行。。。

百度找了好久前后端分离session处理,网上大部分都是说前端请求加上withCredentials: true ,后端在拦截器或过滤器设置

response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With, Content-Type, Accept,token");
response.setHeader("Access-Control-Allow-Credentials","true");

但是不行。就很奇怪,之前都没这种问题啊,怎么这次代码都没改过,就会有这种问题?

因为这个项目一开始是别人已经做好的,然后给我们公司修改一些细节,bug之类的,然后整个项目也没用shiro和redis。权限控制,存储信息也都是用session的,然后我想过用redis来存储验证码吧,但是用redis的话,要改挺多配置的,挺麻烦的,而且这个项目本来就挺乱的,就放弃了。

暂时找不到什么好的办法,就只能先将验证码验证去掉,因为甲方部署这个项目是将前端后端都部署到一台机器的,也不存在跨域问题,所以我们自己这边测试的话,就先不用验证码了。

这个项目分为app端登录和web端登录,它app端登录是将token存储到数据库中,每次请求都是根据token去数据库查询这个token是否过期。web端则是将token存储到session中,结果可想而知,web端登录进去后,好多请求都报权限不足的错误。后面干脆把web端也和app端一样,都将token存储到数据库中,就不用session了,权限问题得以解决。

后面我就想,能不能也让验证码的session像token一样,保存。登录的时候,将这个sessionid直接设置到请求头中,那样应该可以吧?仔细想了下,感觉可行,那么这样的话,问题就主要在如何根据sessionid来获取session。

解决

1、SessionContextUtils 用于获取session

import javax.servlet.http.HttpSession;
import java.util.HashMap;

/**
 * SessionContext用于获取session
 * 根据sessionId获取session
 */
public class SessionContextUtils {
    private static SessionContextUtils instance;
    private HashMap<String, HttpSession> sessionMap;

    private SessionContextUtils() {
        sessionMap = new HashMap<String,HttpSession>();
    }

    public static SessionContextUtils getInstance() {
        if (instance == null) {
            instance = new SessionContextUtils();
        }
        return instance;
    }

    public synchronized void addSession(HttpSession session) {
        if (session != null) {
            sessionMap.put(session.getId(), session);
        }
    }

    public synchronized void delSession(HttpSession session) {
        if (session != null) {
            sessionMap.remove(session.getId());
        }
    }

    public synchronized HttpSession getSession(String sessionID) {
        if (sessionID == null) {
            return null;
        }
        return sessionMap.get(sessionID);
    }
}

2、SessionListener 用于监听session

import com.utils.SessionContextUtils;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * 监听session
 */
@WebListener
public class SessionListener implements HttpSessionListener {

    private SessionContextUtils sessionContext= SessionContextUtils.getInstance();

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        sessionContext.addSession(session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        sessionContext.delSession(session);
    }
}

3、在启动类加上@ServletComponentScan注解

4、验证码生成

@ResponseBody
@GetMapping(value = "/get/code")
public JSONObject getCode(HttpServletRequest request, HttpServletResponse response) {
    try {
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");

        // 生成随机字串(VerifyCodeUtils验证码生成工具类就不贴出来了,网上随便一找好多的)
        String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
        // 存入会话session
        HttpSession session = request.getSession(true);
        // 删除以前的
        session.removeAttribute("verCode");
        session.removeAttribute("codeTime");
        session.setAttribute("verCode", verifyCode.toLowerCase());
        session.setAttribute("codeTime", LocalDateTime.now());
        // 生成图片
        int w = 200, h = 50;
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        //注意,这里是将生成的图片转为base64的格式将返回给前端,而不是直接给张图片的地址。
        //这样获取验证码就是一个请求,而不是一张图片的地址,因为是一个请求,所以我们可以返回sessionid给前端。
        VerifyCodeUtils.outputImage(w, h, stream, verifyCode);
        try{
            String encode = Base64.encode(stream.toByteArray());
            Map<String,Object> map = new HashMap<>();
            map.put("img","data:image/png;base64,"+ encode);
            //返回sessionid给前端,前端只需要在调用登录接口的时候将这个sessionid设置到请求头中即可
            map.put("sessionId",session.getId());
            return success(map,"");
        }catch (Exception e){

        }finally{
            stream.close();
        }
    } catch (Exception e) {
        System.out.println("验证码获取错误");
    }
    return failure("");
}

5、登录接口

@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST)
public JSONObject login(@NotBlank(message = "用户名不能为空") String username, @NotBlank(message = "密码不能为空") String password,String code, HttpServletRequest request) {

    Map<String, Object> data = new HashMap<>();
    //获取请求头中的sessionId,然后根据sessionId来获取session
    String sessionId = request.getHeader("sessionId");
    SessionContextUtils sessionContext= SessionContextUtils.getInstance();
    HttpSession session = sessionContext.getSession(sessionId);
    // 验证码
    Object verCode = session.getAttribute("verCode");
    if (null == verCode) {
        data.put("msg", "验证码已失效,请重新输入");
        data.put("type", 3);
        return failure(data);
    }

    String verCodeStr = verCode.toString();
    LocalDateTime localDateTime = (LocalDateTime) session.getAttribute("codeTime");
    ZoneId zoneId = ZoneId.systemDefault();
    ZonedDateTime zdt = localDateTime.atZone(zoneId);
    Date codeDateTime = Date.from(zdt.toInstant());

    if (verCodeStr == null || StringUtils.isEmpty(code) || !verCodeStr.equalsIgnoreCase(code)) {
        data.put("msg", "验证码错误");
        data.put("type", 3);
        return failure(data);
    } else if (((System.currentTimeMillis()) - (codeDateTime.getTime())) > (1 * 60 * 1000)) {
        data.put("msg", "验证码已过期,请重新输入");
        data.put("type", 3);
        return failure(data);
    } else {
        //验证成功,删除存储的验证码
        session.removeAttribute("verCode");
        session.removeAttribute("codeTime");
    }

    //省略其他代码
    return success(map, "登录成功");
}

大功告成,重启项目,测试,可以了,无论是postman还是另一台电脑测试都没问题。

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

符华-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值