Spring Security OAuth2.0(四)-----OAuth2+JWT

news2025/1/13 9:45:27

传统的通过 session 来记录用户认证信息的方式我们可以理解为这是一种有状态登录,而 JWT 则代表了一种无状态登录。「无状态登录天然的具备单点登录能力」

1. 无状态登录

1.1 什么是有状态

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,不支持集群化部署

1.2 什么是无状态

微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

那么这种无状态性有哪些好处呢?

  • 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
  • 减小服务端存储压力

1.3 如何实现无状态

无状态登录的流程:

  • 首先客户端发送账户名/密码到服务端进行认证
  • 认证通过后,服务端将用户信息加密并且编码成一个 token,返回给客户端
  • 以后客户端每次发送请求,都需要携带认证的 token
  • 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息

1.4 JWT

1.4.1 简介

JWT,全称是 Json Web Token , 是一种 JSON 风格的轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权:https://jwt.io/
JWT 作为一种规范,并没有和某一种语言绑定在一起,常用的 Java 实现是 GitHub 上的开源项目 jjwt,地址如下:https://github.com/jwtk/jjwt

1.4.2 JWT 数据格式

JWT 包含三部分数据:

1.Header:头部,通常头部有两部分信息:

  • 声明类型,这里是JWT
  • 加密算法,自定义
  • 我们会对头部进行 Base64Url 编码(可解码),得到第一部分数据。

2.Payload:载荷,就是有效数据,在官方文档中(RFC7519),这里给了 7 个示例信息:

  • iss (issuer):表示签发人
  • exp (expiration time):表示token过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号
  • 这部分也会采用 Base64Url 编码,得到第二部分数据。

3.Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥 secret(密钥保存在服务端,不能泄露给客户端),通过 Header 中配置的加密算法生成。用于验证整个数据完整和可靠性。
生成的数据格式如下图:

在这里插入图片描述
注意,这里的数据通过 . 隔开成了三部分,分别对应前面提到的三部分,另外,这里数据是不换行的,图片换行只是为了展示方便而已。

1.4.3 JWT 交互流程

流程图:

在这里插入图片描述
步骤翻译:

  1. 应用程序或客户端向授权服务器请求授权
  2. 获取到授权后,授权服务器会向应用程序返回访问令牌
  3. 应用程序使用访问令牌来访问受保护资源(如API)
    因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,这样就符合了 RESTful 的无状态规范。

1.5 JWT 存在的问题

说了这么多,JWT 也不是天衣无缝,由客户端维护登录状态带来的一些问题在这里依然存在,举例如下:

  1. 续签问题,这是被很多人诟病的问题之一,传统的 cookie+session 的方案天然的支持续签,但是 jwt 由于服务端不保存用户状态,因此很难完美解决续签问题,如果引入 redis,虽然可以解决问题,但是 jwt 也变得不伦不类了。
  2. 注销问题,由于服务端不再保存用户信息,所以一般可以通过修改 secret 来实现注销,服务端 secret 修改后,已经颁发的未过期的 token 就会认证失败,进而实现注销,不过毕竟没有传统的注销方便。
  3. 密码重置,密码重置后,原本的 token 依然可以访问系统,这时候也需要强制修改 secret。
  4. 基于第 2 点和第 3 点,一般建议不同用户取不同 secret。

我们目前OAuth2存在的问题

在前面的文章中,授权服务器派发了 access_token 之后,客户端拿着 access_token 去请求资源服务器,资源服务器要去校验 access_token 的真伪,所以我们在资源服务器上配置了 RemoteTokenServices,让资源服务器做远程校验:
在这里插入图片描述
在高并发环境下这样的校验方式显然是有问题的,如果结合 JWT,用户的所有信息都保存在 JWT 中,这样就可以有效的解决上面的问题。

解决方案 -OAuth2+JWT

首先我们来看对授权服务器authorization_server的改造,我们来修改 AccessTokenConfig 类,如下:

package com.xql.authorization_server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class AccessTokenConfig {

    private String SIGNING_KEY = "xql";

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Bean
    TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}

