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

Spring Boot中跳过认证仅实现JWT角色授权方案咨询

实现Spring Boot REST接口的纯授权逻辑(跳过认证)

核心思路

因为认证由外部系统完成,我们只需要:

  1. 从JWT中提取角色信息,转换为Spring Security能识别的权限格式
  2. 将权限信息注入到SecurityContext中
  3. 通过@PreAuthorize注解实现接口的角色访问控制
  4. 关闭Spring Security的默认认证流程,仅保留授权校验

步骤1:开启方法级安全注解

创建Spring Security配置类,开启@PreAuthorize等方法注解支持:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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
@EnableMethodSecurity(prePostEnabled = true) // 开启方法级权限注解
public class SecurityConfig {

    private final JwtRoleExtractionFilter jwtRoleExtractionFilter;

    // 构造注入自定义过滤器
    public SecurityConfig(JwtRoleExtractionFilter jwtRoleExtractionFilter) {
        this.jwtRoleExtractionFilter = jwtRoleExtractionFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // REST接口关闭CSRF防护
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态会话
            .authorizeHttpRequests(auth -> auth
                .anyRequest().permitAll() // 允许所有请求进入,授权交给方法注解处理
            )
            .addFilterBefore(jwtRoleExtractionFilter, UsernamePasswordAuthenticationFilter.class); // 加入角色提取过滤器

        return http.build();
    }
}

步骤2:自定义JWT角色提取过滤器

实现过滤器,从JWT中解析角色并注入SecurityContext:

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class JwtRoleExtractionFilter extends OncePerRequestFilter {

    private static final String USER_PROFILE = "user_profile";

    // 从请求头解析JWT并获取claims(替换为你实际的JWT解析逻辑)
    private Claims extractClaimsFromRequest(HttpServletRequest request) {
        String token = request.getHeader("Authorization").replace("Bearer ", "");
        return Jwts.parser().setSigningKey("your-jwt-secret").parseClaimsJws(token).getBody();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            Claims claims = extractClaimsFromRequest(request);
            Optional<Profile> profileOpt = Optional.ofNullable(claims.get(USER_PROFILE, Profile.class));

            if (profileOpt.isPresent()) {
                Profile profile = profileOpt.get();
                // 转换角色为Spring Security格式:必须加ROLE_前缀,因为hasRole会自动匹配前缀
                Collection<GrantedAuthority> authorities = profile.getRoles().stream()
                        .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                        .collect(Collectors.toList());

                // 创建Authentication对象(用AnonymousAuthenticationToken,因为不需要认证信息)
                Authentication authentication = new AnonymousAuthenticationToken(
                        "security-key",
                        "external-authenticated-user",
                        authorities
                );

                // 将权限注入SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            // JWT解析失败时清空上下文,后续接口会返回403
            SecurityContextHolder.clearContext();
        }

        filterChain.doFilter(request, response);
    }
}

步骤3:Profile类示例

public class Profile {
    private List<String> roles;

    // getter/setter
    public List<String> getRoles() {
        return roles;
    }

    public void setRoles(List<String> roles) {
        this.roles = roles;
    }
}

步骤4:控制器中使用@PreAuthorize

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;

@RestController
public class ApiController {

    @GetMapping("/user-resource")
    @PreAuthorize("hasRole('USER')")
    public String userResource() {
        return "仅USER角色可访问";
    }

    @GetMapping("/common-resource")
    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public String commonResource() {
        return "USER或ADMIN角色可访问";
    }

    @GetMapping("/admin-resource")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminResource() {
        return "仅ADMIN角色可访问";
    }
}

关键注意事项

  • 角色前缀:hasRole/hasAnyRole会自动给角色名添加ROLE_前缀匹配,所以转换权限时必须手动加上该前缀
  • 过滤器顺序:必须将自定义过滤器放在UsernamePasswordAuthenticationFilter之前,确保权限先被注入
  • 异常处理:JWT解析失败时清空SecurityContext,此时访问受保护接口会返回403未授权

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:55:15