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

配置permitAll()后访问API仍出现Error 401 Unauthorized问题

Spring Boot 集成Keycloak时匿名访问接口返回401问题

我正在用Keycloak开发Spring Boot项目,已在安全配置中将/users和/products/**设置为允许匿名访问,但用Postman不带JWT token请求/users接口时,却收到401 Unauthorized错误。


相关配置

安全配置类

@Configuration
@EnableMethodSecurity
public class SecurityConfigurations{
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        http.cors().and().csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeHttpRequests()
                .requestMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                .requestMatchers("/users").permitAll()
                .requestMatchers("/products/**").permitAll()
                .anyRequest().authenticated().and()
            .oauth2ResourceServer().jwt().jwtAuthenticationConverter(new ConverterJwt());
        return http.build();
    }
}

JWT转换器类

public class ConverterJwt implements Converter<Jwt, AbstractAuthenticationToken> {
    @Value("${keycloak.client-id}")
    private String client;

    @Override
    @SuppressWarnings("unchecked")
    public AbstractAuthenticationToken convert(final Jwt source) {
        Map<String, Object> resourceAccess = source.getClaim("resource_access");
        Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(client);
        Collection<String> resourceRoles = (Collection<String>) resource.get("roles");
        Set<GrantedAuthority> authorities = resourceRoles.stream()
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toSet());
        return new JwtAuthenticationToken(source, authorities);
    }
}

application.yaml配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/EcommerceDB?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    username: ---
    password: ---
  jpa:
    database-platform: org.hibernate.dialect.MySQLDialect
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8080/realms/EcommerceKeycloack/protocol/openid-connect/certs
          issuer-uri: http://localhost:8080/realms/EcommerceKeycloak
keycloak:
  client-id: ecommerce-keycloack-client
  grant-type: authorization_code
  scope: openid

server:
  port: 8090

pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>it.unical</groupId>
    <artifactId>Ecommerce</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Ecommerce</name>
    <description>Ecommerce</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>8.0.0.Final</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

问题排查与解决办法

1. 修复JWT转换器的Spring托管问题

当前ConverterJwt未被Spring容器管理,直接new会导致@Value无法注入配置值。虽然匿名请求不会触发该转换器,但这会导致认证请求报错,同时可能干扰Security过滤器链初始化。

解决步骤:

  • 给ConverterJwt添加@Component注解,让Spring自动扫描实例化:
@Component
public class ConverterJwt implements Converter<Jwt, AbstractAuthenticationToken> {
    // 原有代码保持不变
}
  • 在SecurityConfigurations中通过构造注入获取ConverterJwt实例,替换手动new的方式:
@Configuration
@EnableMethodSecurity
public class SecurityConfigurations{
    private final ConverterJwt jwtAuthenticationConverter;

    public SecurityConfigurations(ConverterJwt jwtAuthenticationConverter) {
        this.jwtAuthenticationConverter = jwtAuthenticationConverter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        // 原有配置不变,仅替换jwtAuthenticationConverter的引用
        http.cors().and().csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeHttpRequests()
                .requestMatchers(HttpMethod.OPTIONS,"/**").permitAll()
                .requestMatchers("/users").permitAll()
                .requestMatchers("/products/**").permitAll()
                .anyRequest().authenticated().and()
            .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter);
        return http.build();
    }
}

2. 验证请求路径匹配

确认Postman请求路径与配置完全一致:

  • 避免路径末尾带斜杠(如/users/),若需兼容可修改配置为requestMatchers("/users", "/users/").permitAll()
  • 若/users仅处理特定HTTP方法(如POST),需显式指定:requestMatchers(HttpMethod.POST, "/users").permitAll()

3. 显式启用匿名访问

在Spring Security 6.x结合OAuth2资源服务器时,显式配置匿名访问可避免规则冲突:
在filterChain中添加.anonymous():

@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
    http.cors().and().csrf().disable()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeHttpRequests()
            .requestMatchers(HttpMethod.OPTIONS,"/**").permitAll()
            .requestMatchers("/users").permitAll()
            .requestMatchers("/products/**").permitAll()
            .anyRequest().authenticated().and()
        .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter).and()
        .anonymous();
    return http.build();
}

4. 配置CORS规则(可选)

若存在跨域相关问题,可自定义CORS配置确保请求正常通过:
添加CORS配置Bean:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(Collections.singletonList("*")); // 生产环境需替换为具体域名
    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
    config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

然后在filterChain中绑定该配置:

http.cors(cors -> cors.configurationSource(corsConfigurationSource())).and().csrf().disable()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:47:00