配置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
相关产品推荐
相关产品推荐

