Spring Security 与 JWT

spring security 与 jwt

在本文中,我们将探讨如何将 spring security 与 jwt 集成,为您的应用程序构建坚实的安全层。我们将完成从基本配置到实现自定义身份验证过滤器的每个步骤,确保您拥有必要的工具来高效、大规模地保护您的 api。

配置

在 spring initializr 中,我们将使用 java 21mavenjar 和这些依赖项构建一个项目:

spring 数据 jpa春天网龙目岛春季安全postgresql 驱动程序oauth2 资源服务器

设置 postgresql 数据库

使用 docker,您将使用 docker-compose 创建一个 postgresql 数据库。
在项目的根目录创建一个 docker-compose.yaml 文件。

services:  postgre:    image: postgres:latest    ports:      - "5432:5432"    environment:      - postgres_db=database      - postgres_user=admin      - postgres_password=admin    volumes:      - postgres_data:/var/lib/postgresql/datavolumes:  postgres_data:

运行命令启动容器。

docker compose up -d

设置 application.properties 文件

这个文件是spring boot应用程序的配置。

spring.datasource.url=jdbc:postgresql://localhost:5432/databasespring.datasource.username=adminspring.datasource.password=adminspring.jpa.hibernate.ddl-auto=updatespring.jpa.show-sql=truejwt.public.key=classpath:public.keyjwt.private.key=classpath:private.key

jwt.public.key 和 jwt.private.key 是我们将进一步创建的密钥。

生成私钥和公钥

永远不要将这些密钥提交到你的github

在控制台运行,在资源目录下生成私钥

cd src/main/resourcesopenssl genrsa > private.key

之后,创建链接到私钥的公钥。

openssl rsa -in private.key -pubout -out public.key 

代码

创建安全配置文件

靠近主函数创建一个目录 configs 并在其中创建一个 securityconfig.java 文件。

import java.security.interfaces.rsaprivatekey;import java.security.interfaces.rsapublickey;import org.springframework.beans.factory.annotation.value;import org.springframework.context.annotation.bean;import org.springframework.context.annotation.configuration;import org.springframework.http.httpmethod;import org.springframework.security.config.annotation.web.builders.httpsecurity;import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;import org.springframework.security.oauth2.jwt.jwtdecoder;import org.springframework.security.oauth2.jwt.jwtencoder;import org.springframework.security.oauth2.jwt.nimbusjwtdecoder;import org.springframework.security.oauth2.jwt.nimbusjwtencoder;import org.springframework.security.web.securityfilterchain;import com.nimbusds.jose.jwk.jwkset;import com.nimbusds.jose.jwk.rsakey;import com.nimbusds.jose.jwk.source.immutablejwkset;@configuration@enablewebsecurity@enablemethodsecuritypublic class securityconfig {    @value("${jwt.public.key}")    private rsapublickey publickey;    @value("${jwt.private.key}")    private rsaprivatekey privatekey;    @bean    securityfilterchain securityfilterchain(httpsecurity http) throws exception {        http                .csrf(csrf -> csrf.disable())                .authorizehttprequests(auth -> auth.requestmatchers(httpmethod.post, "/signin").permitall()                        .requestmatchers(httpmethod.post, "/login").permitall()                        .anyrequest().authenticated())                .oauth2resourceserver(config -> config.jwt(jwt -> jwt.decoder(jwtdecoder())));        return http.build();    }    @bean    bcryptpasswordencoder bpasswordencoder() {        return new bcryptpasswordencoder();    }    @bean    jwtencoder jwtencoder() {        var jwk = new rsakey.builder(this.publickey).privatekey(this.privatekey).build();        var jwks = new immutablejwkset(new jwkset(jwk));        return new nimbusjwtencoder(jwks);    }    @bean    jwtdecoder jwtdecoder() {        return nimbusjwtdecoder.withpublickey(publickey).build();    }}

解释

@enablewebscurity:当您使用@enablewebsecurity时,它会自动触发spring security的配置以保护web应用程序。此配置包括设置过滤器、保护端点以及应用各种安全规则。

@enablemethodsecurity:是 spring security 中的一个注释,可在 spring 应用程序中启用方法级安全性。它允许您使用 @preauthorize、@postauthorize、@secured 和 @rolesallowed 等注释直接在方法级别应用安全规则。