这里的改造主要是两方面:

  1. TokenStore 我们使用 JwtTokenStore 这个实例。之前我们将 access_token 无论是存储在内存中,还是存储在 Redis 中,都是要存下来的,客户端将 access_token 发来之后,我们还要校验看对不对。但是如果使用了 JWT,access_token 实际上就不用存储了(无状态登录,服务端不需要保存信息),因为用户的所有信息都在 jwt 里边,所以这里配置的 JwtTokenStore 本质上并不是做存储。
  2. 另外我们还提供了一个 JwtAccessTokenConverter,这个 JwtAccessTokenConverter 可以实现将用户信息和 JWT 进行转换(将用户信息转为 jwt 字符串,或者从 jwt 字符串提取出用户信息)。
  3. 另外,在 JWT 字符串生成的时候,我们需要一个签名,这个签名需要自己保存好。

这里 JWT 默认生成的用户信息主要是用户角色、用户名等,如果我们希望在生成的 JWT 上面添加额外的信息,可以按照如下方式添加:

package com.xql.authorization_server.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class CustomAdditionalInformation implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> info = accessToken.getAdditionalInformation();
        info.put("author", "自律");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

自定义类 CustomAdditionalInformation 实现 TokenEnhancer 接口,并实现接口中的 enhance 方法。enhance 方法中的 OAuth2AccessToken 参数就是已经生成的 access_token 信息,我们可以从 OAuth2AccessToken 中取出已经生成的额外信息,然后在此基础上追加自己的信息。

「需要提醒一句,其实我们配置的 JwtAccessTokenConverter 也是 TokenEnhancer 的一个实例」

配置完成之后,我们还需要在 AuthorizationServer 中修改 AuthorizationServerTokenServices 实例,如下:

package com.xql.authorization_server.bean;

import com.xql.authorization_server.config.CustomAdditionalInformation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.*;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @author xuqinglei
 * @date 2023/04/21 08:37
 **/
@Configuration
public class AuthorizationBean {


    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired
    CustomAdditionalInformation customAdditionalInformation;


    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        //内存
        return new InMemoryAuthorizationCodeServices();
        //JdbcAuthorizationCodeServices
    }

    /**
     * 这个 Bean 主要用来配置 Token 的一些基本信息,
     * 例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。
     * Token 有效期这个好理解,刷新 Token 的有效期我说一下,当 Token 快要过期的时候,
     * 我们需要获取一个新的 Token,在获取新的 Token 时候,需要有一个凭证信息,
     * 这个凭证信息不是旧的 Token,而是另外一个 refresh_token,这个 refresh_token 也是有有效期的。
     */
    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        //客户端详情服务
        services.setClientDetailsService(clientDetailsService);
        //允许令牌自动刷新
        services.setSupportRefreshToken(true);
        //令牌存储策略-内存
        services.setTokenStore(tokenStore);
        // 令牌默认有效期2小时
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        // 刷新令牌默认有效期3天
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter, customAdditionalInformation));
        services.setTokenEnhancer(tokenEnhancerChain);
        return services;
    }

//    @Bean
//    @Primary
//    @ConfigurationProperties(prefix = "spring.datasource")
//    public DataSource dataSource() {
//        // 配置数据源(使用的是 HikariCP 连接池),以上注解是指定数据源,否则会有冲突
//        return DataSourceBuilder.create().build();
//    }
}

在这里插入图片描述
这里主要是是在 DefaultTokenServices 中配置 TokenEnhancer,将之前的 JwtAccessTokenConverter 和 CustomAdditionalInformation 两个实例注入进来即可。

资源服务器resource_server改造

接下来我们还需要对资源服务器进行改造,也就是resource_server,我们将上面的 AccessTokenConfig 类拷贝到 resource_server 中,然后在资源服务器配置中不再配置远程校验地址,而是配置一个 TokenStore 即可:


package com.xql.resource_server.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class AccessTokenConfig {

    private String SIGNING_KEY = "xql";


    @Bean
    TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
}
package com.xql.resource_server.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    TokenStore tokenStore;

