文章目录
在 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 {
@Override
protected 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 {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public 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 {
@Autowired
private 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 {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private 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 {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Override
protected 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 {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected 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的身份认证是推荐的做法。
版权声明:本博客内容为原创,转载请保留原文链接及作者信息。
参考文章: