Spring Boot 3实现登录/注册后生成安全令牌的方案及示例咨询
Spring Boot 3实现登录/注册后生成安全令牌的方案及示例咨询
嗨,我来给你详细拆解在Spring Boot 3中实现登录/注册后生成安全令牌的方案,咱们用最常用的JWT(JSON Web Token)来做示例,步骤清晰,代码直接就能跑起来~
一、先准备依赖
首先得把需要的依赖加到项目里,Maven的pom.xml里加这些:
<!-- Spring Security 核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JJWT 用于JWT生成与解析 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
二、编写JWT工具类
这个类负责生成令牌、解析令牌、从令牌里提取用户信息,咱们写个实用的工具类:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtil { // 密钥,实际项目里要存在配置文件或者密钥管理服务里,别硬编码! private final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); // 令牌有效期,这里设为1小时 private final long JWT_EXPIRATION = 3600000; // 从令牌中提取用户名 public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } // 从令牌中提取过期时间 public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } // 通用提取方法 public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody(); } // 检查令牌是否过期 private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } // 生成令牌 public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) .signWith(SECRET_KEY, SignatureAlgorithm.HS256) .compact(); } // 验证令牌有效性 public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
三、自定义用户实体类
实现Spring Security的UserDetails接口,用来封装用户信息:
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; public class CustomUserDetails implements UserDetails { private final String username; private final String password; private final List<GrantedAuthority> authorities; public CustomUserDetails(String username, String password, List<GrantedAuthority> authorities) { this.username = username; this.password = password; this.authorities = authorities; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
四、实现UserDetailsService
这个类负责根据用户名获取用户信息,示例里咱们用内存存储,实际项目里替换成数据库查询就行:
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class CustomUserDetailsService implements UserDetailsService { // 模拟数据库存储用户 private final Map<String, String> userStore = new HashMap<>(); private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); public CustomUserDetailsService() { // 初始化一个测试用户,密码是123456 userStore.put("test", passwordEncoder.encode("123456")); } // 注册用户的方法,实际项目里要加参数校验 public void registerUser(String username, String password) { if (userStore.containsKey(username)) { throw new RuntimeException("用户名已存在"); } userStore.put(username, passwordEncoder.encode(password)); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String password = userStore.get(username); if (password == null) { throw new UsernameNotFoundException("用户不存在"); } // 给用户加一个默认的角色,实际可以根据业务调整 List<SimpleGrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new CustomUserDetails(username, password, authorities); } }
五、编写JWT认证过滤器
这个过滤器会在每次请求时检查请求头里的JWT令牌,验证通过后设置认证信息:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final CustomUserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, CustomUserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头里获取Authorization字段 String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; // 格式是Bearer <token>,所以要拆分 if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtil.extractUsername(jwt); } // 如果用户名存在且当前没有认证信息 if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 验证令牌有效性 if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); } }
六、配置Spring Security
编写Security配置类,关闭csrf、配置过滤器链、指定认证管理器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomUserDetailsService userDetailsService; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomUserDetailsService userDetailsService) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.userDetailsService = userDetailsService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 关闭csrf,因为JWT不需要session .csrf(csrf -> csrf.disable()) // 配置请求权限:注册和登录接口允许匿名访问,其他接口需要认证 .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/register", "/api/auth/login").permitAll() .anyRequest().authenticated()) // 无状态会话,因为JWT是无状态的 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 把JWT过滤器加到UsernamePasswordAuthenticationFilter前面 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
七、编写登录/注册控制器
最后写个控制器,提供注册和登录的接口:
import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/auth") public class AuthController { private final AuthenticationManager authenticationManager; private final CustomUserDetailsService userDetailsService; private final JwtUtil jwtUtil; public AuthController(AuthenticationManager authenticationManager, CustomUserDetailsService userDetailsService, JwtUtil jwtUtil) { this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; this.jwtUtil = jwtUtil; } // 注册接口 @PostMapping("/register") public ResponseEntity<String> register(@RequestBody RegisterRequest request) { try { userDetailsService.registerUser(request.getUsername(), request.getPassword()); return ResponseEntity.ok("注册成功"); } catch (RuntimeException e) { return ResponseEntity.badRequest().body(e.getMessage()); } } // 登录接口 @PostMapping("/login") public ResponseEntity<String> login(@RequestBody LoginRequest request) { // 验证用户名密码 authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) ); // 获取用户信息并生成令牌 final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); final String jwt = jwtUtil.generateToken(userDetails); return ResponseEntity.ok(jwt); } // 内部请求类,用来接收参数 public static class RegisterRequest { private String username; private String password; // getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } public static class LoginRequest { private String username; private String password; // getters and setters public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } }
测试一下
- 启动项目,用Postman或者curl调用注册接口:
POST http://localhost:8080/api/auth/register,请求体是{"username":"myuser","password":"mypassword"} - 调用登录接口:
POST http://localhost:8080/api/auth/login,请求体是{"username":"myuser","password":"mypassword"},会返回一个JWT令牌 - 调用需要认证的接口时,在请求头里加
Authorization: Bearer <刚才拿到的令牌>,就能正常访问了
整个流程就是这样,实际项目里你可以把内存存储换成数据库,密钥放到配置文件里,还可以根据需求添加更多的Claims(比如用户ID、角色),调整令牌有效期之类的~
备注:内容来源于stack exchange,提问作者Leonardo Cerchioni
相关产品推荐
相关产品推荐