jQuery与CSS3选择滑块按钮代码 jQuery与CSS3选择滑块按钮代码

jQuery与CSS3选择滑块按钮代码

jQuery与CSS3选择滑块按钮代码 29 查看详情 jQuery与CSS3选择滑块按钮代码

privatekeypublickey:是用于签名和验证 jwt 的 rsa 公钥和私钥。 @value 注解将属性文件(application.properties)中的键注入到这些字段中。

csrf:禁用 csrf(跨站请求伪造)保护,该保护通常在使用 jwt 进行身份验证的无状态 rest api 中禁用。

authorizehttprequests:配置基于url的授权规则。

requestmatchers(httpmethod.post, “/signin”).permitall():允许未经身份验证访问 /signin 和 /login 端点,这意味着任何人都可以在不登录的情况下访问这些路由。anyrequest().authenticated():需要对所有其他请求进行身份验证。

oauth2resourceserver:将应用程序配置为使用 jwt 进行身份验证的 oauth 2.0 资源服务器。

config.jwt(jwt -> jwt.decoder(jwtdecoder())):指定将用于解码和验证 jwt 令牌的 jwt 解码器 bean (jwtdecoder)。

bcryptpasswordencoder:这个bean定义了一个密码编码器,它使用bcrypt哈希算法对密码进行编码。 bcrypt 因其自适应特性而成为安全存储密码的热门选择,使其能够抵抗暴力攻击。

jwtencoder:这个bean负责编码(签名)jwt令牌。

rsakey.builder:使用提供的公钥和私钥 rsa 密钥创建新的 rsa 密钥。immutablejwkset(new jwkset(jwk)):将 rsa 密钥包装在 json web 密钥集 (jwkset) 中,使其不可变。nimbusjwtencoder(jwks):使用 nimbus 库创建 jwt 编码器,该编码器将使用 rsa 私钥对令牌进行签名。

jwtdecoder:这个bean负责解码(验证)jwt令牌。

nimbusjwtdecoder.withpublickey(publickey).build():使用rsa公钥创建jwt解码器,用于验证jwt令牌的签名。 实体

import org.springframework.security.crypto.password.passwordencoder;import jakarta.persistence.column;import jakarta.persistence.entity;import jakarta.persistence.enumtype;import jakarta.persistence.enumerated;import jakarta.persistence.generatedvalue;import jakarta.persistence.generationtype;import jakarta.persistence.id;import jakarta.persistence.table;import lombok.getter;import lombok.noargsconstructor;import lombok.setter;@entity@table(name = "tb_clients")@getter@setter@noargsconstructorpublic class cliententity {    @id    @generatedvalue(strategy = generationtype.sequence)    @column(name = "client_id")    private long clientid;    private string name;    @column(unique = true)    private string cpf;    @column(unique = true)    private string email;    private string password;    @column(name = "user_type")    private string usertype = "client";    public boolean islogincorrect(string password, passwordencoder passwordencoder) {        return passwordencoder.matches(password, this.password);    }}

存储库

import java.util.optional;import org.springframework.data.jpa.repository.jparepository;import org.springframework.stereotype.repository;import example.com.challengepicpay.entities.cliententity;@repositorypublic interface clientrepository extends jparepository {    optional findbyemail(string email);    optional findbycpf(string cpf);    optional findbyemailorcpf(string email, string cpf);}

服务

客户服务

import org.springframework.beans.factory.annotation.autowired;import org.springframework.http.httpstatus;import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;import org.springframework.stereotype.service;import org.springframework.web.server.responsestatusexception;import example.com.challengepicpay.entities.cliententity;import example.com.challengepicpay.repositories.clientrepository;@servicepublic class clientservice {    @autowired    private clientrepository clientrepository;    @autowired    private bcryptpasswordencoder bpasswordencoder;    public cliententity createclient(string name, string cpf, string email, string password) {        var clientexists = this.clientrepository.findbyemailorcpf(email, cpf);        if (clientexists.ispresent()) {            throw new responsestatusexception(httpstatus.bad_request, "email/cpf already exists.");        }        var newclient = new cliententity();        newclient.setname(name);        newclient.setcpf(cpf);        newclient.setemail(email);        newclient.setpassword(bpasswordencoder.encode(password));        return clientrepository.save(newclient);    }}

