Spring Boot用Spring Security + JWT + MySQL实现基于Token的身份认证

在现代Web应用中,安全是一个至关重要的议题。随着微服务架构和分布式系统的普及,传统的会话管理方式已经无法满足需求。JSON Web Tokens(JWT)作为一种无状态的、基于Token的认证机制,提供了一种安全、高效且易于扩展的解决方案。本文将介绍如何在Spring Boot应用中,结合Spring Security和MySQL数据库,实现基于JWT的身份认证。

JWT简介

JWT是一种紧凑、自包含的方式,用于在双方之间以JSON对象的形式安全地传输信息。它广泛应用于身份认证和授权。一个JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  • 头部通常包含两部分:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。

  • 载荷包含所要传递的信息。标准中注册的声明和公共的声明可以在此部分添加,例如用户ID(user_id)或角色(role)。

  • 签名用于验证消息在传输过程中没有被更改,并且,对于使用私钥签名的Token,还可以验证发送者的身份。

Spring Security与JWT

Spring Security是一个功能强大且高度可定制的认证和访问控制框架。它为Spring应用程序提供全面的安全服务,包括保护Web应用程序、REST API和微服务。

结合JWT和Spring Security,可以实现无状态的认证机制,其中JWT在客户端和服务器之间传递,用于验证用户的身份。

实现步骤

  1. 添加依赖:在Spring Boot项目的pom.xml文件中添加必要的依赖,包括Spring Security、Spring Data JPA和JWT库。

  2. 配置MySQL数据库:设置数据库并创建相应的用户表和角色表,以及用户角色关联表。

  3. 创建JPA实体:定义用户(User)和角色(Role)的JPA实体,并建立多对多的关系映射。

  4. 创建Spring Data JPA仓库:为用户和角色创建相应的仓库接口。

  5. JWT工具类:实现一个JWT工具类,提供生成和解析JWT的方法。

  6. Spring Security配置:配置Spring Security,定义认证管理器、用户详情服务和安全过滤器链。

  7. 创建DTO类:创建登录DTO和JWT认证响应DTO。

  8. 服务层:实现认证服务,用于处理登录请求和生成JWT。

  9. 控制器层:创建认证控制器,提供登录接口并返回JWT。

  10. SQL脚本:编写SQL脚本,初始化数据库数据。

  11. 测试:使用Postman或其他API测试工具测试登录接口和JWT认证。

示例代码

以下是一些关键组件的示例代码:

JwtTokenProvider.java

复制代码
java

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtTokenProvider {

    @Value("${app.jwt-secret}")
    private String jwtSecret;

    @Value("${app.jwt-expiration-milliseconds}")
    private long jwtExpirationDate;

    public String generateToken(Authentication authentication) {
        String username = authentication.getName();
        Date currentDate = new Date();
        Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUsername(String token) {
        return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parse(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

JwtAuthenticationFilter.java

复制代码
java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtTokenProvider.getUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (jwtTokenProvider.validateToken(jwt)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }
}

SpringSecurityConfig.java

复制代码
java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

总结

通过上述步骤和代码示例,你可以在Spring Boot应用中实现基于JWT的身份认证。这种方式不仅提高了应用的安全性,还增强了系统的可扩展性和性能。确保在实际部署时,使用HTTPS来保护Token的安全传输。

相关推荐
fen_fen1 小时前
用户信息表建表及批量插入 100 条数据(MySQL/Oracle)
数据库·mysql·oracle
JH30738 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
qq_124987075311 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_12 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
2301_8187320612 小时前
前端调用控制层接口,进不去,报错415,类型不匹配
java·spring boot·spring·tomcat·intellij-idea
码字的字节12 小时前
Spring Cloud服务注册与发现(一):手把手搭建Eureka Server,详解高可用配置
spring·spring cloud·eureka
大厂资深架构师12 小时前
Spring Cloud Eureka在后端系统中的服务剔除策略
spring·spring cloud·ai·eureka
汤姆yu15 小时前
基于springboot的尿毒症健康管理系统
java·spring boot·后端
暮色妖娆丶15 小时前
Spring 源码分析 单例 Bean 的创建过程
spring boot·后端·spring
·云扬·16 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql