登录认证方式汇总,例如ThreadLocal+拦截器+Redis、JWT

news2024/12/24 20:44:32

登录方式汇总

先讲讲传统的登录方式

1.Cookie方案

用cookie作为媒介存放用户凭证。

用户登录系统之后,会返回一个加密的cookie,当用户访问子应用的时候会带上这个cookie,授权以解密cookie并进行校验,校验通过后即可登录当前用户。

缺点:

Cookie不安全,Cookie是存到客户端的,攻击者可以伪造Cookie伪造成特定用户。

2.传统Session方案

最经典的一种

因为HTTP协议是一种无状态的协议,这就意味着,某个用户登录之后,下一次他再请求时,用户还得登录,因为客户端和服务器是多对一的关系,所以我们不知道那些用户登录了,哪些没登录。

传统解决方式:

1. 用户第一次发送登录请求时,用Session存下来用户的信息,将SessionID用Cookie传给客户端。

2. 之后这个浏览器每次访问服务器的时候,看它的Cookie里面有没有SessionID,用SessionID找到对应的Session,找到了那就说明登录了,没找到就是没登录。

简单示例

SpringBoot项目中写一个简单的接口测试一下:

 

访问接口

可以在浏览器的Cookie中看到有JSESSIONID,就是SessionID

缺点

  1. 用户多了之后,Session全部存到服务器中,服务器开销大。
  2. 分布式的应用中,有多台服务器,那么Session是存在单个服务器上的,不共享Session,如果用户在服务器1上登录之后,下次的请求跳转到服务器2上了,就需要再次登录。

3.分布式Session方案

为了解决分布式系统中,多服务器是不共享session,传统的单机session不适用于分布式系统中,所以这里使用分布式session。

实现分布式session有四种方案:

  1. 数据库统一存储
  2. session复制
  3. 客户端存储
  4. HASH一致性

具体请看:分布式session解决方案_半格咖啡的博客-CSDN博客

流程:

(1) 用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;

(2) 用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;

(3) 一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;

(4) 存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;

缺点

(1)服务器压力增大:通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。(可以将数据保存在磁盘中)

(2)扩展性不强:如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。(可以使用分布式session将session在各个集群中保持一致)

(3)CSRF跨站伪造请求攻击:session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

ThreadLocal + 拦截器 + Redis 实现登录鉴权

就是上面所说的 统一在数据库存储的方法。

最终实现的效果

这种方式实现了:允许在分布式环境中实现线程隔离的单点登录,确保用户在不同的请求之间共享登录状态,并使用Redis作为分布式会话存储来保持会话一致性。

其实就是解决了两个问题

  1. 分布式环境中Session不共享的问题。
  2. 多线程环境下并发导致不安全的问题。

解释

在分布式的环境下,因为Session是存到服务器上面的,使用session会出现Session不共享数据的问题。那么可以将共享数据存入数据库中,然后应用服务器就可以去数据库获取共享数据。

对于每一次请求,可以在一开始从数据库里取到数据,然后将其临时存放在本地的内存里。

考虑到线程安全的问题,所以使用threadlocal进行线程隔离,这样在本次请求的过程中,就可以随时获取到这份共享数据了。

所以,session的替代方案是数据库,ThreadLocal在这里起辅助的作用。

数据库不建议用mysql,访问慢,用Redis的话访问快。

具体实现流程:

  1. 在登录业务代码中,当用户登录成功时,生成一个登录凭证存储到redis中,将凭证中的字符串保存在cookie中返回给客户端。
  2. 以后每次的请求,使用一个拦截器拦截请求,从cookie中获取凭证字符串。
  3. 将字符串与redis中的凭证进行匹配,获取用户信息,将用户信息存储到ThreadLocal中,在本次请求中持有用户信息,即可在后续操作中使用到用户信息。

登录凭证类:

//登录凭证表
@Data
@ApiModel("登录凭证类")
public class LoginTicket {

    private int id;
    private int userId;
    //登录凭证字符串
    private String ticket;
    private int status;
    private Date expired;
}

拦截器

  1. 拦截每一次请求,从request中获取cookies。
  2. 从cookies里面找到 凭证字符串与redis中的凭证进行匹配,获取用户信息。
  3. 将用户信息存储到ThreadLocal中,在本次请求中持有用户信息,即可在后续操作中使用到用户信息。

SpringMVC拦截器使用介绍:

1.创建拦截器类

创建一个DemoInterceptor类实现HandlerInterceptor接口。

重写preHandle(),postHandle(),afterCompletion() 三个方法,如下代码,我们就创建了一个Spring的拦截器。

  • preHandle():在controller执行请求之前执行
  • postHandle():在controller执行请求之后执行,在模板引擎(例如Thymeleaf)之前执行
  • afterCompletion() :在模板引擎之后执行

2.编写拦截器配置类

选择要拦截哪些请求,放行那些资源

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor())
                //放行静态资源
                .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg")
                //拦截 注册 登录请求
                .addPathPatterns("/register","/login");
    }
}
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    // 保存登录信息
    // 调用时间:Controller方法处理之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //从cookie中获取凭证
        String ticket = CookieUtil.getValue(request, "ticket");

        if (ticket != null){
            //查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            //检查凭证是否有效
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())){
                //根据凭证查询用户
                User user = userService.findByUserId(loginTicket.getUserId());
                //在本次请求中持有用户
                hostHolder.setUser(user);
                //构建用户认证的结果,并存入SecurityContext,以便于Security进行授权
                Authentication authentication = new UsernamePasswordAuthenticationToken(
                        user, user.getPassword(), userService.getAuthorities(user.getId()));
                SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
            }
        }

        return true;
    }

    // 调用时间:Controller方法处理完之后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //得到当前线程持有的user
        User user = hostHolder.getUser();
        if (user != null && modelAndView != null){
            modelAndView.addObject("loginUser", user);
        }
    }

    // 调用时间:DispatcherServlet进行视图的渲染之后
    // 请求结束,把保存的用户信息清除掉
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        hostHolder.clear();
        SecurityContextHolder.clearContext();
    }
}

ThreadLocal

他是什么?

  • 可以理解成它是一个特殊的map,它的key就是线程本身,value就是你想存储的数据。
  • ThreadLocal本质是以线程为key存储元素
  • ThreadLocal可以把用户信息保存在线程中,用户的每一次请求,就是一个线程,保存了用户信息,方便我们在后续操作获取用户登录信息。
  • 当请求结束,也就是用户点击了退出登录之后,我们会把保存的用户信息清除掉,防止内存泄漏。

为什么用这个ThreadLocal?

        解决并发带来的问题,有助于确保每个用户的登录状态在不同的线程中得到正确维护和隔离,防止了混淆、安全问题、并发问题和会话管理问题,从而提高了系统的可靠性和安全性。

        为了防止多进程对用户信息修改造成的数据不一致,因此需要保证每个请求(线程)访问自己的资源(用户信息)。隔离起来,就不会发送资源错误的问题了。

        所以在后续请求中,这个线程一直是存活的,ThreadLocal里面的数据也是一直在的。

        当请求处理完,服务器向浏览器做出响应之后,这个线程就被销毁了,我们会把保存的用户信息清除掉,防止内存泄漏。

/**
 *持有用户信息,用于代替session对象
 */
@Component
public class HostHolder {

    //ThreadLocal本质是以线程为key存储元素
    private ThreadLocal<User> users = new ThreadLocal<>();

    public void setUser(User user){
        users.set(user);
    }

    public User getUser(){
        return users.get();
    }

    public void clear(){
        users.remove();
    }
}

还有一种常用的:JWT认证

        登录功能的实现可以有多种方式,具体取决于开发人员的需求和技术选择。有传统的Session会话机制的,也有JWT的。

        JSON Web Token 是个令牌,就是通过Json形式作为web应用中的令牌,用于在各方之间安全的将信息作为JSON对象传输。

        JSON Web Token是在各方之间安全地传输信息的好方法。

        因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

JWT是存储在客户端的。

流程

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名
  3. 形成一个JWT(Token)。形成的JWT就是一个形同111.zzz.xxx的字符串。token head.payload.singurater
  4. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  5. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)HEADEF。
  6. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  7. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

优势

  1. 传输速度快:可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度快。
  2. 自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库。
  3. 跨语言:因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  4. 支持分布式:因为是存储到客户端的,所以不需要在服务端保存会话信息。

 

结构

token string ====> header.payload.singnature token

JWT令牌组成

  • 1.标头(Header)
  • 2.有效负载(Payload)
  • 3.签名(Signature)
  • 因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz Header.Payload.Signature

 

1.Header(标头)

  • 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。
  • 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
  "alg": "HS256",
  "typ": "JWT"
}

2.Payload(有效负载)

  • 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。
  • 同样的,它会使用Base64 编码组成 JWT 结构的第二部分
  • 官方不建议放用户的敏感信息,放些用户ID、用户名什么的没事,不要放密码。不安全。
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

