微服务下的登录实现及相关问题解决(代码片段)

darling2047 darling2047     2022-12-22     790

关键词:

  最近由于工作需要,需要开发一个登录的微服务;由于前期在网上找session共享的实现方案遇到各种问题,所以现在回过头来记录下整个功能的实现和其中遇到的问题;总结一下主要有以下几点:

  1、登录实现(整合redis以及用户信息的共享问题)

  2、登录拦截器的实现及拦截后成功跳转(这里踩了一个大坑)

  3、登录过期时间随用户的操作而跟新(即当用户操作时间大于设置的登录时间时不要让用户推出登录)

  4、非扫描类如何使用@autowired成功注入

  5、Springboot的全局异常捕获(初衷是为了解决2的跳转问题,最后兜了一大圈)

  下面对上面提到的几点进行详细记录;

一、登录实现(整合redis以及用户信息的共享问题)

  这是整个功能的核心所在,由于我们有多个服务所以首要解决的就是session共享的问题,解决这个问题主要是通过redis来实现的,我把登录成功后对session的操作全部换为对redis的操作,以userId为key,然后将userId返回给前台,在前台需要写一个common.js来重写ajax请求,使得每次访问后台的请求都自动带上userId,这样再写一个拦截器登录整个登录功能差不多就实现了;

二、登录拦截器的实现及拦截后成功跳转

  首先需要写一个拦截器的配置类,主要就是将我们自定义的拦截器注册到项目中以及白名单的添加;下面是注册拦截器的代码:

@Configuration
public class LoginInterceptorConfig extends WebMvcConfigurerAdapter
@Override
public void addInterceptors(InterceptorRegistry registry)
/**
* LoginInterceptor是自定义的拦截器
* addPathPatterns参数/**是通配符表示拦截所有的请求
* excludePathPatterns方法的参数是可变参数,可以输n个字符串类型的参数,用来添加白名单
*/
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login/doLogin");

  然后贴上我登录拦截器(LoginInterceptor)里的代码:

package com.huayun.base.interceptors;

import com.huayun.base.entity.UserBean;
import com.huayun.base.exception.BaseErrorCode;
import com.huayun.base.exception.BaseException;
import com.huayun.base.util.RedisUtil;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import redis.clients.jedis.Jedis;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 登陆拦截器
 *  主要判断请求中有没有token以及token有没有过期
 */
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter 

//    @Autowired
//    private RedisClusterUtil redisUtil;


    public static LoginInterceptor interceptor;

    @PostConstruct
    public void init()
        interceptor = this;
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        Object token = request.getParameter("token");
        String userId = request.getParameter("userId");
        String url = request.getRequestURI();
        if(isWhiteMenu(url))   //   /login/logout
            return true;
        
        if(token == null || userId == null) 
            JSONObject json = new JSONObject();
            json.put("code","10005");
            json.put("msg","登陆验证失败,请重新登陆!");
            response.setHeader("Access-Control-Allow-Origin","*");
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(200);
            response.getWriter().write(json.toString());
            return false;
//            throw new BaseException("登陆验证失败,请重新登陆",
//                    BaseErrorCode.VALIDATOR_ERROR);
        
//        UserBean user = null;
        try 
            // 获取key过期时间
            Long expireTime = RedisUtil.getExpire(userId);
            if(expireTime < 0)  // key不存在则登录超时
                JSONObject json = new JSONObject();
                json.put("code","10005");
                json.put("msg","登陆超时,请重新登陆!");
                response.setHeader("Access-Control-Allow-Origin","*");
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(200);
                response.getWriter().write(json.toString());
                return false;
            
            if(expireTime < 60*5) // 如果过期时间小于5分钟秒则重置过期时间
                RedisUtil.expire(userId,30*60);
            
//            user = (UserBean)RedisUtil.get(userId);// redis单机
//            user = (UserBean) RedisClusterUtil.getObject(userId.toString());//redis哨兵
        catch (Exception ex) 
//            return true;//ruturn true 是为了当redis连接出问题时程序能正常运行,但没有进行登陆过期的判断
            ex.printStackTrace();
            throw new BaseException("Redis连接异常",
                    BaseErrorCode.VALIDATOR_ERROR);
        