代币服务

import java.time.instant;import org.springframework.beans.factory.annotation.autowired;import org.springframework.http.httpstatus;import org.springframework.security.authentication.badcredentialsexception;import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;import org.springframework.security.oauth2.jwt.jwtclaimsset;import org.springframework.security.oauth2.jwt.jwtencoder;import org.springframework.security.oauth2.jwt.jwtencoderparameters;import org.springframework.stereotype.service;import org.springframework.web.server.responsestatusexception;import example.com.challengepicpay.repositories.clientrepository;@servicepublic class tokenservice {    @autowired    private clientrepository clientrepository;    @autowired    private jwtencoder jwtencoder;    @autowired    private bcryptpasswordencoder bcryptpasswordencoder;    public string login(string email, string password) {        var client = this.clientrepository.findbyemail(email)                .orelsethrow(() -> new responsestatusexception(httpstatus.bad_request, "email not found"));        var iscorrect = client.islogincorrect(password, bcryptpasswordencoder);        if (!iscorrect) {            throw new badcredentialsexception("email/password invalid");        }        var now = instant.now();        var expiresin = 300l;        var claims = jwtclaimsset.builder()                .issuer("pic_pay_backend")                .subject(client.getemail())                .issuedat(now)                .expiresat(now.plusseconds(expiresin))                .claim("scope", client.getusertype())                .build();        var jwtvalue = jwtencoder.encode(jwtencoderparameters.from(claims)).gettokenvalue();        return jwtvalue;    }}

控制器

客户端控制器

package example.com.challengepicpay.controllers;import org.springframework.beans.factory.annotation.autowired;import org.springframework.http.httpstatus;import org.springframework.http.responseentity;import org.springframework.web.bind.annotation.postmapping;import org.springframework.web.bind.annotation.requestbody;import org.springframework.web.bind.annotation.restcontroller;import org.springframework.security.oauth2.server.resource.authentication.jwtauthenticationtoken;import example.com.challengepicpay.controllers.dto.newclientdto;import example.com.challengepicpay.entities.cliententity;import example.com.challengepicpay.services.clientservice;@restcontrollerpublic class clientcontroller {    @autowired    private clientservice clientservice;    @postmapping("/signin")    public responseentity createnewclient(@requestbody newclientdto client) {        var newclient = this.clientservice.createclient(client.name(), client.cpf(), client.email(), client.password());        return responseentity.status(httpstatus.created).body(newclient);    }    @getmapping("/protectedroute")    @preauthorize("hasauthority('scope_client')")    public responseentity protectedroute(jwtauthenticationtoken token) {        return responseentity.ok("authorized");    }}

解释

/protectedroute 是私有路由,登录后只能使用 jwt 访问。

例如,令牌必须作为不记名令牌包含在标头中。

您可以稍后在应用程序中使用令牌信息,例如在服务层中。

@preauthorize:spring security中的@preauthorize注解用于在调用方法之前执行授权检查。此注释通常应用于 spring 组件(如控制器或服务)中的方法级别,以根据用户的角色、权限或其他安全相关条件来限制访问。注解用于定义方法执行必须满足的条件。如果条件评估为真,则该方法继续进行。如果评估结果为 false,则访问被拒绝,

“hasauthority(‘scope_client’)”:检查当前经过身份验证的用户或客户端是否具有特定权限 scope_client。如果这样做,则执行 protectedroute() 方法。如果不这样做,访问将被拒绝。

token controller:在这里,你可以登录应用程序,如果成功,会返回一个token。

package example.com.challengePicPay.controllers;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.RestController;import example.com.challengePicPay.controllers.dto.LoginDTO;import example.com.challengePicPay.services.TokenService;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;@RestControllerpublic class TokenController {    @Autowired    private TokenService tokenService;    @PostMapping("/login")    public ResponseEntity<Map> login(@RequestBody LoginDTO loginDTO) {        var token = this.tokenService.login(loginDTO.email(), loginDTO.password());        return ResponseEntity.ok(Map.of("token", token));    }}

参考

春季安全spring security-toptal 文章

以上就是Spring Security 与 JWT的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/494100.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月8日 17:06:52
下一篇 2025年11月8日 17:07:36

