SpringBoot+SpringSecurity+JWT实现系统认证与授权示例

目录
  • 1. Spring Security简介
  • 2. JWT简介
  • 3. Spring Boot整合Spring Security
  • 4. 配置Spring Security使用JWT认证
  • 5. 实现登录接口
  • 6. 测试
  • 7. 源码

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等)。
  • 密码加密存储,支持多种加密算法
  • 扩展性和可定制性极强
  • OAuth2 JWT认证支持
  • … …

2. JWT简介

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

https://jwt.io/,该网站提供了一个debuggr,便于初学者学习理解JWT。

3. Spring Boot整合Spring Security

注意本篇文章演示使用JDK和Spring Boot的版本如下:
Spring Boot:2.7.2
JDK: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>
<!-- 下面的依赖包含了OAuth2 JWT认证实现 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-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.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.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.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_前缀
                .oauth2ResourceServer().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,具体可以阅读相关源码来更深入的了解。

至此我们的项目已经支持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 OAuth2 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的状态码,表示我们认证失败!

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

返回了JWT令牌。

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

@RestController
public class HelloController {

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

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

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

@RestController
public class HelloController {

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

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

我们已经可以正常访问了!

Spring Security专业性很强,有些术语对于初学者可能有点难度,但是一旦掌握这些概念,你会喜欢上Spring Security的!

7. 源码

这儿有一个可直接运行的demo供参考:https://github.com/cloudgyb/spring-security-study-jwt

到此这篇关于SpringBoot+SpringSecurity+JWT实现系统认证与授权示例的文章就介绍到这了,更多相关SpringBoot SpringSecurity JWT认证授权内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot+jwt+springSecurity微信小程序授权登录问题

    场景重现:1.微信小程序向后台发送请求 --而后台web采用的springSecuriry没有token生成,就会拦截请求,,所以小编记录下这个问题 微信小程序授权登录问题 思路 参考网上一大堆资料 核心关键字: 自定义授权+鉴权 (说的通俗就是解决办法就是改造springSecurity的过滤器) 参考文章 https://www.jb51.net/article/204704.htm 总的来说的 通过自定义的WxAppletAuthenticationFilter替换默认的UsernameP

  • SpringBoot集成SpringSecurity和JWT做登陆鉴权的实现

    废话 目前流行的前后端分离让Java程序员可以更加专注的做好后台业务逻辑的功能实现,提供如返回Json格式的数据接口就可以.SpringBoot的易用性和对其他框架的高度集成,用来快速开发一个小型应用是最佳的选择. 一套前后端分离的后台项目,刚开始就要面对的就是登陆和授权的问题.这里提供一套方案供大家参考. 主要看点: 登陆后获取token,根据token来请求资源 根据用户角色来确定对资源的访问权限 统一异常处理 返回标准的Json格式数据 正文 首先是pom文件: <dependencies

  • SpringBoot+SpringSecurity+jwt实现验证

    目录 环境 目录结构信息 记录一下使用springSecurity实现jwt的授权方法,这方法可以实现权限的基本认证.当然这个案例还有许多的问题,不过还是先记录一下.其他功能以后在补充. 建议工程创建流程 创建 JwtTokenUtils 创建 jwtAccessDeniedHandler 和 JwtAuthenticationEntryPoint 创建 UserDetailsServiceImpl 创建 JwtAuthenticationFilter 配置 Security信息 启动类的信息

  • Springboot+SpringSecurity+JWT实现用户登录和权限认证示例

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的后端框架是Spring-Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进行了很好的无缝连接,在移动互联网的授权认证方面有很强的优势,具体的使用大家可以结合自己的业务场景进行选

  • 详解SpringBoot+SpringSecurity+jwt整合及初体验

    原来一直使用shiro做安全框架,配置起来相当方便,正好有机会接触下SpringSecurity,学习下这个.顺道结合下jwt,把安全信息管理的问题扔给客户端, 准备 首先用的是SpringBoot,省去写各种xml的时间.然后把依赖加入一下 <!--安全--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-secu