3.Signature(签名)

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。

Signature 需要使用编码后的 header 和 payload以及我们提供的一个密钥

然后使用 header 中指定的签名算法(HS256)进行签名。

也就是:3.Signature = 编码后的1.Header + 编码后的2.Payload + 密钥

签名的作用是保证 JWT 没有被篡改过

例如

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret);

 

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。

如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。

如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

 

关于密钥

通常,密钥存储在服务器的配置文件或环境变量中,以确保只有授权的人员可以访问它。

密钥的安全性非常重要,因为如果密钥泄漏,攻击者可能会伪造有效的JWT令牌来访问受保护的资源。

不同的请求,服务器的密钥通常是相同的。

密钥管理的方式:

  1. 储在安全的配置文件或环境变量中:密钥应该存储在服务器上的安全位置,而不是硬编码在代码中,以避免泄漏。
  2. 定期轮换密钥:定期更改密钥以减小泄漏的风险。如果密钥不再安全,及时进行轮换。
  3. 限制访问:确保只有授权的人员可以访问密钥存储。使用访问控制和权限管理来限制对密钥的访问。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020897.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java的高校竞赛管理系统设计与实现(亮点:发起比赛、报名、审核、评委打分、获奖排名,可随意更换主题如蓝桥杯、ACM、王者荣耀、吃鸡等竞赛)

高校竞赛管理系统 一、前言二、我的优势2.1 自己的网站2.2 自己的小程序&#xff08;小蔡coding&#xff09;2.3 有保障的售后2.4 福利 三、开发环境与技术3.1 MySQL数据库3.2 Vue前端技术3.3 Spring Boot框架3.4 微信小程序 四、功能设计4.1 主要功能描述4.2 系统角色 五、系统…

linux常用命令(1):zip/unzip命令(压缩文件/解压缩文件)

文章目录 前言一、linux安装zip文件二、zip语法2.1、常用选项实例2.1.1、-r &#xff08;压缩文件夹&#xff0c;解决80%的场景&#xff09;2.1.2、-q2.1.3、-d&#xff08;从压缩文件中删除指定文件&#xff09;2.1.4、-u&#xff08;更新文件&#xff09;2.1.5、-f2.1.6、-m2…

leetcode1797. 设计一个验证系统(java)

设计一个验证系统 题目描述哈希表题目描述 题目描述 难度 - 中等 leetcode1797. 设计一个验证系统 你需要设计一个包含验证码的验证系统。每一次验证中&#xff0c;用户会收到一个新的验证码&#xff0c;这个验证码在 currentTime 时刻之后 timeToLive 秒过期。如果验证码被更新…

XGBoost算法讲解和公式推导

本文节选自《机器学习入门基础&#xff08;微课版&#xff09;》 10.5 XGBoost 算法 XGBoost 是 2014 年 2 月由华盛顿大学的博士生陈天奇发明的基于梯度提升算法(GBDT)的机器学习算法&#xff0c;其算法不但具有优良的学习效果&#xff0c;而且训练速度高效&#xff0c;在数据…

Linux下的系统编程——线程同步(十三)

前言&#xff1a; 在多线程编程中&#xff0c;如果多个线程同时访问和修改共享资源&#xff0c;可能会产生竞争条件和数据不一致的问题。同步机制用于协调线程之间的访问和操作&#xff0c;确保数据的正确性和一致性。为了避免多个线程同时访问和操作共享资源导致的问题&#…

小程序键盘没有【小数点】输入

<input v-model"formData.number" :auto-height"true" placeholder"请输入" confirm-type"done" type"digit" maxlength"11" input"inputNumber" />number&#xff1a;数字键盘&#xff08;没有小…

Minitab Express for Mac(数据分析软件)附破解补丁 v1.5.0 支持M1

Minitab Express是一款专为Mac用户设计的数据分析和统计软件。它提供了一套全面的工具和功能&#xff0c;用于分析数据、执行统计计算和生成可视化。 下载&#xff1a;Minitab Express for Mac(数据分析软件)附破解补丁 以下是 Minitab Express for Mac 的一些主要功能&#x…

随机森林案例分析

阅读随机森林模型前&#xff0c;建议首先阅读决策树模型手册&#xff08;点击后跳到决策树模型的帮助手册页面&#xff09;&#xff0c;因为随机森林模型实质上是多个决策树模型的综合&#xff0c;决策树模型只构建一棵分类树&#xff0c;但是随机森林模型构建非常多棵决策树&a…

