SpringBoot3.0 + SpringSecurity6.0+JWT

Posted 七七r

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot3.0 + SpringSecurity6.0+JWT相关的知识,希望对你有一定的参考价值。

JWT_SpringSecurity

SpringBoot3.0 + SpringSecurity6.0+JWT

Spring Security 是 Spring 家族中的一个安全管理框架。

一般Web应用的需要进行认证授权

  • 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

  • 授权:经过认证后判断当前用户是否有权限进行某个操作

1、快速入门

1.1、准备工作

搭建一个SpringBoot工程

① 设置父工程 添加依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.3</version>
        <relativePath/> <!-- lookup parent from repository -->
</parent>


<dependencies>
        <!--   DB相关     -->
        <!--   JDBC操作数据库     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--    mysql依赖    -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--    mp依赖,简化crud    -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--    SpringSecurity依赖    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--   SpringWeb依赖     -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--    热部署依赖    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!--   懒人神器lombok     -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--    API文档 - swagger   -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.0.0</version>
        </dependency>
    </dependencies>

配置文件application.yml

# 端口号
server:
  port: 48080

--- #################### 数据库相关配置 ####################

spring:
  # 数据源配置项
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/auth-system?useSSL=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true # MySQL Connector/J 8.X 连接的示例
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root # 数据库账号
    password: 123123123 # 数据库密码
    # HikariCP 自定义配置,对应 HikariConfig 配置属性类
    hikari:
      minimum-idle: 10 # 池中维护的最小空闲连接数,默认为 10 个。
      maximum-pool-size: 10 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。

# springdoc-openapi项目配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: org.pp.boot3
# knife4j的增强配置,不需要增强可以不配
knife4j:
  enable: true
  setting:
    language: zh_cn

② 创建启动类

/**
 * @author ss_419
 */
@SpringBootApplication
public class SpringSecurity6JwtBoot3Application 

    public static void main(String[] args) 
        SpringApplication.run(SpringSecurity6JwtBoot3Application.class, args);
    


③ 创建Controller

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * TODO 测试接口
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/2 20:27
 */
@RestController
@RequestMapping("/api/v1/")
@Tag(name = "测试接口")
public class GreetingController 

    @GetMapping(value = "/hello")
    @Operation(summary = "hello")
    public ResponseEntity<String> sayHello() 
        String message = "Hello World!";
        return ResponseEntity.ok(message);
    

启动项目,查看接口文档地址:http://localhost:48080/doc.html#/home

Knife4j的文档地址:http://ip:port/doc.html即可查看文档

出现测试接口,表示项目启动成功

1.2引入SpringSecurity

在SpringBoot项目中使用SpringSecurity我们只需要引入依赖即可实现入门案例。

注意⚠️:1.1创建项目时已经引入过依赖

        <!--    SpringSecurity依赖    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。

须登陆之后才能对接口进行访问

2、认证

2.1、原理初探

用户认证流程:

SpringSecurity的原理是一个过滤器链,内部包含了提供各种功能的过滤器。

  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

  • ExceptionTranslationFilter: 处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。

  • FilterSecurityInterceptor: 负责权限校验的过滤器。

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

  • AuthenticationManager接口:定义了认证Authentication的方法

  • UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法

  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成

  • UserDetails对象返回。然后将这些信息封装到Authentication对象中。

2.2、用户认证核心组件