相关推荐

  • HTML、CSS 和 JavaScript 中的简单侧边栏菜单

    构建一个简单的侧边栏菜单是一个很好的主意,它可以为您的网站添加有价值的功能和令人惊叹的外观。 侧边栏菜单对于客户找到不同项目的方式很有用,而不会让他们觉得自己有太多选择,从而创造了简单性和秩序。 今天,我将分享一个简单的 HTML、CSS 和 JavaScript 源代码来创建一个简单的侧边栏菜单。…

    2025年12月24日
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    300
  • 带有 HTML、CSS 和 JavaScript 工具提示的响应式侧边导航栏

    响应式侧边导航栏不仅有助于改善网站的导航,还可以解决整齐放置链接的问题,从而增强用户体验。通过使用工具提示,可以让用户了解每个链接的功能,包括设计紧凑的情况。 在本教程中,我将解释使用 html、css、javascript 创建带有工具提示的响应式侧栏导航的完整代码。 对于那些一直想要一个干净、简…

    2025年12月24日
    000
  • 布局 – CSS 挑战

    您可以在 github 仓库中找到这篇文章中的所有代码。 您可以在这里查看视觉效果: 固定导航 – 布局 – codesandbox两列 – 布局 – codesandbox三列 – 布局 – codesandbox圣杯 &#8…

    2025年12月24日
    000
  • 隐藏元素 – CSS 挑战

    您可以在 github 仓库中找到这篇文章中的所有代码。 您可以在此处查看隐藏元素的视觉效果 – codesandbox 隐藏元素 hiding elements hiding elements hiding elements hiding elements hiding element…

    2025年12月24日
    400
  • 居中 – CSS 挑战

    您可以在 github 仓库中找到这篇文章中的所有代码。 您可以在此处查看垂直中心 – codesandbox 和水平中心的视觉效果。 通过 css 居中 垂直居中 centering centering centering centering centering centering立即…

    2025年12月24日 好文分享
    300
  • 如何在 Laravel 框架中轻松集成微信支付和支付宝支付?

    如何用 laravel 框架集成微信支付和支付宝支付 问题:如何在 laravel 框架中集成微信支付和支付宝支付? 回答: 建议使用 easywechat 的 laravel 版,easywechat 是一个由腾讯工程师开发的高质量微信开放平台 sdk,已被广泛地应用于许多 laravel 项目中…

    2025年12月24日
    000
  • 如何在移动端实现子 div 在父 div 内任意滑动查看?

    如何在移动端中实现让子 div 在父 div 内任意滑动查看 在移动端开发中,有时我们需要让子 div 在父 div 内任意滑动查看。然而,使用滚动条无法实现负值移动,因此需要采用其他方法。 解决方案: 使用绝对布局(absolute)或相对布局(relative):将子 div 设置为绝对或相对定…

    2025年12月24日
    000
  • 移动端嵌套 DIV 中子 DIV 如何水平滑动?

    移动端嵌套 DIV 中子 DIV 滑动 在移动端开发中,遇到这样的问题:当子 DIV 的高度小于父 DIV 时,无法在父 DIV 中水平滚动子 DIV。 无限画布 要实现子 DIV 在父 DIV 中任意滑动,需要创建一个无限画布。使用滚动无法达到负值,因此需要使用其他方法。 相对定位 一种方法是将子…

    2025年12月24日
    000
  • 移动端项目中,如何消除rem字体大小计算带来的CSS扭曲?

    移动端项目中消除rem字体大小计算带来的css扭曲 在移动端项目中,使用rem计算根节点字体大小可以实现自适应布局。但是,此方法可能会导致页面打开时出现css扭曲,这是因为页面内容在根节点字体大小赋值后重新渲染造成的。 解决方案: 要避免这种情况,将计算根节点字体大小的js脚本移动到页面的最前面,即…

    2025年12月24日
    000
  • Nuxt 移动端项目中 rem 计算导致 CSS 变形,如何解决?

    Nuxt 移动端项目中解决 rem 计算导致 CSS 变形 在 Nuxt 移动端项目中使用 rem 计算根节点字体大小时,可能会遇到一个问题:页面内容在字体大小发生变化时会重绘,导致 CSS 变形。 解决方案: 可将计算根节点字体大小的 JS 代码块置于页面最前端的 标签内,确保在其他资源加载之前执…

    2025年12月24日
    200
  • Nuxt 移动端项目使用 rem 计算字体大小导致页面变形,如何解决?

    rem 计算导致移动端页面变形的解决方法 在 nuxt 移动端项目中使用 rem 计算根节点字体大小时,页面会发生内容重绘,导致页面打开时出现样式变形。如何避免这种现象? 解决方案: 移动根节点字体大小计算代码到页面顶部,即 head 中。 原理: flexível.js 也遇到了类似问题,它的解决…

    2025年12月24日
    000
  • 形状 – CSS 挑战

    您可以在 github 仓库中找到这篇文章中的所有代码。 您可以在此处查看 codesandbox 的视觉效果。 通过css绘制各种形状 如何在 css 中绘制正方形、梯形、三角形、异形三角形、扇形、圆形、半圆、固定宽高比、0.5px 线? shapes 0.5px line .square { w…

    2025年12月24日
    000
  • 有哪些美观的开源数字大屏驾驶舱框架?

    开源数字大屏驾驶舱框架推荐 问题:有哪些美观的开源数字大屏驾驶舱框架? 答案: 资源包 [弗若恩智能大屏驾驶舱开发资源包](https://www.fanruan.com/resource/152) 软件 [弗若恩报表 – 数字大屏可视化组件](https://www.fanruan.c…

    2025年12月24日
    000
  • 网站底部如何实现飘彩带效果?

    网站底部飘彩带效果的 js 库实现 许多网站都会在特殊节日或活动中添加一些趣味性的视觉效果,例如点击按钮后散发的五彩缤纷的彩带。对于一个特定的网站来说,其飘彩带效果的实现方式可能有以下几个方面: 以 https://dub.sh/ 网站为例,它底部按钮点击后的彩带效果是由 javascript 库实…

    2025年12月24日
    000
  • 网站彩带效果背后是哪个JS库?

    网站彩带效果背后是哪个js库? 当你访问某些网站时,点击按钮后,屏幕上会飘出五颜六色的彩带,营造出庆祝的氛围。这些效果是通过使用javascript库实现的。 问题: 哪个javascript库能够实现网站上点击按钮散发彩带的效果? 答案: 根据给定网站的源代码分析: 可以发现,该网站使用了以下js…

    好文分享 2025年12月24日
    100
  • 产品预览卡项目

    这个项目最初是来自 Frontend Mentor 的挑战,旨在使用 HTML 和 CSS 创建响应式产品预览卡。最初的任务是设计一张具有视觉吸引力和功能性的产品卡,能够无缝适应各种屏幕尺寸。这涉及使用 CSS 媒体查询来确保布局在不同设备上保持一致且用户友好。产品卡包含产品图像、标签、标题、描述和…

    2025年12月24日
    100
  • 如何利用 echarts-gl 绘制带发光的 3D 图表?

    如何绘制带发光的 3d 图表,类似于 echarts 中的示例? 为了实现类似的 3d 图表效果,需要引入 echarts-gl 库:https://github.com/ecomfe/echarts-gl。 echarts-gl 专用于在 webgl 环境中渲染 3d 图形。它提供了各种 3d 图…

    2025年12月24日
    000
  • 如何在 Element UI 的 el-rate 组件中实现 5 颗星 5 分制与百分制之间的转换?

    如何在el-rate中将5颗星5分制的分值显示为5颗星百分制? 要实现该效果,只需使用 el-rate 组件的 allow-half 属性。在设置 allow-half 属性后,获得的结果乘以 20 即可得到0-100之间的百分制分数。如下所示: score = score * 20; 动态显示鼠标…

    2025年12月24日
    100
  • CSS 最佳实践:后端程序员重温 CSS 时常见的三个疑问?

    CSS 最佳实践:提升代码质量 作为后端程序员,在重温 CSS/HTML 时,你可能会遇到一些关于最佳实践的问题。以下将解答三个常见问题,帮助你编写更规范、清晰的 CSS 代码。 1. margin 设置策略 当相邻元素都设置了 margin 时,通常情况下应为上一个元素设置 margin-bott…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信