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

这篇具有很好参考价值的文章主要介绍了登录认证方式汇总,例如ThreadLocal+拦截器+Redis、JWT。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

登录方式汇总

先讲讲传统的登录方式

1.Cookie方案

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

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

缺点:

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

2.传统Session方案

最经典的一种

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

传统解决方式:

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

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

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

简单示例

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

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

访问接口

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

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

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

缺点

  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是存储在客户端的。

流程

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

  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令牌来访问受保护的资源。

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

密钥管理的方式:文章来源地址https://www.toymoban.com/news/detail-733216.html

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

到了这里,关于登录认证方式汇总,例如ThreadLocal+拦截器+Redis、JWT的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpingMVC拦截器-用户登录权限控制分析

    1.1 这个后台工程,没有进行相关操作也能够进行登录: 4.1 5.1  选择test那个文件: 17.1.var可以获得session对象: 17.2 获取谁啊!获取user,返回的是user对象  17.3 强转之后做判断:  17.4 如果判断没有登录,这里跳转,这里我要设置重定向命令:   17.5 重定向 17.6 内部requestCon

    2024年02月11日
    浏览(38)
  • 登录页面jwt密钥,过滤器,拦截器,异常处理

    需求: 用户未登录时,访问其他也没面,操作添加、删除等操作时,强行跳转至登录页面。 实现方法: 1.使用Cookie,登录后后端添加一个cookie,每次页面判断是否有cookie, 2。使用session,原理同上,只不过session是存储在服务器里的,cookie是在浏览器里。 3。使用jwt令牌,登

    2023年04月25日
    浏览(53)
  • Spring MVC拦截器Interceptor使用(判断用户登录)

    Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。 拦截器可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是

    2024年02月09日
    浏览(46)
  • 登录用户信息获取 网关+拦截器+feign请求添加请求头

    给所有请求添加用户身份 网关已经给所有请求添加了用户身份,也就是authorization头信息。   创建ThreadLocal工具类 : 创建拦截器:  将拦截器注册到SpringMvc,让它生效:  将以上代码(拦截器,config,utils) 放到哪个微服务中,哪个微服务/**路径就会有拦截功能 没有用户信息的请求将会

    2024年02月09日
    浏览(45)
  • Vue3 axios响应拦截器处理接口返回401未登录跳转登录页

    问题: 在 asiox 使用 useRouter 实例化创建 router 路由对象,在 response 响应拦截器里为 undefined 访问不到 使用 window.location.href = \\\'/login\\\' 跳转登录页, 本地可以正常跳转,测试环境页面会显示 not found 404, 测试环境访问地址大概是这样 ip:8080/pm/#/login , 本地没有 /pm 解决方案: 在 mai

    2024年01月21日
    浏览(47)
  • anxios封装拦截器的两种方式

    使用方法 讲解:函数式通过调用方法创建axios实例,通过参数传入基础url,超时时间等定义参数。 使用时比较接近axios写法。 使用方法 类方法: 通过创建一个类,传入baseUrl,超时时间等自定义参数。使用时候调用类的方法实现创建axios实例。 两种方法写法不同,功能相同。

    2024年01月19日
    浏览(93)
  • 以配置的方式开关axios拦截器功能

    前景提要: ts 简易封装 axios,统一 API axios 很多额外功能都是基于拦截器实现。有些功能想要全局使用,因此将拦截器注册在全局。比如重复请求过滤。但也有一小部分请求不希望进行过滤,比如并发上传文件。 因此希望可以在具体的请求方法上,通过配置 config 从而决定针

    2024年02月06日
    浏览(41)
  • 【Java Web】用拦截器的方式获取用户信息

    流程:从cookie中获取凭证,根据凭证查询用户,并在本次请求中持有用户,在视图模板上显示登录用户的信息。 1. 定义拦截器 2. 配置拦截器

    2024年02月10日
    浏览(42)
  • 【Spring实战项目】SpringBoot3整合WebSocket+拦截器实现登录验证!从原理到实战

    🎉🎉 欢迎光临,终于等到你啦 🎉🎉 🏅我是 苏泽 ,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.

    2024年04月17日
    浏览(58)
  • 【Spring Boot】拦截器与统一功能处理:统一登录验证、统一异常处理与统一数据返回格式

     Spring AOP是一个基于面向切面编程的框架,用于将横切性关注点(如日志记录、事务管理)与业务逻辑分离,通过代理对象将这些关注点织入到目标对象的方法执行前后、抛出异常或返回结果时等特定位置执行,从而提高程序的可复用性、可维护性和灵活性。但使用原生Sp

    2024年02月16日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包