在Spring Boot中编写基于JWT(JSON Web Token)的用户登录和注册功能,同时结合Spring Security和Redis来实现安全认证和令牌管理是一项复杂的任务。下面是一个基本的步骤和示例代码,可以帮助你入门。
# 步骤1: 创建Spring Boot项目
首先,创建一个Spring Boot项目并配置所需的依赖。确保在pom.xml
文件中添加以下依赖项:
<dependencies>
<!-- Hutool的JWT工具集 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-jwt</artifactId>
<version>5.8.22</version>
</dependency>
<!-- jjwt:JSON Web令牌工具 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- spring-boot-starter-security:鉴权认证框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.2</version>
</dependency>
</dependencies>
# 步骤2: 配置Spring Security
创建一个SecurityConfig
类来配置Spring Security,并启用基本的身份验证和授权:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
# 步骤3: 创建用户实体和存储库
创建一个用户实体类,以及一个用于访问用户数据的存储库:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
// getters and setters
}
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
# 步骤4: 创建用户注册和登录控制器
创建一个控制器类来处理用户的注册和登录请求,并生成JWT令牌:
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
public class AuthController {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expirationMs}")
private Long jwtExpirationMs;
@PostMapping("/register")
public String register(@RequestBody UserRequest userRequest) {
if (StrUtil.isEmpty(userRequest.getUsername()) || StrUtil.isEmpty(userRequest.getPassword())) {
return "Username and password are required!";
}
if (userRepository.findByUsername(userRequest.getUsername()) != null) {
return "Username is already taken!";
}
User user = new User();
user.setUsername(userRequest.getUsername());
user.setPassword(passwordEncoder.encode(userRequest.getPassword()));
userRepository.save(user);
return "Registration successful!";
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody UserRequest userRequest) {
User user = userRepository.findByUsername(userRequest.getUsername());
if (user == null || !passwordEncoder.matches(userRequest.getPassword(), user.getPassword())) {
throw new BadCredentialsException("Invalid username or password");
}
String token = generateToken(user.getUsername());
Map<String, String> response = new HashMap<>();
response.put("token", token);
return response;
}
private String generateToken(String username) {
Date expirationDate = new Date(System.currentTimeMillis() + jwtExpirationMs);
return Jwts.builder()
.setSubject(username)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
}
# 步骤5: 配置JWT和Redis
在application.properties
或application.yml
中配置JWT和Redis相关属性:
# JWT Configuration
jwt.secret=YourSecretKey
jwt.expirationMs=86400000 # 1 day in milliseconds
# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
# 步骤6: 运行应用程序
现在,你可以运行你的Spring Boot应用程序并测试注册和登录功能。注册用户后,将获得JWT令牌,可以在后续的请求中使用该令牌进行身份验证。
请注意,这只是一个基本的示例,实际生产环境中需要更多的安全措施,例如错误处理、身份验证错误处理、令牌刷新等。同时,应该保护/login
和/register
端点,以防止恶意攻击。
要在登录或注册成功后将JWT令牌存入Redis,并在调用其他接口时检查请求头中的令牌与Redis中的令牌是否匹配,可以按照以下步骤进行操作:
# 步骤1: 在登录成功后将JWT令牌存入Redis
在登录成功后,将生成的JWT令牌存入Redis中,以便后续的访问进行验证。可以在AuthController
中的login
方法中添加以下代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
// ...
@Autowired
private RedisTemplate<String, String> redisTemplate;
// ...
@PostMapping("/login")
public Map<String, String> login(@RequestBody UserRequest userRequest) {
// ...
String token = generateToken(user.getUsername());
// 存储令牌到Redis
redisTemplate.opsForValue().set(user.getUsername(), token);
Map<String, String> response = new HashMap<>();
response.put("token", token);
return response;
}
# 步骤2: 在其他接口中验证JWT令牌
创建一个拦截器或过滤器来验证请求头中的令牌是否与Redis中的令牌匹配。首先,创建一个JwtTokenFilter
类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.server.ResponseStatusException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Value("${jwt.secret}")
private String jwtSecret;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String token = extractToken(request);
if (token != null) {
String username = getUsernameFromToken(token);
// 从Redis中获取存储的令牌
String storedToken = redisTemplate.opsForValue().get(username);
// 验证请求头中的令牌是否与Redis中的令牌匹配
if (token.equals(storedToken)) {
SecurityContextHolder.getContext().setAuthentication(new JwtAuthentication(username));
} else {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token");
}
}
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token");
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
// 从请求头中提取令牌
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
private String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
}
# 步骤3: 注册拦截器
将JwtTokenFilter
注册为Spring Bean,以便它能够拦截请求并验证JWT令牌。在你的应用程序主类(通常带有@SpringBootApplication
注解的类)中添加以下代码:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
@Bean
public JwtTokenFilter jwtTokenFilter() {
return new JwtTokenFilter();
}
}
# 步骤4: 配置Spring Security
为了使JwtTokenFilter
生效,你需要配置Spring Security以跳过对特定路径的验证。在SecurityConfig
中添加以下代码:
import org.springframework.security.config.annotation.web.builders.WebSecurity;
// ...
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略对特定路径的验证
web.ignoring().antMatchers("/register", "/login");
}
这将使Spring Security跳过对注册和登录路径的验证,但其他路径仍然会进行验证。
现在,你已经完成了JWT令牌存储和验证的配置。在调用其他需要身份验证的接口时,请求头中的令牌将与Redis中的令牌进行对比,以确保令牌的有效性。如果令牌无效,将返回401 Unauthorized响应。