0

0

怎么使用SpringBoot+SpringSecurity+JWT实现系统认证与授权

WBOY

WBOY

发布时间:2023-05-14 20:16:04

|

2370人浏览过

|

来源于亿速云

转载

1. spring security简介

spring security是spring的一个核心项目,它是一个功能强大且高度可定制的认证和访问控制框架。它提供了认证和授权功能以及抵御常见的攻击,它已经成为保护基于spring的应用程序的事实标准。

Spring Boot提供了自动配置,引入starter依赖即可使用。
Spring Security特性总结:

  • 使用简单,提供Spring Boot starter依赖,极易与Spring Boot项目整合。

  • 专业,提供CSRF防护、点击劫持防护、XSS防护等,并提供各种安全头整合(X-XSS-Protection,X-Frame-Options等)。

  • 密码加密存储,支持多种加密算法

  • 扩展性和可定制性极强

  • OAuth3 JWT认证支持

  • … …

2. JWT简介

JWT(Json web token),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息(例如,权限信息)。一旦用户被授予token,用户即可通过该token访问服务器上的资源。

3. Spring Boot整合Spring Security

注意本篇文章演示使用JDK和Spring Boot的版本如下:Spring Boot:2.7.2JDK:11不同的Spring Boot版本配置不同,但是原理相同。

在Spring Boot项目的pom.xml文件中加入下面的依赖:

<!-- Spring Security的Spring boot starter,引入后将自动启动Spring Security的自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 下面的依赖包含了OAuth3 JWT认证实现 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth3-resource-server</artifactId>
</dependency>

以上两个依赖即可。

4. 配置Spring Security使用JWT认证

注意: 不同的Spring Boot版本配置不同,但是原理相同,本文使用的是Spring Boot:2.7.2。

主要是配置HttpSecurity Bean生成SecurityFilterBean,配置如下:

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth3.jwt.JwtDecoder;
import org.springframework.security.oauth3.jwt.JwtEncoder;
import org.springframework.security.oauth3.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth3.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth3.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth3.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth3.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth3.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * Spring Security 配置
 *
 * @author cloudgyb
 * @since 2022/7/30 18:31
 */
@Configuration(proxyBeanMethods = false)
@EnableMethodSecurity
public class WebSecurityConfigurer {
    //使用RSA对JWT做签名,所以这里需要一对秘钥。
    //秘钥文件的路径在application.yml文件中做了配置(具体配置在下面)。
    @Value("${jwt.public.key}")
    private RSAPublicKey key; 
    @Value("${jwt.private.key}")
    private RSAPrivateKey priv;

