SpringSecurity(二十)---OAuth2:实现资源服务器(上)资源服务器搭建以及直接调用授权服务器模式

news2025/4/7 0:07:22

一、 前言

本章将讨论如何使用Spring Security实现一个资源服务器,资源服务器是管理用户资源的组件。另外,学习本章有个前提,需要先把前面搭建授权服务器的相关文章先给阅读,否则可能后面出现的授权服务器相关代码不知道个所以然。就OAuth2而言,它代表了我们要保护的后端(端点),就像前几章保护的其他应用程序是一样的。为了允许客户端访问资源,资源服务器需要一个有效的访问令牌。客户端会从授权服务器获得访问令牌,并通过将该令牌添加到HTTP请求头信息来使用该令牌调用资源服务器上的资源。
还记得前两章讨论客户端和授权服务器的实现时,我们曾经提起过资源服务器更为重要的是选择资源服务器验证令牌的方式。对于在资源服务器级别实现令牌验证,我们主要有三种方式:

  • 远程检查令牌,即通过网络调用授权服务器检查token
  • 黑板模式,我们使用一个公共数据库,这个可以是Mysql也可以是redis,这个我们都会讲。redis我会专门提起一章来说。授权服务器会在其中存储令牌,然后资源服务器可以在其中访问和验证令牌。这种方法也称为黑板模式。
  • 最后第三个选项是使用加密签名授权服务器在颁发令牌时会对其进行签名,资源服务器则要验证签名。这里是我们通常使用的JWT的地方。这个也会专门提一章后面讲。

二、实现资源服务器

首先要实现我们的第一个资源服务器应用程序,这是OAuth2拼图的最后一块。
使用可以颁发令牌的授权服务器的原因是为了允许客户端访问用户的资源。资源服务器将管理和保护用户的资源。由于这个原因,我们需要知道如何实现资源服务器。
在这里插入图片描述
当资源服务器需要验证令牌时,它会直接调用授权服务器。如果授权服务器确认它颁发了该令牌,则资源服务器认为该令牌有效

要实现资源服务器,需要在和之前搭建授权服务器的同一个父项目下创建一个新项目spring_security_resource_server并添加依赖项,完整pom文件如下,关于mysql,redis,fastjson等后面会用到,大家可以提前加上。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring_security_oauth2_demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring_security_resource_server</artifactId>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
</project>

资源服务器的目的是管理和保护用户的资源。因此为了证明它是如何工作的,这里需要一个我们希望访问的资源。我们写一个控制器类代表我们需要保护的资源:
TestController.java

package com.mbw.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

	@GetMapping("/xiao")
	public String xiao(){return "纳西妲我抽爆!";}
	@GetMapping("/giao")
	public String giao(){
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		String name = authentication.getName();
		return "giao,"+name;
	}
	@GetMapping("/a")
	public String getEndpointA(){
		return "a";
	}
	@GetMapping("/a/b")
	public String getEndpointAB(){
		return "ab";
	}

	@GetMapping("/product/{code}")
	public String productCode(@PathVariable String code){
		return code;
	}
}

这里还需要一个配置类,在这个类中将使用@EnableResourceServer注解来允许Spring Boot为应用程序配置成为资源服务器所需的内容。而你也可以通过扩展ResourceServerConfigurerAdapter去重写资源服务器相关组件和方法,代码如下:
ResourceServerConfig.java

package com.mbw.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

import javax.servlet.http.HttpServletResponse;


@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
	@Override
	public void configure(HttpSecurity http) throws Exception {
		http
				.csrf().disable()
				.exceptionHandling()
				.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
				.and()
				.authorizeRequests()
				.antMatchers("/test/**").authenticated()
				.and()
				.httpBasic();
	}
}

