Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
Expand All @@ -25,9 +26,12 @@
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;

import java.util.stream.Stream;


@Slf4j
@EnableWebSecurity
Expand Down Expand Up @@ -73,13 +77,11 @@ public SecurityFilterChain defaultSecurityFilterChain(
.requestMatchers("/login/captcha/image")
.permitAll()
.requestMatchers("/actuator/internalTokenKeyStatus")
.permitAll()
// The management port is private to the deployment network. Only the
// aggregate scrape endpoint is exposed to Prometheus without a user token.
.requestMatchers("/actuator/prometheus")
.permitAll()
.hasAuthority(ActuatorMonitoringAccess.AUTHORITY)
.requestMatchers(ActuatorMonitoringAccess.metricPathArray())
.hasAuthority(ActuatorMonitoringAccess.AUTHORITY)
.requestMatchers("/actuator/**")
.hasAuthority("SCOPE_actuator.read")
.requestMatchers(
"/authorizations", "/authorizations/**",
"/authorization-consents", "/authorization-consents/**")
Expand All @@ -96,6 +98,9 @@ public SecurityFilterChain defaultSecurityFilterChain(
BearerTokenAuthenticationFilter.class)
.addFilterBefore(loginCaptchaAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.userDetailsService(userDetailsService);
httpSecurity.exceptionHandling(exceptions -> exceptions.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
PathPatternRequestMatcher.withDefaults().matcher("/actuator/**")));
// 管理台以 DELETE /logout 发起注销,成功后由处理器写入审计日志并返回 204。
httpSecurity.logout(logout -> logout
.logoutRequestMatcher(PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.DELETE, "/logout"))
Expand Down Expand Up @@ -145,8 +150,13 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthoritiesClaimName("roles");
authoritiesConverter.setAuthorityPrefix("");
JwtGrantedAuthoritiesConverter scopeAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt ->
Stream.concat(authoritiesConverter.convert(jwt).stream(),
scopeAuthoritiesConverter.convert(jwt).stream())
.distinct()
.toList());
return authenticationConverter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@ void shouldRestoreAuthoritiesFromJwtRolesClaim() {
.contains("ADMIN", "IT");
}

@Test
void shouldRestoreActuatorScopeWithoutGrantingApplicationRoles() {
Jwt jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.subject("opensabre-prometheus")
.issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(60))
.claim("scope", "actuator.read")
.build();

var authentication = new WebSecurityConfig()
.jwtAuthenticationConverter()
.convert(jwt);

assertThat(authentication).isNotNull();
assertThat(authentication.getAuthorities())
.extracting("authority")
.contains("SCOPE_actuator.read")
.doesNotContain("ADMIN");
}

@Test
void shouldConfigureDaoAuthenticationProvider() {
// Verify that DaoAuthenticationProvider is configured
Expand Down
Loading