  • SpringBoot整合SpringSecurity实现JWT认证的项目实践

    目录 前言 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5.实现token验证的过滤器 6. SpringSecurity的关键配置 7. 编写Controller进行测试 前言 微服务架构,前后端分离目前已成为互联网项目开发的业界标准,其核心思想就是前端(APP.小程序.H5页面等)通过调用后端的API接口,提交及返回JSON数据进行交互.在前后端分离项目中,

  • SpringBoot+SpringSecurity+JWT实现系统认证与授权示例

    目录 1. Spring Security简介 2. JWT简介 3. Spring Boot整合Spring Security 4. 配置Spring Security使用JWT认证 5. 实现登录接口 6. 测试 7. 源码 1. Spring Security简介 Spring Security是Spring的一个核心项目,它是一个功能强大且高度可定制的认证和访问控制框架.它提供了认证和授权功能以及抵御常见的攻击,它已经成为保护基于spring的应用程序的事实标准. Spring Boot

  • SpringBoot+SpringSecurity实现基于真实数据的授权认证

    (一)概述 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,Spring Security主要做两个事情,认证.授权.我之前写过一篇关于SpringSecurity的博客,但是当时只是介绍了基于mock数据的案例,本期就来介绍一下基于真实数据的认证授权实现. (二)前期项目搭建 为了更好的展示SpringSecurity,我们先搭建一个简单的web项目出来.引入thymeleaf依赖 <dependency> <groupId>org.spring

  • Java SpringSecurity+JWT实现登录认证

    目录 整合步骤 实现原理 目录结构 做了哪些变化 前言: 学习过我的mall项目的应该知道,mall-admin模块是使用SpringSecurity+JWT来实现登录认证的,而mall-portal模块是使用的SpringSecurity基于Session的默认机制来实现登陆认证的.很多小伙伴都找不到mall-portal的登录接口,最近我把这两个模块的登录认证给统一了,都使用SpringSecurity+JWT的形式实现.主要是通过把登录认证的通用逻辑抽取到了mall-security模块来

  • SpringBoot使用JWT实现登录验证的方法示例

    什么是JWT JSON Web Token(JWT)是一个开放的标准(RFC 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以JSON对象安全地传输信息.这些信息可以通过数字签名进行验证和信任.可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对来对JWT进行签名. 具体的jwt介绍可以查看官网的介绍:https://jwt.io/introduction/ jwt请求流程 引用官网的图片 中文介绍: 用户使用账号和面发出post请求: 服务器使用私钥创建一个jwt: 服务器返

  • SpringBoot 使用jwt进行身份验证的方法示例

    这里只供参考,比较使用jwt方式进行身份验证感觉不好,最不行的就是不能退出 登陆时设定多长过期时间,只能等这个时间过了以后才算退出,服务端只能验证请求过来的token是否通过验证 Code: /** * Created by qhong on 2018/6/7 15:34 * 标注该注解的,就不需要登录 **/ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documente

  • 基于SpringBoot整合oauth2实现token认证

    这篇文章主要介绍了基于SpringBoot整合oauth2实现token 认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 session和token的区别: session是空间换时间,而token是时间换空间.session占用空间,但是可以管理过期时间,token管理部了过期时间,但是不占用空间. sessionId失效问题和token内包含. session基于cookie,app请求并没有cookie . token更加安全(每次请

  • Springboot如何实现Web系统License授权认证

    在我们做系统级框架的时候,我们要一定程度上考虑系统的使用版权,不能随便一个人拿去在任何环境都能用,所以我们需要给我们系统做一个授权认证机制,只有上传了我们下发的lic文件并验证通过,才能正常使用,下面就开始一步一步实现这个功能 1.生成机器码 我们首先要做的就是对软件部署的环境的唯一性进行限制,这里使用的是macadderss,当然你也可以换成cpu序列编号,并无太大影响,先上代码 private static String getMac() { try { Enumeration<Networ

随机推荐