//    /**
//     * tokenServices 我们配置了一个 RemoteTokenServices 的实例,
//     * 这是因为资源服务器和授权服务器是分开的,资源服务器和授权服务器是放在一起的,就不需要配置
//     * RemoteTokenServices 了。
//     */
//    @Bean
//    RemoteTokenServices tokenServices() {
//        RemoteTokenServices services = new RemoteTokenServices();
//        services.setCheckTokenEndpointUrl("http://localhost:53020/uaa-service/oauth/check_token");
//        services.setClientId("xql");
//        services.setClientSecret("xql123");
//        return services;
//    }

    /**
     * RemoteTokenServices 中我们配置了 access_token
     * 的校验地址、client_id、client_secret 这三个信息,当用户来资源服务器请求资源时,
     * 会携带上一个 access_token,通过这里的配置,就能够校验出 token 是否正确等
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("res1").tokenStore(tokenStore);
    }

    /**
     * 最后配置一下资源的拦截规则,这就是 Spring Security 中的基本写法,我就不再赘述。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .anyRequest().authenticated()
                .and().cors();
    }
}

在这里插入图片描述
这里配置好之后,会自动调用 JwtAccessTokenConverter 将 jwt 解析出来,jwt 里边就包含了用户的基本信息,所以就不用远程校验 access_token 了。

测试流程和前几篇一样 走一遍流程都可以了
发现控制台打印
在这里插入图片描述

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

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

相关文章

asp.net+sqlserver房屋中介管理信息系统

据信息处理工作量大时容易出错&#xff1b;数据繁多时&#xff0c;容易丢失&#xff0c;且不易查找&#xff0c;出错后又不易修改。 基于以二、系统主要划分为房屋和客户两种角色。主要功能划分为&#xff1a;客户管理&#xff0c;房屋管理和交易管理三大块。其中&#xff1a;…

JVM系列-第5章-堆(JVisualVM)

堆 堆的核心概述 堆与进程 堆针对一个JVM进程来说是唯一的。也就是一个进程只有一个JVM实例&#xff0c;一个JVM实例中就有一个运行时数据区&#xff0c;一个运行时数据区只有一个堆和一个方法区。 但是进程包含多个线程&#xff0c;他们是共享同一堆空间的。 一个JVM实例…

Introduction to modern Cryptography 现代密码学原理与协议第二章笔记

M表示明文空间&#xff0c;K表示密钥空间&#xff0c;C表示所有可能的密文集合 完善保密加密的概念: 简化约定&#xff0c;不再特殊声明&#xff0c;除数为0无意义 完全保密加密的等价公式: 证明: 必要性证明略&#xff0c;此证明为条件概率的简单应用 完全不可区分性: 完善保密…

PCL学习八:Keypoints-关键点

参考引用 Point Cloud Library黑马机器人 | PCL-3D点云 1. 引言 关键点也称为兴趣点&#xff0c;它是 2D 图像或 3D 点云或曲面模型上,可以通过检测标准来获取的具有稳定性、区别性的点集。从技术上来说&#xff0c;关键点的数量比原始点云或图像的数据量少很多&#xff0c;其与…

外译笔记 | 比尔盖茨:AI与智能手机和互联网一样具有革命性 | 京东云技术团队

3月21号&#xff0c;微软创始人比尔盖茨&#xff0c;在盖茨笔记中&#xff0c;发表最新AI文章《人工智能时代已经开始——人工智能与智能手机、互联网一样具有革命性》&#xff0c;文章中表示&#xff1a;自1980年首次看到图形用户界面&#xff08;graphical user interface&am…

使用awvs对网站进行扫描

前言&#xff1a;由于领导要求生成好看的漏洞扫描报告来发送给甲方&#xff0c;因此放弃了xray xray的简单使用 一、首先下载并安装相应的工具 访问此网址观看相应操作系统视频进行安装、有安装包更新记得及时更新 https://www.iculture.cc/software/pig35126 二、开始扫描 …

软考A计划-重点考点-专题一(计算机系统知识)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

【读书笔记】次第花开——安抚内心平静的宝藏书

种草许久的书&#xff0c;终于在合适的时间点读上了。还是从樊登老师那里了解到的&#xff0c;我本身是对佛法没有太深的理解和概念&#xff0c;但总感觉在这浮躁的社会需要一些理念支撑让人能沉静下来的。信仰对我还很陌生&#xff0c;但次第花开读起来真的让人舒服&#xff0…

3.Docker实用技术

Docker实用篇 0.学习目标 1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署…

看电商数据,用api接口数据

看渠道&#xff0c;看行业&#xff0c;看竞品很方便&#xff0c;不用拉一堆品牌比较&#xff0c;我看整体的时候很直接&#xff0c;谁最近数据好销量高一目了然&#xff0c;我也能做出反应和调整。 深圳电商运营中心 看历史数据的功能&#xff0c;极大满足了我不断总结复盘的习…

洗稿伪原创工具-洗稿生成器

chatgpt快速洗稿一篇文章 做自媒体创作的人都知道&#xff0c;文案创作是一件十分耗时间和精力的事情&#xff0c;而且要做好一篇文章&#xff0c;还需要考虑排版、语法和语气等问题。为了让自媒体写作更简单、更快速&#xff0c;我们推出了一款名为ChatGPT的快速洗稿软件&…

Java ---多线程

&#xff08;一&#xff09;目录 多线程介绍 线程的创建 线程的使用 线程的优先级 守护线程 线程同步 线程并发协作 &#xff08;二&#xff09;多线程介绍 1.多线程的基本概念 程序 “程序(Program)”是一个静态的概念&#xff0c;一般对应于操作系统中的一个可执行文…

openpnp - Smoothieware project debug

文章目录 openpnp - Smoothieware project debug概述笔记记录一下工程中, 和调试相关的资料备注END openpnp - Smoothieware project debug 概述 迁出了Smoothieware工程, 想单步调试学习一下工程. 工程可以编译下载, 但是没有对单步调试方法做说明. 研究了几天, 大概知道咋回…

Spring/SpringBoot常用注解总结

为什么要写这篇文章&#xff1f; 最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多&#xff0c;我看了文章内容之后属实觉得质量有点低&#xff0c;并且有点会误导没有太多实际使用经验的人&#xff08;这些人又占据了大多数&#xff09;。所以&#xff0c;自…

第三章 图像的低通与高通滤波

文章目录 前言一、卷积操作二、低通滤波1. 方盒滤波与均值滤波2. 中值滤波3.高斯滤波3.1 高斯分布3.2 滤波流程3.2 OpenCV代码及手动实现 4. 双边滤波4.1 原理4.2 OpenCV代码实现 二、高通滤波1. Sobel算子2. Schar算子3.拉普拉斯算子4. Canny边缘检测4.1 算法流程4.1.2 图像降…

垃圾回收小程序开发功能与优势有哪些?

垃圾回收、二手资源回收、废旧物品回收是最近几年社会提倡也越来越受到人们重视的&#xff0c;所以市面上也产生了一系列二手资源回收软件助力再生资源得到更好的利用&#xff0c;同时对环境保护也有很积极的作用。 垃圾资源回收小程序借助微信平台强大的用户群体&#…

CCF-202209-2-何以包邮?01背包

目录 1、题目描述&#xff1a; 2、思路1&#xff1a;动态规划 2.1、确定dp数组及下标含义 2.2、递推公式 2.3、初始化dp数组 2.4、确定遍历顺序 2.5、C实现如下 3、思路2&#xff1a;暴力法-空间换时间 1、题目描述&#xff1a; 新学期伊始&#xff0c;适逢顿顿书城有…

OpenGL(八)——图像逆透视算法IPM

目录 一、前言 二、相机模型 2.1 针孔模型 2.2 相机外参 三、逆透视模型 四、算法总结 一、前言 透视变换&#xff08;Perspective Transformation)是指利用透视中心、像点、目标点三点共线的条件&#xff0c;按透视旋转定律使承影面&#xff08;透视面&#xff09;绕迹线…

Cesium 实战-解决 The browser supports WebGL, but initialization failed 问题

Cesium 实战-解决 The browser supports WebGL, but initialization failed 问题 系统环境版本试错过程解决问题 在公司内网服务器部署 Cesium 项目的时候&#xff0c;发现提示浏览器不支持 WebGL 错误&#xff0c;经尝试&#xff0c;确认 Cesium 1.101.0 以及之前的版本是可以…

职场小白如何快速成为房地产策划专家?全覆盖解密

如果你是刚入行的地产策划新手小白&#xff1a; 1、首先要会房地产的基础知识&#xff0c;相关的政策法规等&#xff0c;因为这些都是制定策略的最基本的依据&#xff0c;如果这些你都不熟&#xff0c;制定出来的策划就会有错误。建议你多买几本地产基础知识的书看看。 2、把…