文章目录
- 在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证
- 一、引言
- 二、环境搭建
- 1、第一步:引入依赖
- 2、第二步:配置MySQL数据库
- 三、实现身份认证
- 三、实现身份认证
- 1、定义实体和数据访问层
- 1.1、实体类定义
- 1.2、数据访问层
- 2、JWT工具类
- 2.1、JwtTokenProvider类
- 3、Spring Security配置
- 3.1、配置WebSecurityConfigurerAdapter
- 3.2、用户详情服务
- 4、登录和认证接口
- 4.1、登录接口
- 4.2、AuthService类
- 4.3、过滤器
- 四、总结
在 Spring Boot 中使用 Spring Security + JWT + MySQL 实现基于 Token 的身份认证
一、引言
在现代Web应用中,安全是一个核心考虑因素。随着微服务架构和前后端分离模式的流行,传统的会话管理(基于Cookie和Session)已不再满足需求。基于Token的身份认证机制因其无状态、可扩展性高和适用于分布式系统等优点,逐渐成为主流。本文将介绍如何在Spring Boot应用中整合Spring Security、JWT和MySQL,实现基于Token的身份认证机制。
二、环境搭建
1、第一步:引入依赖
在Spring Boot项目中,首先需要引入必要的依赖。以下是pom.xml
中需要添加的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
2、第二步:配置MySQL数据库
创建一个名为login_system
的数据库,并在application.properties
中配置数据库连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/login_system
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
三、实现身份认证
三、实现身份认证
1、定义实体和数据访问层
1.1、实体类定义
首先,定义User
和Role
实体类,并在它们之间建立多对多关系。使用JPA注解来标注这些类和字段。
User实体:
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;import java.util.Set;@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;@Column(nullable = false, unique = true)private String username;@Column(nullable = false, unique = true)private String email;@Column(nullable = false)private String password;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "users_roles",joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))private Set<Role> roles;
}
Role实体:
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;@Entity
@Table(name = "roles")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String name;
}
1.2、数据访问层
为User
和Role
创建相应的JPA仓库接口。
UserRepository接口:
import org.springframework.data.jpa.repository.JpaRepository;import java.util.Optional;public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);Optional<User> findByEmail(String email);Boolean existsByEmail(String email);Boolean existsByUsername(String username);
}
RoleRepository接口:
import org.springframework.data.jpa.repository.JpaRepository;import java.util.Optional;public interface RoleRepository extends JpaRepository<Role, Long> {Optional<Role> findByName(String name);
}
2、JWT工具类
2.1、JwtTokenProvider类
创建JwtTokenProvider
类,用于生成和解析JWT Token。使用java-jwt
库来实现JWT的创建和验证。
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.function.Function;@Component
public class JwtTokenProvider {@Value("${app.jwt-secret}")private String jwtSecret;@Value("${app.jwt-expiration-milliseconds}")private Long jwtExpirationInMs;public String generateToken(String username) {return generateToken(username, null);}public String generateToken(String username, Date expiration) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(expiration != null ? expiration : expirationDate()).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();}public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {final Claims claims = extractAllClaims(token).getBody();return claimsResolver.apply(claims);}private Claims extractAllClaims(String token) {try {return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();} catch (JwtException | IllegalArgumentException e) {throw new IllegalArgumentException("Expired or invalid JWT token");}}public Boolean validateToken(String token) {try {extractAllClaims(token);return true;} catch (Exception e) {return false;}}private Date expirationDate() {return new Date(System.currentTimeMillis() + jwtExpirationInMs);}
}
3、Spring Security配置
3.1、配置WebSecurityConfigurerAdapter
创建一个配置类,继承WebSecurityConfigurerAdapter
,重写方法以配置Spring Security。
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/api/auth/**").permitAll().anyRequest().authenticated().and().addFilter(new JwtAuthenticationFilter(authenticationManager())).addFilter(new JwtAuthorizationFilter(authenticationManager())).exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint());}
}
3.2、用户详情服务
实现UserDetailsService
接口,用于从数据库加载用户信息。
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.stream.Collectors;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),user.getRoles().stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()));}
}
4、登录和认证接口
4.1、登录接口
创建一个登录接口,用户可以通过提供用户名和密码来获取JWT Token。
@RestController
@RequestMapping("/api/auth")
public class AuthController {@Autowiredprivate AuthService authService;@PostMapping("/login")public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthRequest authenticationRequest) {final String jwt = authService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword());return ResponseEntity.ok(new AuthResponse(jwt));}
}
4.2、AuthService类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;@Service
public class AuthService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtTokenProvider jwtTokenProvider;public String login(String username, String password) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));return jwtTokenProvider.generateToken(authentication.getName());}
}
4.3、过滤器
实现两个过滤器,JwtAuthenticationFilter
用于处理登录请求,生成JWT;JwtAuthorizationFilter
用于验证后续请求中的JWT。
JwtAuthenticationFilter:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate AuthenticationManager authenticationManager;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = tokenProvider.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (tokenProvider.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authToken);}}chain.doFilter(request, response);}
}
JwtAuthorizationFilter:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtAuthorizationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {final String authorizationHeader = request.getHeader("Authorization");String username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {jwt = authorizationHeader.substring(7);username = tokenProvider.extractUsername(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (token
Provider.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authToken);}}chain.doFilter(request, response);}
}
这样,我们就完成了基于Token的身份认证机制的实现。用户登录时,系统会生成一个JWT,用户需在随后的请求中携带此JWT进行身份验证。通过这种方式,我们可以确保应用的安全性和可扩展性。
四、总结
通过上述步骤,我们成功在Spring Boot应用中整合了Spring Security、JWT和MySQL,实现了基于Token的身份认证机制。这种机制不仅提高了应用的安全性,还增强了其可扩展性和维护性。在微服务和分布式系统中,基于Token的身份认证是推荐的做法。
版权声明:本博客内容为原创,转载请保留原文链接及作者信息。
参考文章:
- Springdoc - Spring Boot, Spring Security, JWT and MySQL
- CSDN - 【全网最细致】SpringBoot整合Spring Security + JWT实现用户认证