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的安全传输。

相关推荐
宽广1 小时前
springboot+vue+elementui大文件分片上传
java·vue.js·spring boot·后端·elementui
vonlinee1 小时前
MySQL常用SQL语句(持续更新中)
数据库·sql·mysql
知识分享小能手2 小时前
mysql学习教程,从入门到精通,SQL UNION 运算符(27)
大数据·开发语言·数据库·sql·学习·mysql·数据分析
xs_20122 小时前
MySQL5.7实现分组排序
数据库·sql·mysql
猿~阿峰2 小时前
MySQL
android·mysql·adb
可靠百灵鸟2 小时前
Python操作TXT文本:从入门到精通
数据库·python·mysql
逆流°只是风景-bjhxcc3 小时前
【表分区】MySQL表分区(partition)创建、查询、删除以及重建分区等
数据库·mysql
李小码哥3 小时前
mysql如何快速编写单表查询语句
数据库·mysql
程序员陆通3 小时前
Spring Boot 快速入门教程
spring boot