SpringBoot无认证场景下REST API的Bearer Token实现方案咨询
在SpringBoot中直接生成并验证Bearer Token保护无登录API
完全可以跳过用户登录流程,直接生成Bearer Token供客户端使用,通过Spring Security实现Token验证逻辑。以下是几种实用方案:
方案1:预生成固定Token(适合固定客户端调用)
实现步骤
添加依赖
在pom.xml中引入Spring Security依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>配置固定Token
在application.yml中定义合法Token:app: api-token: "your-secure-fixed-token-12345"编写Security配置类
配置拦截规则并添加自定义Token验证过滤器:import org.springframework.beans.factory.annotation.Value; 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.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { @Value("${app.api-token}") private String validToken; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .addFilterBefore(new BearerTokenFilter(validToken), UsernamePasswordAuthenticationFilter.class); return http.build(); } }自定义Token验证过滤器
提取请求头中的Token并与配置值对比: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.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; public class BearerTokenFilter extends OncePerRequestFilter { private final String validToken; public BearerTokenFilter(String validToken) { this.validToken = validToken; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); if (validToken.equals(token)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( "api-client", null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authToken); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的Bearer Token"); return; } } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要携带Bearer Token"); return; } filterChain.doFilter(request, response); } }
方案2:动态生成临时Token(适合临时访问场景)
如果需要生成带过期时间的临时Token,可以结合Redis存储Token状态:
添加Redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>编写Token生成接口
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; import java.util.concurrent.TimeUnit; @RestController public class TokenController { private final StringRedisTemplate redisTemplate; public TokenController(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @GetMapping("/api/generate-token") public String generateToken() { String token = UUID.randomUUID().toString(); // 设置1小时过期时间 redisTemplate.opsForValue().set("api-token:" + token, "valid", 1, TimeUnit.HOURS); return "Bearer " + token; } }修改过滤器验证逻辑
从Redis中检查Token是否存在且有效:import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; import java.util.concurrent.TimeUnit; public class DynamicBearerTokenFilter extends OncePerRequestFilter { private final StringRedisTemplate redisTemplate; public DynamicBearerTokenFilter(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); String key = "api-token:" + token; if (redisTemplate.hasKey(key)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( "api-client", null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authToken); // 刷新Token过期时间 redisTemplate.expire(key, 1, TimeUnit.HOURS); } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token无效或已过期"); return; } } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要携带Bearer Token"); return; } filterChain.doFilter(request, response); } }
方案3:JWT无状态Token(适合分布式场景)
用JWT生成自包含Token,无需存储,直接验证签名和过期时间:
添加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参数
app: jwt-secret: "your-strong-jwt-secret-key-32-chars-minimum" jwt-expiration-ms: 3600000 # 1小时生成JWT接口
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; @RestController public class JwtTokenController { @Value("${app.jwt-secret}") private String jwtSecret; @Value("${app.jwt-expiration-ms}") private long jwtExpirationMs; @GetMapping("/api/generate-jwt") public String generateJwtToken() { return Jwts.builder() .setSubject("api-client") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } }JWT验证过滤器
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import javax.crypto.SecretKey; import java.io.IOException; import java.util.Collections; public class JwtValidationFilter extends OncePerRequestFilter { @Value("${app.jwt-secret}") private String jwtSecret; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( "api-client", null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authToken); } catch (Exception e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT无效或已过期"); return; } } else { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要携带Bearer Token"); return; } filterChain.doFilter(request, response); } }
注意事项
- 固定Token适合信任度高的固定客户端,需严格保密Token内容
- 动态Token和JWT更适合临时或多客户端场景,能有效降低泄露风险
- 生产环境务必使用HTTPS传输Token,避免明文泄露
内容的提问来源于stack exchange,提问作者RVD
相关产品推荐
相关产品推荐

