
关于
Quarkus 安全最佳实践,涵盖认证、授权、JWT/OIDC、RBAC、输入验证、CSRF、密钥管理和依赖安全。
name: quarkus-security description: Quarkus安全最佳实践,涵盖认证、授权、JWT/OIDC、RBAC、输入验证、CSRF、密钥管理和依赖安全。 origin: ECC
Quarkus 安全审查
使用认证、授权和输入验证保护Quarkus应用的最佳实践。
何时激活
- 添加认证(JWT、OIDC、Basic Auth)
- 使用 @RolesAllowed 或 SecurityIdentity 实现授权
- 验证用户输入(Bean Validation、自定义验证器)
- 配置CORS或安全头
- 管理密钥(Vault、环境变量、配置源)
- 添加速率限制或暴力破解防护
- 扫描依赖中的CVE
- 使用MicroProfile JWT或SmallRye JWT
认证
JWT认证
// Resource protected with JWT
@Path("/api/protected")
@Authenticated
public class ProtectedResource {
@Inject
JsonWebToken jwt;
@Inject
SecurityIdentity securityIdentity;
@GET
public Response getData() {
String username = jwt.getName();
Set<String> roles = jwt.getGroups();
return Response.ok(Map.of(
"username", username,
"roles", roles,
"principal", securityIdentity.getPrincipal().getName()
)).build();
}
}
配置(application.properties):
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://auth.example.com
# OIDC
quarkus.oidc.auth-server-url=https://auth.example.com/realms/myrealm
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=${OIDC_SECRET}
自定义认证过滤器
@Provider
@Priority(Priorities.AUTHENTICATION)
public class CustomAuthFilter implements ContainerRequestFilter {
@Inject
SecurityIdentity identity;
@Override
public void filter(ContainerRequestContext requestContext) {
String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Reject immediately if header is absent or malformed
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
return;
}
String token = authHeader.substring(7);
if (!validateToken(token)) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private boolean validateToken(String token) {
// Token validation logic
return true;
}
}
授权
基于角色的访问控制
@Path("/api/admin")
@RolesAllowed("ADMIN")
public class AdminResource {
@GET
@Path("/users")
public List<UserDto> listUsers() {
return userService.findAll();
}
@DELETE
@Path("/users/{id}")
@RolesAllowed({"ADMIN", "SUPER_ADMIN"})
public Response deleteUser(@PathParam("id") Long id) {
userService.delete(id);
return Response.noContent().build();
}
}
@Path("/api/users")
public class UserResource {
@Inject
SecurityIdentity securityIdentity;
@GET
@Path("/{id}")
@RolesAllowed("USER")
public Response getUser(@PathParam("id") Long id) {
// Check ownership
if (!securityIdentity.hasRole("ADMIN") &&
!isOwner(id, securityIdentity.getPrincipal().getName())) {
return Response.status(Response.Status.FORBIDDEN).build();
}
return Response.ok(userService.findById(id)).build();
}
private boolean isOwner(Long userId, String username) {
return userService.isOwner(userId, username);
}
}
编程式安全
@ApplicationScoped
public class SecurityService {
@Inject
SecurityIdentity securityIdentity;
public boolean canAccessResource(Long resourceId) {
if (securityIdentity.isAnonymous()) {
return false;
}
if (securityIdentity.hasRole("ADMIN")) {
return true;
}
String userId = securityIdentity.getPrincipal().getName();
return resourceRepository.isOwner(resourceId, userId);
}
}
输入验证
Bean Validation
// BAD: No validation
@POST
public Response createUser(UserDto dto) {
return Response.ok(userService.create(dto)).build();
}
// GOOD: Validated DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@NotNull @Min(18) @Max(150) Integer age,
@Pattern(regexp = "^\\+?[1-9]\\d{1,14}$") String phone
) {}
@POST
@Path("/users")
public Response createUser(@Valid CreateUserDto dto) {
User user = userService.create(dto);
return Response.status(Response.Status.CREATED).entity(user).build();
}
自定义验证器
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UsernameValidator.class)
public @interface ValidUsername {
String message() default "Invalid username format";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class UsernameValidator implements ConstraintValidator<ValidUsername, String> {
private static final Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{3,30}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true;
return USERNAME_PATTERN.matcher(value).matches();
}
}
安全头和CORS
# CORS配置
quarkus.http.cors=true
quarkus.http.cors.origins=https://myapp.example.com
quarkus.http.cors.methods=GET,POST,PUT,DELETE
quarkus.http.cors.headers=Content-Type,Authorization
quarkus.http.cors.exposed-headers=X-Custom-Header
quarkus.http.cors.access-control-max-age=24H
# 安全头
quarkus.http.header."X-Content-Type-Options".value=nosniff
quarkus.http.header."X-Frame-Options".value=DENY
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
密钥管理
# 永远不要硬编码密钥 - 使用环境变量
quarkus.datasource.password=${DB_PASSWORD}
quarkus.oidc.credentials.secret=${OIDC_SECRET}
# 或使用HashiCorp Vault
quarkus.vault.url=https://vault.example.com
quarkus.vault.authentication.userpass.username=${VAULT_USER}
quarkus.vault.authentication.userpass.password=${VAULT_PASS}
依赖安全
定期扫描依赖中的已知漏洞:
# 使用OWASP依赖检查
mvn org.owasp:dependency-check-maven:check
# 或使用Snyk
snyk test
速率限制
@Path("/api/auth")
public class AuthResource {
@Inject
RateLimiter rateLimiter;
@POST
@Path("/login")
public Response login(LoginDto dto) {
if (!rateLimiter.tryAcquire(dto.email())) {
return Response.status(429)
.header("Retry-After", "60")
.build();
}
return authService.authenticate(dto);
}
}
安全检查清单
- 所有端点都有适当的认证/授权注解
- 用户输入通过Bean Validation验证
- 密钥通过环境变量或Vault管理
- CORS配置限制为已知来源
- 安全头已配置
- 依赖定期扫描CVE
- 速率限制保护认证端点
- 日志不包含敏感数据
兼容工具
Claude CodeCursor
标签
安全
