当前位置: 首页 > article >正文

SpringSecurity6.0 通过JWTtoken进行认证授权

之前写过一个文章,从SpringSecurity 5.x升级到6.0,当时是为了配合公司的大版本升级做的,里面的各项配置都是前人留下来的,其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建,用户的认证授权流程也比较简单,通过用户/密码进行登录,登录后生成JWT token返回给前端,后续认证通过token进行,就把SpringSecurity重新捡了起来,搭建整个系统的安全认证框架。

项目依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.4.4</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- jwt token相关依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependencies>

项目后端整体还是通过Springboot来搭建,Springboot3.0中把JWT相关的依赖都整合到了spring-boot-starter-oauth2-resource-server中,无需再单独指定

认证

首先我们先完成通过账号密码进行登录相关代码

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Value("${jwt.public.key}")RSAPublicKey key;@Value("${jwt.private.key}")RSAPrivateKey priv;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// @formatter:offhttp.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).csrf((csrf) -> csrf.ignoringRequestMatchers("/token")).httpBasic(Customizer.withDefaults()).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()).accessDeniedHandler(new BearerTokenAccessDeniedHandler()));// @formatter:onreturn http.build();}@BeanUserDetailsService users() {// @formatter:offreturn new InMemoryUserDetailsManager(User.withUsername("user").password("{noop}password").authorities("app").build());// @formatter:on}@BeanJwtDecoder jwtDecoder() {return NimbusJwtDecoder.withPublicKey(this.key).build();}@BeanJwtEncoder jwtEncoder() {JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));return new NimbusJwtEncoder(jwks);}}

这里关注几个重点:

  1. HttpSecurity#httpBasic,这个方法表明通过基于HTTP Basic认证协议
  2. anyRequest().authenticated()表明所有请求都需要经过认证
  3. UserDetailsService,这里创建了一个仅存在于内存中的用户,用户名和密码是user/password,密码中添加的前缀{noop}和userDetailService的作用我们稍后再说
  4. oauth2ResourceServer设置jwt token相关的配置,Spring推荐情况是配置一个第三方的校验服务,我们这里为了简化将相关的生成和校验都在本地进行。

UserDetailService

public interface UserDetailsService {/*** Locates the user based on the username. In the actual implementation, the search* may possibly be case sensitive, or case insensitive depending on how the* implementation instance is configured. In this case, the <code>UserDetails</code>* object that comes back may have a username that is of a different case than what* was actually requested..* @param username the username identifying the user whose data is required.* @return a fully populated user record (never <code>null</code>)* @throws UsernameNotFoundException if the user could not be found or the user has no* GrantedAuthority*/UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}

这个接口里只定义了一个方法,loadUserByUsername在通过用户名/密码进行认证时,需要通过来判断用户是否存在,在生产中,我们可以根据自己的需要通过数据库等获取用户信息。
拿到用户信息之后,要怎么校验密码呢?SpringSecurity提供了另外一个接口PasswordEncoder进行密码的编码和校验,
在这里插入图片描述

这里提供了非常多的实现方式,默认情况下Spring会加载DelegatingPasswordEncoder,同时将其他的实现都包含进去,那在进行密码校验的时候要匹配哪一个Encoder呢,这里{noop}password中的前缀就发挥作用了,{noop}表明使用NoOpPasswordEncoder进行处理,即不仅限任何编码处理,直接通过明文进行对比,这里当然不符合安全要求,在实际工作中我们根据需要直接指定一个Encoder即可

  @BeanPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}

生成JWT token