//        if(user == null) 
//            JSONObject json = new JSONObject();
//            json.put("code","10005");
//            json.put("msg","登陆超时,请重新登陆!");
//            response.setHeader("Access-Control-Allow-Origin","*");
//            response.setContentType("application/json;charset=utf-8");
//            response.setStatus(200);
//            response.getWriter().write(json.toString());
//            return false;
////            throw new BaseException("登陆超时,请重新登陆",
////                    BaseErrorCode.VALIDATOR_ERROR);
//        
        return true;
    

    /**
     * 判断是否白名单
     * @param url
     * @return
     */
    private boolean isWhiteMenu(String url) 
        if(url.contains("removeLoginParam") || url.contains("setLoginParam") ||
                url.contains("getYwzAndBdz") || url.contains("getYwzAndBdzInfo")
                || url.contains("searchStation") || url.contains("swagger-resources")
                || url.contains("configuration/ui") || url.contains("v2/api-docs"))
            return true;
        else
            return false;
        

    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception 

    

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 

    

 

  这其中拦截成功的跳转登录页我踩了一个大坑,起初很天真的想直接用跳转和转发,稍微想下也知道是不可能实现的,因为这完全是跨域的,百度说在response里添加Access-Control-Allow-Origin就可以了,但仅仅添加这个响应头也是不行的,因为我们是前后端分离的;这其中一个大神给的意见是让我自定义一个异常,拦截成功后直接抛异常然后进行捕获统一处理,他自己曾经就这样试过,然后我就屁颠屁颠的把他的代码拿来改了,最后的确是成功跳转了,但后来回看代码其实成功的关键并不在于异常的捕获然后捕获成功后的处理;也就是说我不捕获异常直接在拦截器里写也是可以成功跳转的;

  关键是跳转的这段逻辑,由于跨域、前后端分离等原因从后台直接跳转实现不了,所以换个思路,不直接跳转,而是给拦截到的请求进行自定义响应,让ajax的success回调能捕获到我们返回的登录被拦截的信息最后从前端跳登录页;关键代码如下:

          JSONObject json = new JSONObject();
                json.put("code","10005");
                json.put("msg","登陆超时,请重新登陆!");
                response.setHeader("Access-Control-Allow-Origin","*");
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(200);
                response.getWriter().write(json.toString());

   标红的缺一不可,其实起初我也想到了这个实现方法,但是不论怎么写ajax的回调捕获到的响应信息都是空,最后发现我们可以自定义请求的http响应码response.setStatus(200);

   至此,登录拦截器也基本实现了,接下来说说一些小细节

三、登录过期时间随用户的操作而跟新

  这个bug是给领导演示的时候发现的,我默认redis的key过期时间为30分钟,那天演示的时候领导一直在操作页面结果三十分钟到了,果断跳到登录页了,这下领导不满意了,你这个怎么这样呀,不能实时获取用户的操作状态吗?这样用户体验太差了;我表面点头称是其实心里在想哪个用户像你这样一点点半个小时的呀,哈哈;话虽这么说bug还是要改掉的;想到的第一个解决方案就是写监听器监听session的状态,只要监听到用户在操作就跟新过期时间,先不说这样平白无故添加了n次redis的操作,就连监听器我都实现不了;因为我们是多个服务session没有实现真正意义上的共享.无法有效监听;所以后来想到了一个超级简单的方法,就是在拦截器里获取过期时间的同时添加一个判断,当有效时间小于五分钟时重新更新有效时间,这样就不会新增无畏的redis操作了。关键代码如下:

       // 获取key过期时间
            Long expireTime = RedisUtil.getExpire(userId);
            if(expireTime < 0)  // key不存在则登录超时
                JSONObject json = new JSONObject();
                json.put("code","10005");
                json.put("msg","登陆超时,请重新登陆!");
                response.setHeader("Access-Control-Allow-Origin","*");
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(200);
                response.getWriter().write(json.toString());
                return false;
            
            if(expireTime < 60*5) // 如果过期时间小于5分钟秒则重置过期时间
                RedisUtil.expire(userId,30*60);
            

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  












基于sessionredis短信登录功能实现,解决session共享,登录状态刷新问题及threadlocal线程隔离(代码片段)

短信登录功能实现短信登录1、准备工作1.1导入数据库1.2当前项目模型1.3导入后端项目1.4导入前端工程1.5运行前端项目2、基于Session实现登录流程发送验证码:短信验证码登录、注册:校验登录状态:2.1实现发送短信验证码功能页... 查看详情

微服务架构及幂等性(代码片段)

微服务架构微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将功能分解到离散的各个服务当中,从而降低系统的耦合性,并提供更加灵活的服务支持。和微服务相... 查看详情

jhipster创建微服务及相关微服务架构组件介绍(代码片段)

...使用jhipster创建应用在线使用jdl生成器创建数据表和相应服务端代码一.创建微服务微服务是一种JHipster应用程序,它没有前端(必须在网关)上生成Angular前端),并且可以与JHipsterRegistry一起配置,发现和管理。创建微服务应用安... 查看详情

基于threadlocal和jwt登录的问题,微服务登录架构解决方案(代码片段)

https://www.bilibili.com/video/BV1f3411G7xk公司之前是以JWT+ThreadLocal做的登录系统,在使用的过程发现了如下的问题,下面我们一起来看看,后面也会给出更好的解决方案。一、基于JWT+ThreadLocal实现登录1-1、JWT所谓的JWT是... 查看详情