     /**
      * 构建SecurityFilterChain bean
      */
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        //"/login"是系统的登录接口,所以需要匿名可访问
        http.authorizeRequests().antMatchers("/login").anonymous();
        //其他请求都需认证后才能访问
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                
                //采用JWT认证无需session保持,所以禁用掉session管理器
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //login接口可能来自其他站点,所以对login不做csrf防护
                .csrf((csrf) -> csrf.ignoringAntMatchers("/login"))
                //配置认证方式为JWT,并且配置了一个JWT认证装换器,用于去掉解析权限时的SCOOP_前缀
                .oauth3ResourceServer().jwt().jwtAuthenticationConverter(
                        JwtAuthenticationConverter()
                );
        //配置认证失败或者无权限时的处理器
        http.exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
        );
         //根据配置生成SecurityFilterChain对象
        return http.build();
    }


    /**
     * JWT解码器,用于认证时的JWT解码 
     */
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }
    /**
     * JWT编码器,生成JWT
     */
    @Bean
    JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }
    
    /**
     * JWT认证解码时,去掉Spring Security对权限附带的默认前缀SCOOP_
     */
    @Bean
    JwtAuthenticationConverter JwtAuthenticationConverter() {
        final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

application.yml

jwt:
  private.key: classpath:app.key
  public.key: classpath:app.pub

上边的配置需要在Spring Boot项目的Resource目录下生成一对RSA秘钥。
可以使用下面的网站进行生成:http://tools.jb51.net/password/rsa_encode/,注意: 密钥格式使用 PKCS#8,私钥密码为空。

还有一点需要说明,我在代码中使用了Spring Boot的值注入:

@Value("${jwt.public.key}")
 private RSAPublicKey key; 
@Value("${jwt.private.key}")
private RSAPrivateKey priv;

有没有很好奇Spring Boot是如何将yaml文件中的字符串对应的文件转换为RSAPublicKey和RSAPrivateKey ?
其实是Spring Security帮我们做了处理,在Spring Security中帮我们实现了一个转换器ResourceKeyConverterAdapter,具体可以阅读相关源码来更深入的了解。

Favird No-Code Tools
Favird No-Code Tools

无代码工具的聚合器

下载

至此我们的项目已经支持JWT认证了。
但是用户需要在请求头Authorization中携带合法的JWT才能通过认证,进而访问服务器资源,那么如何给用户颁发一个合法的JWT呢?
很简单,可以提供一个登录接口,让用户输入用户名和密码,匹配成功后颁发令牌即可。

其实并不是必须这样做,还有其他方式,比如我们调用第三方接口,我们经常的做法是先去第三方申请,申请通过后我们就可以得到一个令牌。这个过程和上面的登录通过后颁发一个令牌是一样的,都是通过合法的途径获得一个令牌!

5. 实现登录接口

登录接口只有一个目的,就是给合法用户颁发令牌!
登录API接口:

@RestController
public class SysLoginController {
    private final SysLoginService sysLoginService;

    public SysLoginController(SysLoginService sysLoginService) {
        this.sysLoginService = sysLoginService;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginInfo loginInfo) {
        return sysLoginService.login(loginInfo);
    }
}

登录逻辑实现:

@Service
public class SysLoginService {
    private final JwtEncoder jwtEncoder;
    private final SpringSecurityUserDetailsService springSecurityUserDetailsService;

    public SysLoginService(JwtEncoder jwtEncoder, SpringSecurityUserDetailsService springSecurityUserDetailsService) {
        this.jwtEncoder = jwtEncoder;
        this.springSecurityUserDetailsService = springSecurityUserDetailsService;
    }

    public String login(LoginInfo loginInfo) {
        //从用户信息存储库中获取用户信息
        final UserDetails userDetails = springSecurityUserDetailsService.loadUserByUsername(loginInfo.getUsername());
        final String password = userDetails.getPassword();
        //匹配密码,匹配成功生成JWT令牌
        if (password.equals(loginInfo.getPassword())) {
            return generateToken(userDetails);
        }
        //密码不匹配,抛出异常,Spring Security发现抛出该异常后会将http响应状态码设置为401 unauthorized
        throw new BadCredentialsException("密码错误!");
    }

    private String generateToken(UserDetails userDetails) {
        Instant now = Instant.now();
        //JWT过期时间为36000秒,也就是600分钟,10小时
        long expiry = 36000L;
        String scope = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
         //将用户权限信息使用空格分割拼为字符串,放到JWT的payload的scope字段中,注意不要改变scope这个属性,这是Spring Security OAuth3 JWT默认处理方式,在JWT解码时需要读取该字段,转为用户的权限信息!
        JwtClaimsSet claims = JwtClaimsSet.builder()
                .issuer("self")
                .issuedAt(now)
                .expiresAt(now.plusSeconds(expiry))
                .subject(userDetails.getUsername())
                .claim("scope", scope)
                .build();
        return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }
}

其他非核心代码这里就不贴出来了,我将代码放到github上了,具体可以转到https://github.com/cloudgyb/spring-security-study-jwt。

6. 测试

使用postman测试一下:
使用错误的密码,会返回401 Unauthorized的状态码,表示我们认证失败!

怎么使用SpringBoot+SpringSecurity+JWT实现系统认证与授权

使用正确的用户名和密码:

怎么使用SpringBoot+SpringSecurity+JWT实现系统认证与授权

返回了JWT令牌。

此时客户端拿到了合法的令牌,接下来就可以访问服务器上有权访问的资源了。
我写了一个测试接口:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority('test')")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

该接口需要用户拥有"test"的权限,但是登录用户没有该权限(只有一个app的权限),此时调用该接口:
首先将上一步登录获得的令牌粘贴到token中:

怎么使用SpringBoot+SpringSecurity+JWT实现系统认证与授权

我们发送请求得到了403 Forbidden的响应,意思就是我们没有访问权限,此时我们将接口权限改为“app”:

@RestController
public class HelloController {

    @GetMapping("/")
    @PreAuthorize("hasAuthority('app')")
    public String hello(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}

重启项目。再次发起请求:

怎么使用SpringBoot+SpringSecurity+JWT实现系统认证与授权

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

38

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

83

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

97

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

223

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

458

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

169

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

246

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

34

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Redis6入门到精通超详细教程
Redis6入门到精通超详细教程

共47课时 | 5.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号