@RestController
public class TokenController {@AutowiredJwtEncoder encoder;@PostMapping("/token")public String token(Authentication authentication) {Instant now = Instant.now();long expiry = 36000L;// @formatter:offString scope = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "));JwtClaimsSet claims = JwtClaimsSet.builder().issuer("self").issuedAt(now).expiresAt(now.plusSeconds(expiry)).subject(authentication.getName()).claim("scope", scope).build();// @formatter:onreturn this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();}}

这里有一个小提示,我们在创建UserDetail的时候可以设置#authorities()#roles(),但是最终都会设置到authorities中,这两个在当今的SpringSecurity中实际上是一个东西,所以我们在Authentication中也只有getAuthorities()这一个方法
进行测试

curl -XPOST user:password@localhost:8080/token

然后能够得到类似的返回

eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzZWxmIiwic3ViIjoidXNlciIsImV4cCI6MTYwNDA0MzA1MSwiaWF0IjoxNjA0MDA3MDUxfQ.yDF_JgSwl5sk21CF7AE1AYbYzRd5YYqe3MIgSWpgN0t2UqsjaaEDhmmICKizt-_0iZy8nkEpNnvgqv5bOHDhs7AXlYS1pg8dgPKuyfkhyVIKa3DhuGyb7tFjwJxHpr128BXf1Dbq-p7Njy46tbKsZhP5zGTjdXlqlAhR4Bl5Fxaxr7D0gdTVBVTlUp9DCy6l-pTBpsvHxShkjXJ0GHVpIZdB-c2e_K9PfTW5MDPcHekG9djnWPSEy-fRvKzTsyVFhdy-X3NXQWWkjFv9bNarV-bhxMlzqhujuaeXJGEqUZlkhBxTsqFr1N7XVcmhs3ECdjEyun2fUSge4BoC7budsQ

然后我们把token配置到环境变量中

export TOKEN=`curl -XPOST user:password@localhost:8080/token`

请求另外一个接口

curl -H "Authorization: Bearer $TOKEN" localhost:8080 && echo
Hello, user!

权限控制

在完成认证后,后续我们可以继续进行授权相关的校验工作,SpringSecurity提供两种授权校验的方式