08-微服务版单点登陆系统(sso)实践(2107)(代码片段)

...统概述单点登陆系统解决方案设计单点登陆系统初步设计服务设计工程结构设计SSO父工程创建及初始化创建父工程父工程pom文件初始配置系统基础服务工程设计及实现业务描述表结构设计工程数据初始化创建系统服务工程并初始... 查看详情

微服务框架下的数据一致性第二篇(代码片段)

  上篇文章主要讲述的是如何实现的思路问题,本文内容主要是结合我们代码的具体实现来讲解一下(springboot环境下实现)。  首先我们需要定义一些常量和工具类:如ExchangeEnum(交换器常量)和QueueEnum(队列常量)以及... 查看详情

cors跨域实现思路及相关解决方案(代码片段)

...NPCORS,BROWSER支持情况主要用途Ajax请求跨域资源的异常CORS实现思路安全说明CORS几种解决方案自定义CORSFilterNginx配置支持Ajax跨域支持多域名配置的CORSFilterkeyword:cors,跨域,ajax,403,filter,RESTful,origin,http,nginx,jsonp原创作品,转载请附带 查看详情

redis解决秒杀微服务抢购代金券超卖和同一个用户多次抢购(代码片段)

文章目录超卖和同一用户多次抢购问题分析解决库存超卖问题添加相关枚举添加RedisTemplate配置类改造原先添加代金券逻辑改造下单逻辑调整数据库相关为redisRedis+Lua解决超卖问题解决同一用户多次抢购问题问题描述Redisson分布... 查看详情

12-2107课上问题分析及总结(代码片段)

文章目录Day01~微服务架构入门核心知识点常见问题分析常见Bug分析课后作业作业答案Day02~Nacos注册中心入门核心知识点常见问题分析常见Bug分析课后作业Day03~服务发现及调用核心知识点常见问题分析常见Bug分析课后作业Day01~微服... 查看详情

git相关问题及解决方案(代码片段)

GitPullFailedRecvfailure:Connectionwasresetgitpull拉取代码时出现如下错误:gitpullfailedunabletoaccess'repositoryurl':Recvfailure:Connectionwasreset解决方法:gitremoteremoveorigingitremoteaddorigi 查看详情

rsync服务介绍及相关实验(代码片段)

...行数据同步。四、rsync相关实验1、ssh协议数据同步,将NFS服务器数据同步备份到rsync服务器实现数据下载格式:rsync-avz服务器地址:/服务器目录/*/本地目录示例:rsync-avzroot@100.100.10 查看详情

基于kafka构建事件溯源模式的微服务(代码片段)

概要本文中我们将讨论如何借助Kafka实现分布式消息管理,使用事件溯源(EventSourcing)模式实现原子化数据处理,使用CQRS模式(Command-QueryResponsibilitySegregation)实现查询职责分离,使用消费者群组解决单点故障问题,理解分布式... 查看详情

基于kafka构建事件溯源模式的微服务(代码片段)

概要本文中我们将讨论如何借助Kafka实现分布式消息管理,使用事件溯源(EventSourcing)模式实现原子化数据处理,使用CQRS模式(Command-QueryResponsibilitySegregation)实现查询职责分离,使用消费者群组解决单点故障问题,理解分布式... 查看详情

vue实现企业微信扫码登录后台管理系统(代码片段)

...取url后面拼接的code,用code去请求后台接口后台接口服务那边用access_token和code去获取用户的企业微信号通过企业微信号查找数据库中是否存在,存在返回用户的基本信息,否则不存在返回提示‘该用户非企业人员’代... 查看详情

解析微服务架构:微服务重构应用及ibm解决方案

解析微服务架构系列文章将分几篇描述微服务的定义、特点、应用场景、企业集成架构的演进以及微服务转型思路和技术决策考虑等内容,并以IBM技术为例介绍如何实现微服务架构转型。上一篇文章介绍了融入微服务的企业集成... 查看详情

跟着小程学微服务-mock自动化系统的原理及实现(代码片段)

...测试人员面临的测试问题我公司目前用的是基于Dubbo的微服务改造,服务之间的调用链路冗长,每个服务又是单独的团队在 查看详情

企业级工作流解决方案--微服务消息处理模型之与abp集成(代码片段)

...laims.ClaimsPrincipal里面,但是用户的身份信息如何在不同的服务之间传递呢,不可能每一个服务都必须实现这套身份认证吧?比如我们请求调用过程如下:  Portal站点获取用户信息没有问题,但如何传递到调用的其他 查看详情

解决微服务架构下流量有损问题的实践和探索

简介:绝⼤多数的软件应⽤⽣产安全事故发⽣在应⽤上下线发布阶段,尽管通过遵守业界约定俗成的可灰度、可观测和可滚回的安全⽣产三板斧,可以最⼤限度的规避发布过程中由于应⽤⾃身代码问题对⽤户造成的影... 查看详情