feat: disable local auth when OIDC is configured (resource server mode)

- UiAuthController.login returns 404 when OIDC issuer is configured
- JwtAuthenticationFilter skips internal user tokens in OIDC mode (agents still work)
- UserAdminController.createUser and resetPassword return 400 in OIDC mode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-07 23:15:47 +02:00
parent b5e85162f8
commit 7657081b78
3 changed files with 28 additions and 1 deletions

View File

@@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cameleer3.server.app.security.SecurityProperties;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.time.Instant;
@@ -47,12 +48,15 @@ public class UserAdminController {
private final RbacService rbacService;
private final UserRepository userRepository;
private final AuditService auditService;
private final boolean oidcEnabled;
public UserAdminController(RbacService rbacService, UserRepository userRepository,
AuditService auditService) {
AuditService auditService, SecurityProperties securityProperties) {
this.rbacService = rbacService;
this.userRepository = userRepository;
this.auditService = auditService;
String issuer = securityProperties.getOidcIssuerUri();
this.oidcEnabled = issuer != null && !issuer.isBlank();
}
@GetMapping
@@ -78,8 +82,12 @@ public class UserAdminController {
@PostMapping
@Operation(summary = "Create a local user")
@ApiResponse(responseCode = "200", description = "User created")
@ApiResponse(responseCode = "400", description = "Disabled in OIDC mode")
public ResponseEntity<UserDetail> createUser(@RequestBody CreateUserRequest request,
HttpServletRequest httpRequest) {
if (oidcEnabled) {
return ResponseEntity.badRequest().build();
}
String userId = "user:" + request.username();
UserInfo user = new UserInfo(userId, "local",
request.email() != null ? request.email() : "",
@@ -178,10 +186,14 @@ public class UserAdminController {
@PostMapping("/{userId}/password")
@Operation(summary = "Reset user password")
@ApiResponse(responseCode = "204", description = "Password reset")
@ApiResponse(responseCode = "400", description = "Disabled in OIDC mode")
public ResponseEntity<Void> resetPassword(
@PathVariable String userId,
@Valid @RequestBody SetPasswordRequest request,
HttpServletRequest httpRequest) {
if (oidcEnabled) {
return ResponseEntity.badRequest().build();
}
userRepository.setPassword(userId, passwordEncoder.encode(request.password()));
auditService.log("reset_password", AuditCategory.USER_MGMT, userId, null, AuditResult.SUCCESS, httpRequest);
return ResponseEntity.noContent().build();

View File

@@ -74,6 +74,12 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
JwtValidationResult result = jwtService.validateAccessToken(token);
String subject = result.subject();
// In OIDC mode, only accept agent tokens via internal validation.
// User tokens must go through the OIDC decoder path.
if (oidcDecoder != null && subject != null && subject.startsWith("user:")) {
return false;
}
List<String> roles = result.roles();
if (!subject.startsWith("user:") && roles.isEmpty()) {
roles = List.of("AGENT");

View File

@@ -71,6 +71,10 @@ public class UiAuthController {
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
public ResponseEntity<AuthTokenResponse> login(@RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
if (isOidcEnabled()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new AuthTokenResponse(null, null, "Local login disabled when OIDC is configured", null));
}
String configuredUser = properties.getUiUser();
String configuredPassword = properties.getUiPassword();
String subject = "user:" + request.username();
@@ -149,6 +153,11 @@ public class UiAuthController {
}
}
private boolean isOidcEnabled() {
String issuer = properties.getOidcIssuerUri();
return issuer != null && !issuer.isBlank();
}
public record LoginRequest(String username, String password) {}
public record RefreshRequest(String refreshToken) {}
}