  • 基于http请求的方式,包括路径匹配、请求方法匹配等,
  • 基于方法的控制,通过@PreAuthorize等注解,在方法上进行更细粒度的控制,我采用了这一种方式
  @PreAuthorize("hasAuthority('SCOPE_ADMIN')")//JWT token解析后会加一个前缀'scope'@GetMapping("/admin")public String admin(Authentication authentication){return authentication.getAuthorities().toString();}

默认情况下,Authority中的内容会比你生成token时多加一个前缀SCOPE_,当然你也可以通过配置进行更改。

小结

这里简单介绍了一下认证和授权的配置,实际上SpringSecurity要远比这些要复杂的多,有更深入的需求可以参考官方文档或者源码

这里推荐一下自己的项目地址,已经把用户配置到h2数据库中
https://gitee.com/xiiiao/hello-spring-security

相关文章:

SpringSecurity6.0 通过JWTtoken进行认证授权

之前写过一个文章&#xff0c;从SpringSecurity 5.x升级到6.0&#xff0c;当时是为了配合公司的大版本升级做的&#xff0c;里面的各项配置都是前人留下来的&#xff0c;其实没有花时间进行研究SpringSecurity的工作机制。现在新东家有一个简单的系统要搭建&#xff0c;用户的认…...

【Java】Maven

一、概念 是一个项目管理和构建工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff09;的概念&#xff0c;通过一小段描述信息来管理项目的构建。 二、Maven坐标 <groupId>com.itheima</groupId><artifactId>maven-project01</artifactId>&…...

第十五届蓝桥杯PythonC组题解

A题&#xff1a;拼正方形 问题描述 给定一定数量的 22 和 11 的方块&#xff0c;求能拼出的最大正方形边长。 解题思路 二分法&#xff1a;将奇数和偶数边长分开处理&#xff0c;通过二分法寻找最大满足条件的边长。面积验证&#xff1a;总方块面积需大于等于目标正方形面积…...

MATLAB中plot函数的详细参数表

LineSpec - 线型、标记和颜色 线型说明-实线--虚线:点线-.点划线 标记说明o圆圈加号*星号.点x叉号_水平线条|垂直线条s方形d菱形^上三角v下三角>右三角<左三角p五角形h六角形 颜色说明 y 黄色 m 品红色 c 青蓝色 r 红色 g 绿色 b 蓝色 w 白色 k 黑色 MarkerFaceColor…...

R语言赋能气象水文科研:从多维数据处理到学术级可视化

全球气候变化加剧了极端天气与水文事件的复杂性&#xff0c;气象卫星、雷达、地面观测站及水文传感器每天产生TB级‌时空异质数据‌。传统研究常面临四大瓶颈&#xff1a; ‌数据清洗低效‌&#xff1a;缺失值、异常值处理耗时&#xff1b;‌时空分析模型构建复杂‌&#xff1…...

虚拟试衣间-云尚衣橱小程序-衣橱管理实现

衣橱管理实现 目标 (Goal): 用户 (User): 能通过 UniApp 小程序上传衣服图片。 后端 (Backend): 接收图片,存到云存储,并将图片信息(URL、用户ID等)存入数据库。 用户 (User): 能在小程序里看到自己上传的所有衣服图片列表。 技术栈细化 (Refined Tech Stack for this Pha…...

BGP路由协议之属性2

Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的&#xff0c;那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的&#xff0c;那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…...

纯个人整理,蓝桥杯使用的算法模板day2(0-1背包问题),手打个人理解注释,超全面,且均已验证成功(附带详细手写“模拟流程图”,全网首个

算法索引 01背包优化前空间优化版&#xff08;使用一维数组&#xff09;优化后的模拟流程图为何优化后&#xff0c;j不能使用正序遍历模拟流程图 代码对应实现案例 01背包 优化前 /*** 0-1背包问题解法&#xff08;与下方代码表格示例对应&#xff0c;已模拟验证&#xff09;*…...

算法与数据结构线性表之栈和队列

Hello大家好&#xff01; 很高兴与大家见面&#xff01; 给生活添点快乐&#xff0c;开始今天的编程之路。 我的博客:<但愿. 我的专栏:C语言、题目精讲、算法与数据结构、C 欢迎点赞&#xff0c;关注 一 栈 1概念&#xff1a;栈是⼀种特殊的线性表&#xff0c;其只允许…...

python应用之使用pdfplumber 解析pdf文件内容

目录标题 一. 通过 pdfplumber.open() 解析复杂PDF&#xff1a;1-2. 报错&#xff1a;V2 &#xff1a; 1-3. v3 使用tk 库&#xff0c;弹框选择文件运行环境准备完整代码保存运行测试步骤方式二&#xff1a;命令行方式&#xff08;适用于自动化&#xff09; 测试用例示例常见问…...

laravel update报In PackageManifest.php line 122:Undefined index: name 错误的解决办法

用 composer 更新 laravel依赖包时报错 > Illuminate\Foundation\ComposerScripts::postAutoloadDump > Illuminate\Foundation\ComposerScripts::postAutoloadDump > php artisan package:discover --ansiIn PackageManifest.php line 122:Undefined index: nameScr…...

Vue中使用antd-table组件实现数据选择、禁用、已选择禁用-demo

实现案例 实现过程 表格代码 关键代码 :row-selection="rowSelection" <div><div class="flex items-center justify-between pt-[24px] pb-[16px]"><p>已选:{{ keysNum }}</p><a-input-search v-model:value="productN…...

C语言--统计输入字符串中的单词个数

输入 输入&#xff1a;大小写字母以及空格&#xff0c;单词以空格分隔 输出&#xff1a;单词个数 代码 如果不是空格且inWord0说明是进入单词的第一个字母&#xff0c;则单词总数加一。 如果是空格&#xff0c;证明离开单词&#xff0c;inWord 0。 #include <stdio.h&g…...

Kubernetes 集群搭建(三):使用dashboard用户界面(需要访问外网获取yaml)

&#xff08;一&#xff09;简介 K8s Dashboard是Kubernetes提供的一种基于Web的用户界面工具&#xff0c;用于可视化地管理和监控Kubernetes集群 主要功能&#xff1a; 资源查看与管理&#xff1a; 查看Kubernetes集群中的各种资源&#xff0c;如节点、Pod、服务、部署等。 对…...

Debian 12 服务器搭建Beego环境

一、Debian 12系统准备 1.更新系统 #apt update && apt upgrade -y 2.安装基础工具 #apt install -y git curl wget make gcc 二、安装Go环境 Go语言的镜像官网&#xff1a;https://golang.google.cn/ 1.下载go最新版 #cd /usr/local/src #wget -o https://golang.go…...

游戏引擎学习第208天

运行游戏并回顾我们的情况 今天&#xff0c;我们将继续完成之前中断的调试输出工作。最近的工作偏离了一些&#xff0c;展示了如何进行元编程的实践&#xff0c;主要涉及了一个小的解析器。尽管这个解析器本身是一个玩具&#xff0c;但它展示了如何完成一个完整的循环&#xf…...

【在校课堂笔记】Python 第 7 节课 总结

- 第 85 篇 - Date: 2025 - 04 - 06 Author: 郑龙浩/仟墨 【Python 在校课堂笔记】 南山-第 7 节课 上课时间: 2025-03-27 文章目录 南山-第 7 节课一 99乘法表 –> 三角二 函数1 已接触的函数&#xff0c;部分举例2 自定函数的定义与使用自定义函数:举例 3 带参数的4 阶乘…...

评价区动态加载是怎么实现的?

淘宝商品评价区的动态加载是通过一系列前端技术和后端接口实现的&#xff0c;其核心目的是提升用户体验和页面性能。以下是其实现原理和关键技术的详细解析&#xff1a; 1. 前端实现&#xff1a;AJAX 和 JavaScript 淘宝利用 AJAX&#xff08;Asynchronous JavaScript and XM…...

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的监控:使用 Actuator 实现健康检查

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、引子&…...

蓝桥杯—数字接龙(dfs+减枝)

一.题目 二.思路 一看就是迷宫问题的变种&#xff0c;从左上角到达右下角&#xff0c;要解决 1.8个方向的方向向量&#xff0c;用dx&#xff0c;dy数组代表方向向量 2.要按照一个规律的数值串进行搜索0&#xff0c;1&#xff0c;2&#xff0c;k-1&#xff0c;0&#xff0c;1…...

Docker与VNC的使用

https://hub.docker.com/r/dorowu/ubuntu-desktop-lxde-vnc 下载nvc 客户端 https://downloads.realvnc.com/download/file/viewer.files/VNC-Viewer-7.12.0-Windows.exe 服务端 docker pull dorowu/ubuntu-desktop-lxde-vnc#下载成功 docker pull dorowu/ubuntu-desktop-l…...

C++——清明

#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> #include <ctime>using namespace std;class Weapon; // 前置声明class Hero{ pr…...

Unity ViewportConstraint

一、组件功能概述 ViewportConstraint是一个基于世界坐标的UI边界约束组件&#xff0c;主要功能包括&#xff1a; 将UI元素限制在父容器范围内支持自定义内边距&#xff08;padding&#xff09;可独立控制水平和垂直方向的约束 二、实现原理 1. 边界计算&#xff08;世界坐…...

Gin、Echo 和 Beego三个 Go 语言 Web 框架的核心区别及各自的优缺点分析,结合其设计目标、功能特性与适用场景

1. Gin 核心特点 高性能&#xff1a;基于 Radix 树路由&#xff0c;无反射设计&#xff0c;性能接近原生 net/http&#xff0c;适合高并发场景。轻量级&#xff1a;仅提供路由、中间件、请求响应处理等基础功能&#xff0c;依赖少。易用性&#xff1a;API 设计简洁直观&#…...

ffmpeg视频转码相关

ffmpeg视频转码相关 简介参数 实战举栗子获取视频时长视频转码mp4文件转为hls m3u8 ts等文件图片转视频抽取视频第一帧获取基本信息 转码日志输出详解转码耗时测试 简介 FFmpeg 是领先的多媒体框架&#xff0c;能够解码、编码、 转码、复用、解复用、流、过滤和播放 几乎所有人…...

手搓多模态-06 数据预处理

前情回顾 我们目前实现了视觉模型的编码器部分&#xff0c;然而&#xff0c;我们所做的是把一张图片编码嵌入成了许多个上下文相关的嵌入向量&#xff0c;然而我们期望的是一张图片用一个向量来表示&#xff0c;从而与文字的向量做点积形成相似度&#xff08;参考手搓多模态-01…...

HCIP【路由过滤技术(详解)】

目录 1 简介 2 路由过滤方法 3 路由过滤工具 3.1 静默接口 3.2 ACL 3.3 地址前缀列表 3.4 filter-policy 3.4.1 filter-policy过滤接收路由&#xff08;以RIP为例&#xff09; 3.4.2 filter-policy过滤接收路由&#xff08;以OSPF为例&#xff09; 1 简介 路由过滤技术…...

【Kafka基础】topics命令行操作大全:高级命令解析(2)

1 强制删除主题 /export/home/kafka_zk/kafka_2.13-2.7.1/bin/kafka-topics.sh --delete \--zookeeper 192.168.10.33:2181 \--topic mytopic \--if-exists 参数说明&#xff1a; --zookeeper&#xff1a;直接连接Zookeeper删除&#xff08;旧版本方式&#xff09;--if-exists&…...

【AI插件开发】Notepad++ AI插件开发实践(代码篇):从Dock窗口集成到功能菜单实现

一、引言 上篇文章已经在Notepad的插件开发中集成了选中即问AI的功能&#xff0c;这一篇文章将在此基础上进一步集成&#xff0c;支持AI对话窗口以及常见的代码功能菜单&#xff1a; 显示AI的Dock窗口&#xff0c;可以用自然语言向 AI 提问或要求执行任务选中代码后使用&…...

Vue3在ZKmall开源商城前端的应用实践与技术创新

ZKmall开源商城作为一款企业级电商解决方案&#xff0c;其前端架构基于Vue3实现了高效、灵活的开发模式&#xff0c;结合响应式设计、组件化开发与全链路性能优化&#xff0c;为多端协同和复杂业务场景提供了先进的技术支持。以下从技术架构、核心特性、性能优化等维度解析Vue3…...