数字虚拟人制作简明指南

如何在线创建虚拟人&#xff1f; 虚拟人&#xff0c;也称为数字化身、虚拟助理或虚拟代理&#xff0c;是一种可以通过各种在线平台与用户进行逼真交互的人工智能人。 在线创建虚拟人变得越来越流行&#xff0c;因为它为个人和企业带来了许多好处。 推荐&#xff1a;用 NSDT编辑…

阿里云无影云电脑和传统PC有什么区别?

阿里云无影云电脑和传统电脑PC有什么区别&#xff1f;区别大了&#xff0c;无影云电脑是云端的桌面服务&#xff0c;传统PC是本地的硬件计算机&#xff0c;无影云电脑的数据是保存在云端&#xff0c;本地传统PC的数据是保存在本地客户端&#xff0c;阿里云百科分享阿里云无影云…

低代码与低代码平台

随着数字化转型和软件需求的不断增长&#xff0c;传统的手写代码开发方式已经无法满足迅速推出应用程序的需求。为了加快软件开发的速度并降低技术门槛&#xff0c;低代码开发模式应运而生。本文将介绍低代码的概念&#xff0c;探讨什么是低代码、什么是低代码平台&#xff1f;…

RFID设备在自动化堆场中的管理应用

随着信息技术的高速发展&#xff0c;带动了港口生产和管理技术的长足进步&#xff0c;港口堆场内的自动化场桥的智能化水平成为码头提高生产率一个重要标签。各地海关着力于现代化科技手段&#xff0c;努力构筑新型的便捷通关模式&#xff0c;在进出口环节做好管理和服务。 全…

已解决 Kotlin Error: Type mismatch: inferred type is String but Int was expected

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

idea之maven的安装与配置

我们到maven的官网里下载maven&#xff0c;地址&#xff1a;https://maven.apache.org/download.cgi下载完成后解压即可配置环境变量 此电脑–>右键–>属性–>高级系统设置–>环境变量–>系统变量&#xff08;S&#xff09;–>新建一个系统变量 变量名&…

【开发记录01】开发环境副本/页的导入&带用户权限管理系统

在蒋老师的指导下大概了解了: 1.开发环境的数据导入/导出 共享组件的同步 因为应用程序277是应用程序100的子程序&#xff0c;所以共享组件必须和100保持一致。 但是会出现一个小问题&#xff1a; 在APEX开发过程中同时打开两个不同的应用程序&#xff0c;但是编辑过程中经…

Java 华为真题-猴子爬山

需求&#xff1a; 一天一只顽猴想去从山脚爬到山顶&#xff0c;途中经过一个有个N个台阶的阶梯&#xff0c;但是这猴子有一个习惯&#xff1a;每一次只能跳1步或跳3步&#xff0c;试问猴子通过这个阶梯有多少种不同的跳跃方式&#xff1f; 输入描述 输入只有一个整数N&#xff…

Python - 小玩意 - 键盘记录器

pip install keyboardimport keyboard import timedef get_time():date_time time.strftime("%Y-%m-%d %H:%S", time.localtime())return date_timedef abc(x):if x.event_type down:print(f"{get_time()}你按下了{x.name}")with open(./键盘记录器.txt,…

CG Magic分享同一场景里下,VR渲染器和CR渲染器哪个好?

渲染操作时&#xff0c;VR渲染器和CR渲染器的对比成为常见问题了。这个问题很多人都会问。 今天CG Magic小编通过一个真实的项目&#xff0c;就是同一场景下来比较一下VR渲染器和CR渲染器的区别。 以下图为例是用来测试的场景当年的最终图。采用了当年的一个伊丽莎白大街152号的…

SwiftUI 导航设置

文章目录 一、导航跳转二、导航设置三、Present跳转&#xff08;模态跳转&#xff09;四、返回页面 一、导航跳转 页面A import SwiftUIstruct NavJumpAView: View {State var isNavPush falsevar body: some View {NavigationView {VStack {NavigationLink(isActive: $isNa…

Epub如何转换成PDF格式,收藏好着两个工具!不要太简单~

你是不是经常遇到这样的情况&#xff1a;你下载了一本很想看的Epub格式的电子书&#xff0c;但是你的电脑或者手机没有支持Epub格式的阅读器&#xff0c;或者你觉得Epub格式的阅读效果不够好&#xff0c;你想要将它转换为PDF格式&#xff0c;但是你又不知道用什么软件或者网站可…