You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合Spring Security、容器安全与JWT实现REST登录服务?

可行性结论

需求完全可实现,核心逻辑是自定义Spring Security认证Provider对接容器内置的用户体系做身份校验,再整合JWT的生成、校验逻辑即可,具体实现步骤如下:

第一步:引入核心依赖

在Spring Boot项目的pom.xml(Gradle配置同理)中引入必要依赖:

<!-- 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>

第二步:自定义容器用户认证Provider

对接容器的用户查询API完成身份校验,以下是Tomcat容器的实现示例,Jetty等其他容器替换对应用户查询接口即可:

@Component
public class ContainerUserAuthProvider implements AuthenticationProvider {
    // 注入Tomcat内置的用户数据库实例,外置Tomcat部署时可通过JNDI获取
    @Autowired
    private UserDatabase userDatabase;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String inputPassword = authentication.getCredentials().toString();
        // 从容器用户库查询用户
        User containerUser = userDatabase.findUser(username);
        if (containerUser == null) {
            throw new BadCredentialsException("用户名不存在");
        }
        // 密码校验,加密规则要和容器配置的密码加密规则一致
        if (!new BCryptPasswordEncoder().matches(inputPassword, containerUser.getPassword())) {
            throw new BadCredentialsException("密码错误");
        }
        // 提取用户角色生成权限列表
        List<GrantedAuthority> authorities = containerUser.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return new UsernamePasswordAuthenticationToken(username, inputPassword, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

第三步:封装JWT工具类

实现JWT的生成、校验、解析逻辑:

@Component
public class JwtUtils {
    // JWT签名密钥,建议配置在application.yml或配置中心
    @Value("${jwt.secret}")
    private String jwtSecret;
    // JWT有效期,默认24小时
    @Value("${jwt.expire-ms:86400000}")
    private long expireMs;

    // 生成JWT Token
    public String generateToken(String username, Collection<? extends GrantedAuthority> authorities) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expireMs))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    // 校验JWT有效性
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }

    // 从JWT解析认证信息
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
        List<String> roles = claims.get("roles", List.class);
        List<GrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
    }
}

第四步:实现登录接口

接收前端传入的用户名密码,完成认证后返回JWT:

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public Map<String, String> login(@RequestBody LoginDTO loginDTO) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginDTO.getUsername(), loginDTO.getPassword())
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token = jwtUtils.generateToken(authentication.getName(), authentication.getAuthorities());
        return Collections.singletonMap("token", token);
    }
}

// LoginDTO结构示例,仅需包含两个字段
// @Data
// public class LoginDTO {
//     private String username;
//     private String password;
// }

第五步:实现JWT校验过滤器

所有业务接口请求先经过该过滤器完成JWT校验:

public class JwtAuthFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = parseTokenFromRequest(request);
        if (token != null && jwtUtils.validateToken(token)) {
            Authentication authentication = jwtUtils.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    // 从请求头提取JWT
    private String parseTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

第六步:Spring Security 规则配置

将自定义的认证Provider和JWT过滤器注册到安全配置中:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ContainerUserAuthProvider containerUserAuthProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 注册自定义容器认证Provider
        auth.authenticationProvider(containerUserAuthProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                // 采用无状态认证,不使用Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/login").permitAll() // 登录接口放行
                .anyRequest().authenticated() // 其余所有接口需要认证
                .and()
                // 将JWT过滤器添加到用户名密码认证过滤器之前
                .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public JwtAuthFilter jwtAuthFilter() {
        return new JwtAuthFilter();
    }
}

第七步:前端适配

React端登录页发起POST请求到/auth/login,携带JSON格式的用户名密码,拿到返回的token后存储在localStorage/sessionStorage中,后续所有业务请求的请求头都携带Authorization: Bearer <token>即可。

注意事项

  • 容器侧的用户配置(如Tomcat的tomcat-users.xml)要保证用户名、密码、角色配置正确,密码加密规则和代码中的密码编码器保持一致。
  • JWT签名密钥生产环境禁止硬编码,建议通过环境变量或配置中心注入。
  • 可根据业务需求补充JWT刷新、过期重试逻辑,不影响核心流程。

内容的提问来源于stack exchange,提问作者Benno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 01:36:00