Authentication`**,它存储了认证信息,代表当前登录用户。

我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取AuthenticationSecurityContext就是我们的上下文对象!这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolder原理非常简单,就是使用ThreadLocal来保证一个线程中传递同一个对象!

现在我们已经知道了Spring Security中三个核心组件:

​ 1、Authentication:存储了认证信息,代表当前登录用户

​ 2、SeucirtyContext:上下文对象,用来获取Authentication

​ 3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext

Authentication中是什么信息呢:

​ 1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

​ 2、Credentials:用户凭证,一般是密码

​ 3、Authorities:用户权限

用户认证:
Spring Security是怎么进行用户认证的呢?

AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。

Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

接下来我们分析一下一个请求发送到服务器都经历了什么:

上图中当有请求发送给服务器都要经过Check JWT Token机制,需要每次收到请求的时候,过滤器都处于活动状态。因此每次用户发送请求时希望过滤器被触发并完成要做的所有工作。

  • 如果我们有我们的用户电子邮箱并且用户未通过身份验证,我们会从数据库中获取用户详细信息(loadUserByUsername --> UserDetails)
  • 然后我们需要做的是检查用户是否有效,如果用户和令牌有效,我们创建一个UsernamePasswordAuthenticationToken对象,传递UserDetails & 凭证 & 权限信息
  • 扩展上面生成的authToken,包含我们请求的详细信息,然后更新安全上下文中的身份验证令牌
  • 最后一步执行过滤器chain,别忘记放行,将请求通过DispatchServlet分发响应给客户端

登录认证流程

登录
①自定义登录接口

调用ProviderManager的方法进行认证 如果认证通过生成jwt 把用户信息存入redis中

②自定义UserDetailsService

在这个实现类中去查询数据库

校验:
①定义Jwt认证过滤器

获取token

解析token获取其中的userid

从redis中获取用户信息

存入SecurityContextHolder

3、JWT_Security整合流程

3.1、什么是JWT

JWT 主要用于用户登录鉴权,所以我们从最传统的 session 认证开始说起。

Session认证:

众所周知,http 协议本身是无状态的协议,那就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过 http 协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至 session ),然后在认证成功后返回 cookie 值传递给浏览器,那么用户在下一次请求时就可以带上 cookie 值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。这就是传统的 session 认证方式。

session 认证的缺点其实很明显,由于 session 是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。于是乎为了解决 session 共享的问题,又引入了 redis,接着往下看。

Session认证还会引发CSRF(跨站请求伪造攻击),因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

Token认证:

这种方式跟Session的方式流程差不多,不同的地方在于保存的是一个token值,token一般是一串随机的字符(比如UUID),value 一般是用户ID,并且设置一个过期时间。

每次请求服务的时候带上 token 在请求头,后端接收到token 则根据 token 查一下 redis 是否存在,如果存在则表示用户已认证,如果 token 不存在则跳到登录界面让用户重新登录,登录成功后返回一个 token 值给客户端。

JWT认证:

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

3.1.1、JWT的数据结构:

JWT 一般是这样一个字符串,分为三个部分,以 “.” 隔开:

xxxxx.yyyyy.zzzzz

JWT官网:https://jwt.io/

进入官网我们可以看到首页有这样一个页面:

其中左侧是生成的jwt编码,我们可以看到它生成的格式就如上述所描述那样,分成了三段

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

右侧是对jwt字符串进行解码

HEADER

jwt的头部承载两部分信息:

声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

PLAYLOAD:
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

标准中注册的声明
公共的声明
私有的声明

标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

然后将其进行base64加密,得到JWT的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

VERIFY SIGNATURE:
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)

secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

如何应用:

一般是在请求头里加入Authorization,并加上Bearer标注:

 'Authorization': 'Bearer ' + token

3.1.2、签名密钥

在Json网络令牌的安全上下文中,签名密钥是用于对JWT进行数字签名的加密信息,签名密钥用于创建JWT的签名部分,用于验证JWT的发送者是否是已经经过确认的用户,并确保消息在整个过程中没有被更改(保证一致性),因此我们要确保发送此JWT密钥的用户是同一个人。

签名密钥通常与JWT标头中指定的登录算法结合使用,以创建签名具体的登录算法,密钥大小将取决于应用程序的安全要求和信任级别(签名方)

可以在allkeysgenertor中生成任意大小的签名密钥
注意⚠️:在JWT中最低安全级别是256bit,因此在本教程中,我们将采用256bit的签名密钥,如下所示:

3.2、准备工作

①添加依赖

        <!-- JWT 相关 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
        </dependency>
        <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.33</version>
        </dependency>

② 添加Redis相关配置

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 * @author ss_419
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>


    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    

    public FastJsonRedisSerializer(Class<T> clazz)
    
        super();
        this.clazz = clazz;
    

    @Override
    public byte[] serialize(T t) throws SerializationException
    
        if (t == null)
        
            return new byte[0];
        
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    
        if (bytes == null || bytes.length <= 0)
        
            return null;
        
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    


    protected JavaType getJavaType(Class<?> clazz)
    
        return TypeFactory.defaultInstance().constructType(clazz);
    

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * TODO Redis配置
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/3 10:24
 */
@Configuration
public class RedisConfig 
    /**
     * Redis配置
     */
    @Bean
    @SuppressWarnings(value = "unchecked", "rawtypes")
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory) 
        RedisTemplate<Object,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        FastJsonRedisSerializer

第十一篇SpringSecurity基于JWT实现Token的处理

SpringSecurity基于JWT实现Token的处理

  前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。

一、认证思路分析

  SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验身份的过滤器!

1.回顾集中式认证流程

用户认证
  使用 UsernamePasswordAuthenticationFilter过滤器中 attemptAuthentication方法实现认证功能,该过滤器父类中 successfulAuthentication方法实现认证成功后的操作。认证失败是在 unsuccessfulAuthentication

身份校验

  使用 BasicAuthenticationFilter 过滤器中 doFilterInternal方法验证是否登录,以决定能否进入后续过滤器。

2.分析分布式认证流程

用户认证
  由于分布式项目,多数是前后端分离的架构设计,我们要满足可以接受异步post的认证请求参数,需要修改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。
  另外,默认successfulAuthentication方法在认证通过后,是把用户信息直接放入session就完事了,现在我们需要修改这个方法,在认证通过后生成token并返回给用户。
身份校验
  原来BasicAuthenticationFilter过滤器中doFilterInternal方法校验用户是否登录,就是看session中是否有用户信息,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。

二、具体实现

1.创建项目

  创建一个SpringBoot项目.引入必要的依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.bobo</groupId>
            <artifactId>security-jwt-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>
	<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
    </dependencies>

2.JWT工具类

  引入前面创建的JWT的工具类。


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.security.SignatureException;
import java.util.Calendar;
import java.util.Map;

public class JWTUtils 
    // 秘钥
    private static final String SING = "123qwaszx";

    /**
     * 生成Token  header.payload.sing 组成
     * @return
     */
    public static String getToken(Map<String,String> map)
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7); // 默认过期时间 7天
        JWTCreator.Builder builder = JWT.create();
        // payload 设置
        map.forEach((k,v)->
            builder.withClaim(k,v);
        );
        // 生成Token 并返回
        return builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SING));
    

    /**
     * 验证Token
     * @return
     *     DecodedJWT  可以用来获取用户信息
     */
    public static DecodedJWT verify(String token)
        // 如果不抛出异常说明验证通过,否则验证失败
        DecodedJWT verify = null;
        try 
            verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
        catch (SignatureVerificationException e)
            e.printStackTrace();
        catch (AlgorithmMismatchException e)
            e.printStackTrace();
        catch (Exception e)
            e.printStackTrace();
        
        return verify;
    


3.用户实例

  创建用户的实例,添加必要的属性

@Data
public class UserPojo implements UserDetails 

    private Integer id;

    private String username;

    private String password;

    private Integer status;


    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        List<SimpleGrantedAuthority> auth = new ArrayList<>();
        auth.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return auth;
    

    @Override
    public String getPassword() 
        return this.password;
    

    @Override
    public String getUsername() 
        return this.username;
    
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isEnabled() 
        return true;
    

4.UserService

  完成基于SpringSecurity的数据库认证。创建UserService接口并实现

public interface UserService extends UserDetailsService 


@Service
public class UserServiceImpl implements UserService 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        UserPojo userPojo = new UserPojo();
        if("zhang".equals(username))
            userPojo.setUsername("zhang");
            userPojo.setPassword("$2a$10$hbMJRuxJoa6kWcfeT7cNPOGdoEXm5sdfSm5DQtp//2cmCF0MHO8b6");
            return userPojo;
        
        return userPojo;
    

5.自定义认证过滤器

  在SpringSecurity中的认证是通过UsernamePasswordAuthenticationFilter来处理的,现在我们要通过JWT来处理,那么我们就需要重写其中几个处理的方法

5.1 认证的方法

  认证的逻辑还是走的UserService处理,但是我们需要自己来手动的调用认证逻辑。

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 

        UserPojo sysUser = null;
        try 
            sysUser = JSON.parseObject(getJson(request), UserPojo.class);
         catch (IOException e) 
            e.printStackTrace();
        
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);
    

    public String getJson(HttpServletRequest request) throws IOException
        BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String inputStr;
        while ((inputStr = streamReader.readLine()) != null) 
            sb.append(inputStr);
        
        return sb.toString();
    

5.2 认证成功

  认证成功生成Token信息,并保存在响应的header头中。

@Override
    protected void successfulAuthentication(HttpServletRequest request
            , HttpServletResponse response
            , FilterChain chain
            , Authentication authResult) throws IOException, ServletException 
        // 生成Token信息
        Map<String,String> map = new HashMap<>();
        map.put("username",authResult.getName());
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        List<String> list = new ArrayList<>();
        for (GrantedAuthority authority : authorities) 
            list.add(authority.getAuthority());
        
        map.put("roles", JSON.toJSONString(list));
        String token = JWTUtils.getToken(map);
        response.addHeader("Authorization","Bearer"+token);
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map<String,Object> resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg", "认证通过!");
            out.write(JSON.toJSONString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

5.3 认证失败

  认证失败会调用 unsuccess… 方法来处理,那么在这儿我们就需要直接响应了

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException 
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
            resultMap.put("msg", "用户名或密码错误!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

完整代码:

package com.bobo.jwt.filter;

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter 

    private  AuthenticationManager authenticationManager;
    public TokenLoginFilter(AuthenticationManager authenticationManager)
        this.authenticationManager = authenticationManager;
    

    /**
     * 具体认证的方法
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 

        UserPojo sysUser = null;
        try 
            sysUser = JSON.parseObject(getJson(request), UserPojo.class);
         catch (IOException e) 
            e.printStackTrace();
        
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);
    

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException 
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
            resultMap.put("msg", "用户名或密码错误!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

    public String getJson(HttpServletRequest request) throws IOException
        BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String inputStr;
        while ((inputStr = streamReader.readLine()) != null) 
            sb.append(inputStr);
        
        return sb.toString();
    



    /**
     * 登录成功后的处理
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request
            , HttpServletResponse response
            , FilterChain chain
            , Authentication authResult) throws IOException, ServletException 
        // 生成Token信息
        Map<String,String> map = new HashMap<>();
        map.put("username",authResult.getName());
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        List<String> list = new ArrayList<>();
        for (GrantedAuthority authority : authorities) 
            list.add(authority.getAuthority());
        
        map.put("roles", JSON.toJSONString(list));
        String token = JWTUtils.getToken(map);
        response.addHeader("Authorization","Bearer"+token);
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map<String,Object> resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put(以上是关于SpringBoot3.0 + SpringSecurity6.0+JWT的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot3.0源码启动流程源码解析 •下

SpringBoot3.0 + SpringSecurity6.0+JWT

Spring Boot 3.0.0 GA版本正式发布,期待已久的SpringBoot3发布了

Spring Boot 3.0.0 GA版本正式发布,期待已久的SpringBoot3发布了

Springboot3.0+spring6.0+JDK17+配置jsp和打war包

Spring Boot 3.0

(c)2006-2024 SYSTEM All Rights Reserved IT常识