上面的代码如果大家对spring Security熟悉的小伙伴应该很快能反应过来,几乎一模一样,其中/test/**就是我们刚才写的Controller的所有端点。
那么我们现在就有了一个资源服务器。但是,如果不能访问端点,它就没有任何用处,就像目前这个示例一样,因为还没有配置资源服务器检查令牌的任何方式。我们知道,对资源的请求也需要提供有效的访问令牌。但即使它提供了有效的访问令牌,请求仍然不能工作,这里的资源服务器还无法验证这些是否是有效的令牌,也无法验证授权服务器确实颁发了它们。这是因为还没有实现资源服务器验证访问令牌所需的任何选项,接下来我们将讨论这些方式。

三、远程检查令牌

本节将通过允许资源服务器直接调用授权服务器来实现令牌验证。此方法是使用有效访问令牌启用对资源服务器的访问的最简单实现,如果系统中的令牌是简单形式(例如,在Spring Security的授权服务器的默认实现中使用的简单UUID),则可以选择此方法。这种验证令牌的机制很简单:

  1. 授权服务器暴露一个端点。对于有效的令牌,它会返回先前向其颁发该令牌的用户所被授予的权限。此处把这个端点称为check_token端点。
  2. 资源服务器为每个请求调用check_token端点。这样,它就会验证从客户端接收的令牌,并获得授予客户端的权限。
    在这里插入图片描述
    这种方法的优点是简单。可以将其应用于任何类型的令牌实现。这种方法的缺点是,对于资源服务器上具有新的未知令牌的每个请求,资源服务器将调用授权服务器来验证该令牌。这些调用会给授权服务器带来不必要的负荷。此外,请记住:网络并不是100%可靠的。每次在架构中设计新的远程调用时,都需要记住这一点。如果由于网络不稳定导致调用失败,则可能还需要应用一些替代解决方案。并且如果重启资源服务器,就算之前产生了令牌并且还没过期,资源服务器也验证不了这个令牌。因为这个令牌已经失效了,所以也就有了后面将令牌持久化的方案。
    那么接下来讨论如何实现。此处的预期是:如果/hello端点提供了授权服务器颁发的访问令牌,则允许客户端访问该端点。
    默认情况下,授权服务器会实现端点/oauth/check_token,资源服务器可以使用该端点验证令牌。但是,目前授权服务器将隐式拒绝对该端点的所有请求。在使用/oauth/check_token端点之前,需要确保资源服务器可以调用它。
    为了允许经过身份验证的请求调用/oauth/check_token端点,需要重写授权服务器的AuthServerConfig类中的configure(AuthorizationServerSecurityConfigurer c)方法重写configure()方法就可以设置允许调用/oauth/check_token端点的条件。代码如下:
import com.mbw.security.service.ClientDetailsServiceImpl;
import com.mbw.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private AuthenticationManager authenticationManager;
	@Autowired
	private ClientDetailsServiceImpl clientDetailsServiceImpl;
	@Autowired
	private UserDetailsServiceImpl userDetailsServiceImpl;

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.authenticationManager(authenticationManager)
		.userDetailsService(userDetailsServiceImpl)
				.tokenStore(jsonRedisTokenStore);
		DefaultTokenServices tokenService = getTokenStore(endpoints);
		endpoints.tokenServices(tokenService);
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetailsServiceImpl);
	}

	/**
	 * 解决访问/oauth/check_token 403的问题
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		// 允许表单认证
		security
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("permitAll()")  //指定可以调用check_token端点的条件
				.allowFormAuthenticationForClients();

	}
}

你也可以使用isAuthenticated(),只是我这儿用permitAll在没有身份验证的情况下也可以访问,便于我测试,但是不建议像我这样做让端点不受保护,在真实场景下,最好对这个端点使用身份验证
除了使这个端点可访问之外,如果还决定只允许经过身份验证的访问,就需要为资源服务器本身注册一个客户端。对于授权服务器而言,资源服务器也是客户端,并且也需要它自己的凭据。需要像添加到其他客户端那样添加它,对于资源服务器,则不需要任何授权类型或作用域,只需要资源服务器用于调用check_token端点的一组凭据即可
所以我修改了下上次注册客户端的接口,让他们也可以自行输入clientId和clientSecret

public OAuth2Client createOAuth2Client(OAuth2Client oAuth2Client){
		String clientId = oAuth2Client.getClientId();
		if(CharSequenceUtil.isBlank(clientId)){
			clientId = getClientIdUnique(oAuth2Client);
		}
		String clientSecret = oAuth2Client.getClientSecret();
		if(CharSequenceUtil.isBlank(clientSecret)) {
			clientSecret = RandomUtil.randomString(16);
		}
		String clientSecretEncoded = passwordEncoder.encode(clientSecret);
		oAuth2Client.setClientId(clientId);
		oAuth2Client.setClientSecret(clientSecretEncoded);
		oAuth2ClientMapper.insert(oAuth2Client);
		oAuth2Client.setClientSecret(clientSecret);
		return oAuth2Client;
	}

然后到postman调用注册客户端的接口,cleintId就取名为resourceServer,clientSecret就为resourceServerSecret
在这里插入图片描述
并且入库,且对clientecret加密。
现在启动授权服务器并获得一个令牌,就想之前授权服务器那样,直接通过postman调用,随便你使用某一种方式都行,这里我为了方便就选择密码授权模式
在这里插入图片描述
接下来要调用check_token端点查找前面的代码片段中所获得的访问令牌的详细信息。这一调用是这样的:

在这里插入图片描述
观察从check_token端点返回的响应。其中包含关于访问令牌所需的所有详细信息:

  • 该令牌是否仍然有效并且何时过期
  • 该令牌是为哪个用户颁发的
  • 表示权利的权限
  • 该令牌是为哪个客户颁发的

现在,如果使用postman调用端点,则资源服务器应该能够用它验证令牌了。还需要配置授权服务器的端点和资源服务器用于访问端点的凭据。可以在application.yaml文件中完成所有这些配置。以下就是配置文件的代码:

server:
  port: 9091
security:
  oauth2:
    resource:
      token-info-uri: http://localhost:9090/oauth/check_token
    client:
      client-id: resourceServer
      client-secret: resourceServerSecret

ps:在对/oauth/check_token(令牌自省)端点使用身份验证时,资源服务器将充当授权服务器的客户端。由于这个原因,它就需要注册一些凭据,在调用自省端点时,它将是=使用这些凭据利用HTTP Basic身份验证进行身份验证
现在可以通过调用/hello端点运行该应用程序和测试整个设置。需要在请求的Authorization头信息中设置访问令牌,并且需要在其值前面加上带有单次bearer的前缀。对于这个单词来说,其大小写是不区分的。这就意味着也可以写作“bearer”或者“BEARER”
在这里插入图片描述
如果在没有令牌或使用错误令牌的情况下调用了端点,那么其结果将是HTTP响应上出现401 Unauthorized状态。例如我现在不传Authorization这个请求头,下面的代码片段给出了该响应:
在这里插入图片描述
又或者我传了一个错误的令牌:
在这里插入图片描述

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

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

相关文章

【Redis】缓存击穿的产生情况解决方案

1. 缓存击穿产生 也叫做 热点 Key 问题&#xff0c;高并发访问并且缓存重建业务较复杂的 key 突然失效了&#xff0c;无数的请求想要重建缓存&#xff0c;大量的访问会在瞬间给数据库带来巨大冲击。 2. 解决方案 2.1 方案一&#xff1a;互斥锁 查询缓存不存在时&#xff0c;…

【OpenFOAM】-olaFlow-算例1- baseWaveFlume

算例路径&#xff1a; olaFlow\tutorials\baseWaveFlume 算例描述&#xff1a; 一个基础的二维波浪水槽 算例快照&#xff1a; 图1 波浪模拟结果图2 算例网格文件结构&#xff1a; ├── 0.org │ ├── U │ ├── alpha.water │ ├── alpha.water.org │ └─…

关于Redis的远程连接 Connection: Disconnect on error 问题

bug描述&#xff1a; Connection: Disconnect on error: Connection error: Connection timed outConnection: 192.168.245.128 > connection failed 问题复现&#xff1a; redis版本&#xff1a; redis-6.2.6 Linux版本&#xff1a;CenterOS 7 在linux上已经完成了red…

第五章. 可视化数据分析分析图表—常用图表的绘制1—折线图,柱形图

第五章. 可视化数据分析分析图 5.3 常用图表的绘制1—折线图,柱形图 本节主要介绍常用图表的绘制&#xff0c;主要包括折线图&#xff0c;柱形图。 1.折线图&#xff08;matplotlib.pyplot.plot&#xff09; 折线图可以显示随时间而变化的连续数据&#xff0c;适用于显示在相…

Hadoop原理与技术——hdfs命令行基本操作

一、实验目的 熟悉hdfs命令行基本操作 二、实验环境 Windows 10 VMware Workstation Pro虚拟机 Hadoop环境 Jdk1.8 三、实验内容 1&#xff1a;hdfs常见命令&#xff1a; &#xff08;1&#xff09;查看帮助&#xff1a;hdfs dfs -help &#xff08;2&#xff09;查看当前目录…

OpenCV图像处理——目标追踪

总目录 图像处理总目录←点击这里 二十四、目标追踪 24.1、多目标&#xff08;手动检测&#xff09;追踪 24.1.1、原理 目标检测&#xff1a;运行之后按下s&#xff0c;通过鼠标对某个目标进行检测&#xff0c;然后点击空格或者回车 目标追踪&#xff1a;opencv的八种追踪…

【JavaEE】JavaScript(基础语法)1

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录前言一、JavaScript初识&#xff08;简单了解就行&#xff0c;没时间跳过&#xff09;二、【JS的代码相关】【注释】【输入输出】三、语法1. 变量2. 【基本数据类型】THINK前言 一个人最大的痛苦就是对自己无能的愤…

信贷风控NCL净损失率的指标实现与应用

在金融信贷业务的风险控制过程中&#xff0c;有一项财务指标发挥着比较重要的信息参考价值&#xff0c;可以有效衡量某个月份放款金额在形成呆账后的资金损失情况&#xff0c;其中呆账指的是信贷逾期180天以上&#xff0c;这个指标便是NCL&#xff08;Net Credit Loss&#xff…

大家都在用的福昕阅读器 foxit 你还不知道吗? 祛除水印PDF转换全功能解锁…

趣味拓展 什么字大家看了都说没用&#xff1f; (答案在文末) 引言 福昕阅读器是一款高级PDF编辑器&#xff0c;查看目录、去除水印、编辑文本、转换格式等都是可以用到的~ 小编第一次使用的时候是用来查看pdf文档目录的&#xff0c;当时未使用破解版&#xff0c;后来循序渐进…

基于PHP+MySQL中小学生科学实验展示网站的设计与实现

中小学生科学实验展示网站能够通过互联网得到广泛的、全面的宣传,让尽可能多的人积极的参加到科学实验行列中来,不仅为需要的人提供了服务,而且锻炼了自己,同时能够让中小学生对科学实验的兴趣有很大的提高 PHP中小学生科学实验展示网站是一个科普类型的网站,系统通过PHp&#…

[附源码]Python计算机毕业设计SSM客户信息管理(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

模拟电子技术(四)放大电路的频率响应

&#xff08;四&#xff09;放大电路的频率响应电路理论基础知识复习频率响应基本概念两个无源频率响应的单元电路波特图&#xff08;幅频、相频&#xff09;高、低同电路对比晶体管的高频等效模型单管放大电路的频率响应研究信号频率的变化对放大电路性能的影响也就是电路对输…

Linux零基础入门(二)Linux基础命令

Linux零基础入门&#xff08;二&#xff09;Linux基础命令前言Linux基础命令一 Linux的目录结构1 Linux路径的描述方式二 Linux命令入门1 Linux命令基础格式2 ls命令3 HOME目录和工作目录4 ls命令的参数ls 命令的 -a选项ls命令的 -l选项ls命令选项的组合使用ls选项和参数的组合…

基于人工势场法的二维平面内无人机的路径规划的matlab仿真,并通过对势场法改进避免了无人机陷入极值的问题

目录 1.算法描述 2.matlab算法仿真效果 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 人工势场法原理是&#xff1a;首先构建一个人工虚拟势场&#xff0c;该势场由两部分组成&#xff0c;一部分是目标点对移动机器人产生的引力场&#xff0c;方向由机器人指向目标点&#xf…

C语言——malloc开辟矩阵

目录 用动态内存开辟矩阵 矩阵初始化 用动态内存开辟矩阵 动态内存更多的知识在这篇博客&#xff0c;本文将介绍用malloc开辟矩阵。 malloc是C语言中用来动态开辟内存的&#xff0c;通过malloc函数可以向计算机申请一串连续的内存空间。 因为malloc开辟的内存在堆上&#…

数据链路层-封装成帧

封装成帧 数据链路层给上层交付的协议数据单元添加上帧头和帧尾使之成为帧帧头和帧尾包含有重要的控制信息帧头和帧尾的作用之一就是帧定界 透明传输 数据链路层对上层的交付的传输数据没有任何的限制&#xff0c;就好像数据链路层不存在一样面向字节的物理链路使用字节填充(字…

manjaro (gnome) 记录 2 常用配置与快捷键介绍

manjaro &#xff08;gnome&#xff09; 记录 2 常用配置与快捷键介绍 初manjaro 记录 2 常用配置与快捷键介绍安装 vim 文本编辑器配置中文输入法安装谷歌拼音配置输入法添加快捷键&#xff1a;gnome 终端通过命令&#xff1a;gnome-terminal 可以打开 gnome 终端窗口设置快捷…

学习Hadoop(一)——搭建hadoop集群

最近开始学习大数据的相关知识&#xff0c;要学习大数据就不得不提到Hadoop。 一般来说学习一门新的知识&#xff0c;很多都是先理论再实践 我则不然&#xff0c;先实践&#xff0c;再了解理论 目录一、VM网络设置二、安装Centos72.1 网络配置2.2 设置主机名2.3 hosts设置2.4…

【在SpringBoot项目中使用Validation框架检查数据格式】

目录 1. 添加依赖 2. 检查POJO类型的请求参数 3. 关于响应的消息文本 4. 快速失败 5. 检查未封装的请求参数 1. 添加依赖 在pom.xml中添加spring-boot-starter-validation依赖项&#xff1a; <!-- Spring Boot Validation框架&#xff0c;用于检查数据格式 --> &…

ctfshow 月饼杯

寒假打算认真学习一下&#xff0c;就先从半个月的刷题开始。 文章目录web1_此夜圆web2_故人心web3_莫负婵娟web1_此夜圆 题目给的有附件&#xff0c;一看就是php反序列化字符串逃逸(应该是签到题)。 源码&#xff1a; <?php error_reporting(0);class a {public $uname;…