6.1 Quarkus 安全架构概述

6.1.1 安全架构图

graph TB
    A[客户端请求] --> B[安全过滤器]
    B --> C{认证检查}
    C -->|未认证| D[认证处理器]
    C -->|已认证| E{授权检查}
    D --> F[身份提供者]
    F --> G[用户存储]
    E -->|授权成功| H[业务逻辑]
    E -->|授权失败| I[拒绝访问]
    H --> J[响应]
    
    subgraph "安全扩展"
        K[quarkus-security]
        L[quarkus-security-jpa]
        M[quarkus-oidc]
        N[quarkus-jwt]
        O[quarkus-elytron-security]
    end
    
    subgraph "认证方式"
        P[Basic Auth]
        Q[JWT Token]
        R[OAuth2/OIDC]
        S[Form Auth]
        T[Certificate Auth]
    end

6.1.2 核心安全扩展

<!-- 核心安全扩展 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security</artifactId>
</dependency>

<!-- JPA 安全扩展 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security-jpa</artifactId>
</dependency>

<!-- JWT 支持 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>

<!-- OIDC 支持 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId>
</dependency>

<!-- Elytron 安全 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>

<!-- 密码哈希 -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-common</artifactId>
</dependency>

6.2 基础认证与授权

6.2.1 用户实体和角色模型

package com.example.security.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Entity
@Table(name = "users")
@UserDefinition
public class User extends PanacheEntityBase {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    
    @Username
    @Column(unique = true, nullable = false)
    @NotBlank
    @Size(min = 3, max = 50)
    public String username;
    
    @Password
    @Column(nullable = false)
    @NotBlank
    public String password;
    
    @Column(unique = true, nullable = false)
    @Email
    @NotBlank
    public String email;
    
    @Column(name = "first_name")
    @Size(max = 50)
    public String firstName;
    
    @Column(name = "last_name")
    @Size(max = 50)
    public String lastName;
    
    @Column(name = "phone_number")
    @Pattern(regexp = "^[+]?[0-9]{10,15}$")
    public String phoneNumber;
    
    @Column(name = "is_active")
    public boolean isActive = true;
    
    @Column(name = "is_email_verified")
    public boolean isEmailVerified = false;
    
    @Column(name = "email_verification_token")
    public String emailVerificationToken;
    
    @Column(name = "password_reset_token")
    public String passwordResetToken;
    
    @Column(name = "password_reset_expires")
    public LocalDateTime passwordResetExpires;
    
    @Column(name = "last_login")
    public LocalDateTime lastLogin;
    
    @Column(name = "failed_login_attempts")
    public int failedLoginAttempts = 0;
    
    @Column(name = "account_locked_until")
    public LocalDateTime accountLockedUntil;
    
    @Column(name = "created_at")
    public LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    public LocalDateTime updatedAt;
    
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    @Roles
    public Set<Role> roles = new HashSet<>();
    
    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
        updatedAt = LocalDateTime.now();
    }
    
    @PreUpdate
    public void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    // 获取所有权限
    public Set<String> getAllPermissions() {
        return roles.stream()
            .flatMap(role -> role.permissions.stream())
            .map(permission -> permission.name)
            .collect(Collectors.toSet());
    }
    
    // 检查是否有特定权限
    public boolean hasPermission(String permissionName) {
        return getAllPermissions().contains(permissionName);
    }
    
    // 检查是否有特定角色
    public boolean hasRole(String roleName) {
        return roles.stream().anyMatch(role -> role.name.equals(roleName));
    }
    
    // 检查账户是否被锁定
    public boolean isAccountLocked() {
        return accountLockedUntil != null && accountLockedUntil.isAfter(LocalDateTime.now());
    }
    
    // 重置失败登录次数
    public void resetFailedLoginAttempts() {
        this.failedLoginAttempts = 0;
        this.accountLockedUntil = null;
    }
    
    // 增加失败登录次数
    public void incrementFailedLoginAttempts() {
        this.failedLoginAttempts++;
        if (this.failedLoginAttempts >= 5) {
            this.accountLockedUntil = LocalDateTime.now().plusMinutes(30);
        }
    }
}

@Entity
@Table(name = "roles")
public class Role extends PanacheEntityBase {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    
    @Column(unique = true, nullable = false)
    @NotBlank
    @Size(min = 2, max = 50)
    public String name;
    
    @Column(length = 500)
    public String description;
    
    @Column(name = "is_system_role")
    public boolean isSystemRole = false;
    
    @Column(name = "created_at")
    public LocalDateTime createdAt;
    
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    public Set<Permission> permissions = new HashSet<>();
    
    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
    }
}

@Entity
@Table(name = "permissions")
public class Permission extends PanacheEntityBase {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;
    
    @Column(unique = true, nullable = false)
    @NotBlank
    @Size(min = 2, max = 100)
    public String name;
    
    @Column(length = 500)
    public String description;
    
    @Column(name = "resource_type")
    public String resourceType;
    
    @Column(name = "action_type")
    public String actionType;
    
    @Column(name = "created_at")
    public LocalDateTime createdAt;
    
    @PrePersist
    public void prePersist() {
        createdAt = LocalDateTime.now();
    }
}

6.2.2 安全配置

# 基础安全配置
quarkus.security.users.embedded.enabled=false
quarkus.security.users.file.enabled=false

# 密码哈希配置
quarkus.security.users.embedded.algorithm=BCrypt
quarkus.security.users.embedded.hash-charset=UTF-8

# HTTP 安全配置
quarkus.http.auth.basic=false
quarkus.http.auth.form.enabled=false
quarkus.http.auth.session.encryption-key=changeit

# CORS 配置
quarkus.http.cors=true
quarkus.http.cors.origins=http://localhost:3000,https://app.example.com
quarkus.http.cors.methods=GET,PUT,POST,DELETE,OPTIONS
quarkus.http.cors.headers=accept,authorization,content-type,x-requested-with
quarkus.http.cors.exposed-headers=location,info
quarkus.http.cors.access-control-max-age=86400
quarkus.http.cors.access-control-allow-credentials=true

# 安全头配置
quarkus.http.header."X-Frame-Options".value=DENY
quarkus.http.header."X-Content-Type-Options".value=nosniff
quarkus.http.header."X-XSS-Protection".value=1; mode=block
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
quarkus.http.header."Content-Security-Policy".value=default-src 'self'

# 会话配置
quarkus.http.auth.session.timeout=30M
quarkus.http.auth.session.cookie-name=QUARKUS_SESSION
quarkus.http.auth.session.cookie-path=/
quarkus.http.auth.session.cookie-same-site=strict
quarkus.http.auth.session.cookie-http-only=true
quarkus.http.auth.session.cookie-secure=true

# 速率限制配置
quarkus.security.rate-limit.enabled=true
quarkus.security.rate-limit.requests-per-minute=60
quarkus.security.rate-limit.burst-size=10

6.2.3 认证服务实现

package com.example.security.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.smallrye.mutiny.Uni;
import com.example.security.entity.User;
import com.example.security.entity.Role;
import com.example.security.exception.*;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;

@ApplicationScoped
public class AuthenticationService {
    
    @Inject
    UserRepository userRepository;
    
    @Inject
    EmailService emailService;
    
    @Inject
    SecurityAuditService auditService;
    
    @Transactional
    public Uni<User> authenticate(String username, String password) {
        return userRepository.findByUsername(username)
            .onItem().transformToUni(userOpt -> {
                if (userOpt.isEmpty()) {
                    auditService.logAuthenticationFailure(username, "User not found");
                    return Uni.createFrom().failure(new AuthenticationException("Invalid credentials"));
                }
                
                User user = userOpt.get();
                
                // 检查账户状态
                if (!user.isActive) {
                    auditService.logAuthenticationFailure(username, "Account inactive");
                    return Uni.createFrom().failure(new AccountInactiveException("Account is inactive"));
                }
                
                if (user.isAccountLocked()) {
                    auditService.logAuthenticationFailure(username, "Account locked");
                    return Uni.createFrom().failure(new AccountLockedException("Account is locked"));
                }
                
                // 验证密码
                if (!BcryptUtil.matches(password, user.password)) {
                    user.incrementFailedLoginAttempts();
                    userRepository.persist(user);
                    auditService.logAuthenticationFailure(username, "Invalid password");
                    return Uni.createFrom().failure(new AuthenticationException("Invalid credentials"));
                }
                
                // 认证成功
                user.resetFailedLoginAttempts();
                user.lastLogin = LocalDateTime.now();
                userRepository.persist(user);
                
                auditService.logAuthenticationSuccess(username);
                return Uni.createFrom().item(user);
            });
    }
    
    @Transactional
    public Uni<User> register(RegisterRequest request) {
        return userRepository.findByUsername(request.username)
            .onItem().transformToUni(existingUser -> {
                if (existingUser.isPresent()) {
                    return Uni.createFrom().failure(new UserAlreadyExistsException("Username already exists"));
                }
                
                return userRepository.findByEmail(request.email)
                    .onItem().transformToUni(existingEmail -> {
                        if (existingEmail.isPresent()) {
                            return Uni.createFrom().failure(new UserAlreadyExistsException("Email already exists"));
                        }
                        
                        // 创建新用户
                        User user = new User();
                        user.username = request.username;
                        user.email = request.email;
                        user.password = BcryptUtil.bcryptHash(request.password);
                        user.firstName = request.firstName;
                        user.lastName = request.lastName;
                        user.phoneNumber = request.phoneNumber;
                        user.emailVerificationToken = UUID.randomUUID().toString();
                        
                        // 分配默认角色
                        return Role.find("name", "USER")
                            .firstResult()
                            .onItem().transformToUni(defaultRole -> {
                                if (defaultRole != null) {
                                    user.roles.add((Role) defaultRole);
                                }
                                
                                return userRepository.persist(user)
                                    .onItem().transformToUni(savedUser -> {
                                        // 发送验证邮件
                                        return emailService.sendEmailVerification(savedUser)
                                            .onItem().transform(ignored -> {
                                                auditService.logUserRegistration(savedUser.username);
                                                return savedUser;
                                            });
                                    });
                            });
                    });
            });
    }
    
    @Transactional
    public Uni<Void> changePassword(String username, ChangePasswordRequest request) {
        return userRepository.findByUsername(username)
            .onItem().transformToUni(userOpt -> {
                if (userOpt.isEmpty()) {
                    return Uni.createFrom().failure(new UserNotFoundException("User not found"));
                }
                
                User user = userOpt.get();
                
                // 验证当前密码
                if (!BcryptUtil.matches(request.currentPassword, user.password)) {
                    auditService.logPasswordChangeFailure(username, "Invalid current password");
                    return Uni.createFrom().failure(new AuthenticationException("Invalid current password"));
                }
                
                // 验证新密码强度
                if (!isPasswordStrong(request.newPassword)) {
                    return Uni.createFrom().failure(new PasswordPolicyException("Password does not meet policy requirements"));
                }
                
                // 更新密码
                user.password = BcryptUtil.bcryptHash(request.newPassword);
                
                return userRepository.persist(user)
                    .onItem().transformToUni(ignored -> {
                        auditService.logPasswordChange(username);
                        return Uni.createFrom().voidItem();
                    });
            });
    }
    
    @Transactional
    public Uni<Void> resetPassword(String email) {
        return userRepository.findByEmail(email)
            .onItem().transformToUni(userOpt -> {
                if (userOpt.isEmpty()) {
                    // 为了安全,即使用户不存在也返回成功
                    return Uni.createFrom().voidItem();
                }
                
                User user = userOpt.get();
                user.passwordResetToken = UUID.randomUUID().toString();
                user.passwordResetExpires = LocalDateTime.now().plusHours(1);
                
                return userRepository.persist(user)
                    .onItem().transformToUni(savedUser -> {
                        return emailService.sendPasswordReset(savedUser)
                            .onItem().transform(ignored -> {
                                auditService.logPasswordResetRequest(savedUser.username);
                                return null;
                            });
                    });
            });
    }
    
    @Transactional
    public Uni<Void> confirmPasswordReset(ConfirmPasswordResetRequest request) {
        return userRepository.findByPasswordResetToken(request.token)
            .onItem().transformToUni(userOpt -> {
                if (userOpt.isEmpty()) {
                    return Uni.createFrom().failure(new InvalidTokenException("Invalid reset token"));
                }
                
                User user = userOpt.get();
                
                if (user.passwordResetExpires.isBefore(LocalDateTime.now())) {
                    return Uni.createFrom().failure(new InvalidTokenException("Reset token has expired"));
                }
                
                // 验证新密码强度
                if (!isPasswordStrong(request.newPassword)) {
                    return Uni.createFrom().failure(new PasswordPolicyException("Password does not meet policy requirements"));
                }
                
                // 更新密码并清除重置令牌
                user.password = BcryptUtil.bcryptHash(request.newPassword);
                user.passwordResetToken = null;
                user.passwordResetExpires = null;
                user.resetFailedLoginAttempts();
                
                return userRepository.persist(user)
                    .onItem().transformToUni(ignored -> {
                        auditService.logPasswordReset(user.username);
                        return Uni.createFrom().voidItem();
                    });
            });
    }
    
    @Transactional
    public Uni<Void> verifyEmail(String token) {
        return userRepository.findByEmailVerificationToken(token)
            .onItem().transformToUni(userOpt -> {
                if (userOpt.isEmpty()) {
                    return Uni.createFrom().failure(new InvalidTokenException("Invalid verification token"));
                }
                
                User user = userOpt.get();
                user.isEmailVerified = true;
                user.emailVerificationToken = null;
                
                return userRepository.persist(user)
                    .onItem().transformToUni(ignored -> {
                        auditService.logEmailVerification(user.username);
                        return Uni.createFrom().voidItem();
                    });
            });
    }
    
    private boolean isPasswordStrong(String password) {
        // 密码强度检查:至少8位,包含大小写字母、数字和特殊字符
        if (password.length() < 8) {
            return false;
        }
        
        boolean hasUpper = password.chars().anyMatch(Character::isUpperCase);
        boolean hasLower = password.chars().anyMatch(Character::isLowerCase);
        boolean hasDigit = password.chars().anyMatch(Character::isDigit);
        boolean hasSpecial = password.chars().anyMatch(ch -> "!@#$%^&*()_+-=[]{}|;:,.<>?".indexOf(ch) >= 0);
        
        return hasUpper && hasLower && hasDigit && hasSpecial;
    }
}

6.3 JWT 令牌认证

6.3.1 JWT 配置

# JWT 配置
mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem
mp.jwt.verify.issuer=https://example.com
mp.jwt.verify.audiences=myapp

# JWT 生成配置
smallrye.jwt.sign.key.location=META-INF/resources/privateKey.pem
smallrye.jwt.new-token.issuer=https://example.com
smallrye.jwt.new-token.audience=myapp
smallrye.jwt.new-token.lifespan=3600

# 刷新令牌配置
quarkus.jwt.refresh-token.enabled=true
quarkus.jwt.refresh-token.lifespan=604800
quarkus.jwt.refresh-token.cookie-name=refresh_token
quarkus.jwt.refresh-token.cookie-path=/
quarkus.jwt.refresh-token.cookie-http-only=true
quarkus.jwt.refresh-token.cookie-secure=true
quarkus.jwt.refresh-token.cookie-same-site=strict

6.3.2 JWT 服务实现

package com.example.security.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.jwt.JsonWebToken;
import com.example.security.entity.User;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@ApplicationScoped
public class JwtService {
    
    @Inject
    UserRepository userRepository;
    
    @Inject
    RefreshTokenRepository refreshTokenRepository;
    
    @Inject
    SecurityAuditService auditService;
    
    public Uni<TokenResponse> generateTokens(User user) {
        return generateAccessToken(user)
            .onItem().transformToUni(accessToken -> {
                return generateRefreshToken(user)
                    .onItem().transform(refreshToken -> {
                        TokenResponse response = new TokenResponse();
                        response.accessToken = accessToken;
                        response.refreshToken = refreshToken;
                        response.tokenType = "Bearer";
                        response.expiresIn = 3600; // 1 hour
                        return response;
                    });
            });
    }
    
    public Uni<String> generateAccessToken(User user) {
        return Uni.createFrom().item(() -> {
            Set<String> roles = user.roles.stream()
                .map(role -> role.name)
                .collect(Collectors.toSet());
            
            Set<String> permissions = user.getAllPermissions();
            
            String sessionId = UUID.randomUUID().toString();
            
            JwtClaimsBuilder claimsBuilder = Jwt.claims()
                .issuer("https://example.com")
                .subject(user.username)
                .audience("myapp")
                .issuedAt(Instant.now())
                .expiresAt(Instant.now().plus(Duration.ofHours(1)))
                .claim("user_id", user.id)
                .claim("email", user.email)
                .claim("session_id", sessionId)
                .claim("roles", roles)
                .claim("permissions", permissions)
                .claim("email_verified", user.isEmailVerified);
            
            // 添加自定义声明
            if (user.firstName != null) {
                claimsBuilder.claim("given_name", user.firstName);
            }
            if (user.lastName != null) {
                claimsBuilder.claim("family_name", user.lastName);
            }
            
            return claimsBuilder.sign();
        });
    }
    
    public Uni<String> generateRefreshToken(User user) {
        return Uni.createFrom().item(() -> {
            String tokenId = UUID.randomUUID().toString();
            
            RefreshToken refreshToken = new RefreshToken();
            refreshToken.tokenId = tokenId;
            refreshToken.userId = user.id;
            refreshToken.username = user.username;
            refreshToken.expiresAt = LocalDateTime.now().plusDays(7);
            refreshToken.isActive = true;
            
            return refreshTokenRepository.persist(refreshToken)
                .onItem().transform(ignored -> {
                    return Jwt.claims()
                        .issuer("https://example.com")
                        .subject(user.username)
                        .audience("myapp")
                        .issuedAt(Instant.now())
                        .expiresAt(Instant.now().plus(Duration.ofDays(7)))
                        .claim("token_id", tokenId)
                        .claim("user_id", user.id)
                        .claim("token_type", "refresh")
                        .sign();
                });
        }).flatMap(uni -> uni);
    }
    
    public Uni<TokenResponse> refreshAccessToken(String refreshTokenString) {
        return validateRefreshToken(refreshTokenString)
            .onItem().transformToUni(claims -> {
                String tokenId = (String) claims.get("token_id");
                Long userId = Long.valueOf(claims.get("user_id").toString());
                
                return refreshTokenRepository.findByTokenId(tokenId)
                    .onItem().transformToUni(tokenOpt -> {
                        if (tokenOpt.isEmpty() || !tokenOpt.get().isActive) {
                            return Uni.createFrom().failure(new InvalidTokenException("Invalid refresh token"));
                        }
                        
                        RefreshToken refreshToken = tokenOpt.get();
                        
                        if (refreshToken.expiresAt.isBefore(LocalDateTime.now())) {
                            refreshToken.isActive = false;
                            refreshTokenRepository.persist(refreshToken);
                            return Uni.createFrom().failure(new TokenExpiredException("Refresh token expired"));
                        }
                        
                        return userRepository.findById(userId)
                            .onItem().transformToUni(userOpt -> {
                                if (userOpt.isEmpty()) {
                                    return Uni.createFrom().failure(new UserNotFoundException("User not found"));
                                }
                                
                                User user = userOpt.get();
                                
                                if (!user.isActive) {
                                    return Uni.createFrom().failure(new AccountInactiveException("Account inactive"));
                                }
                                
                                return generateAccessToken(user)
                                    .onItem().transform(newAccessToken -> {
                                        TokenResponse response = new TokenResponse();
                                        response.accessToken = newAccessToken;
                                        response.refreshToken = refreshTokenString; // 保持原有刷新令牌
                                        response.tokenType = "Bearer";
                                        response.expiresIn = 3600;
                                        
                                        auditService.logTokenRefresh(user.username);
                                        return response;
                                    });
                            });
                    });
            });
    }
    
    public Uni<Map<String, Object>> validateAccessToken(String token) {
        return Uni.createFrom().item(() -> {
            try {
                // 这里应该使用适当的 JWT 验证库
                // 简化示例,实际应用中需要验证签名、过期时间等
                JsonWebToken jwt = parseJwt(token);
                
                if (jwt.getExpirationTime() < Instant.now().getEpochSecond()) {
                    throw new TokenExpiredException("Access token expired");
                }
                
                Map<String, Object> claims = new HashMap<>();
                claims.put("sub", jwt.getSubject());
                claims.put("user_id", jwt.getClaim("user_id"));
                claims.put("email", jwt.getClaim("email"));
                claims.put("roles", jwt.getClaim("roles"));
                claims.put("permissions", jwt.getClaim("permissions"));
                claims.put("session_id", jwt.getClaim("session_id"));
                
                return claims;
            } catch (Exception e) {
                throw new InvalidTokenException("Invalid access token", e);
            }
        });
    }
    
    public Uni<Map<String, Object>> validateRefreshToken(String token) {
        return Uni.createFrom().item(() -> {
            try {
                JsonWebToken jwt = parseJwt(token);
                
                if (jwt.getExpirationTime() < Instant.now().getEpochSecond()) {
                    throw new TokenExpiredException("Refresh token expired");
                }
                
                String tokenType = jwt.getClaim("token_type");
                if (!"refresh".equals(tokenType)) {
                    throw new InvalidTokenException("Not a refresh token");
                }
                
                Map<String, Object> claims = new HashMap<>();
                claims.put("sub", jwt.getSubject());
                claims.put("user_id", jwt.getClaim("user_id"));
                claims.put("token_id", jwt.getClaim("token_id"));
                
                return claims;
            } catch (Exception e) {
                throw new InvalidTokenException("Invalid refresh token", e);
            }
        });
    }
    
    public Uni<Void> revokeRefreshToken(String tokenId) {
        return refreshTokenRepository.findByTokenId(tokenId)
            .onItem().transformToUni(tokenOpt -> {
                if (tokenOpt.isPresent()) {
                    RefreshToken token = tokenOpt.get();
                    token.isActive = false;
                    token.revokedAt = LocalDateTime.now();
                    
                    return refreshTokenRepository.persist(token)
                        .onItem().transform(ignored -> null);
                }
                return Uni.createFrom().voidItem();
            });
    }
    
    public Uni<Void> revokeAllUserTokens(Long userId) {
        return refreshTokenRepository.revokeAllUserTokens(userId);
    }
    
    private JsonWebToken parseJwt(String token) {
        // 实际实现中应该使用适当的 JWT 解析库
        // 这里只是示例
        throw new UnsupportedOperationException("JWT parsing not implemented");
    }
}

6.3.3 认证资源端点

package com.example.security.resource;

import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import com.example.security.service.*;
import com.example.security.dto.*;
import com.example.security.exception.*;

@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Authentication", description = "Authentication and authorization operations")
public class AuthResource {
    
    @Inject
    AuthenticationService authService;
    
    @Inject
    JwtService jwtService;
    
    @Inject
    UserService userService;
    
    @Inject
    SecurityContext securityContext;
    
    @POST
    @Path("/login")
    @PermitAll
    @Operation(summary = "User login", description = "Authenticate user and return JWT tokens")
    public Uni<Response> login(@Valid LoginRequest request) {
        return authService.authenticate(request.username, request.password)
            .onItem().transformToUni(user -> {
                return jwtService.generateTokens(user)
                    .onItem().transform(tokens -> {
                        LoginResponse response = new LoginResponse();
                        response.user = UserResponse.from(user);
                        response.accessToken = tokens.accessToken;
                        response.refreshToken = tokens.refreshToken;
                        response.tokenType = tokens.tokenType;
                        response.expiresIn = tokens.expiresIn;
                        
                        return Response.ok(response)
                            .cookie(createRefreshTokenCookie(tokens.refreshToken))
                            .build();
                    });
            })
            .onFailure().recoverWithItem(throwable -> {
                if (throwable instanceof AuthenticationException) {
                    return Response.status(Response.Status.UNAUTHORIZED)
                        .entity(new ErrorResponse("Invalid credentials"))
                        .build();
                } else if (throwable instanceof AccountInactiveException) {
                    return Response.status(Response.Status.FORBIDDEN)
                        .entity(new ErrorResponse("Account is inactive"))
                        .build();
                } else if (throwable instanceof AccountLockedException) {
                    return Response.status(Response.Status.FORBIDDEN)
                        .entity(new ErrorResponse("Account is locked"))
                        .build();
                } else {
                    return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                        .entity(new ErrorResponse("Login failed"))
                        .build();
                }
            });
    }
    
    @POST
    @Path("/register")
    @PermitAll
    @Operation(summary = "User registration", description = "Register a new user account")
    public Uni<Response> register(@Valid RegisterRequest request) {
        return authService.register(request)
            .onItem().transform(user -> {
                UserResponse userResponse = UserResponse.from(user);
                return Response.status(Response.Status.CREATED)
                    .entity(new RegisterResponse(userResponse, "Registration successful. Please check your email for verification."))
                    .build();
            })
            .onFailure().recoverWithItem(throwable -> {
                if (throwable instanceof UserAlreadyExistsException) {
                    return Response.status(Response.Status.CONFLICT)
                        .entity(new ErrorResponse(throwable.getMessage()))
                        .build();
                } else if (throwable instanceof ValidationException) {
                    return Response.status(Response.Status.BAD_REQUEST)
                        .entity(new ErrorResponse(throwable.getMessage()))
                        .build();
                } else {
                    return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                        .entity(new ErrorResponse("Registration failed"))
                        .build();
                }
            });
    }
    
    @POST
    @Path("/refresh")
    @PermitAll
    @Operation(summary = "Refresh token", description = "Refresh access token using refresh token")
    public Uni<Response> refreshToken(@Valid RefreshTokenRequest request) {
        String refreshToken = request.refreshToken;
        
        // 如果请求中没有刷新令牌,尝试从 Cookie 中获取
        if (refreshToken == null || refreshToken.isEmpty()) {
            // 从 Cookie 中获取刷新令牌的逻辑
            refreshToken = getRefreshTokenFromCookie();
        }
        
        if (refreshToken == null || refreshToken.isEmpty()) {
            return Uni.createFrom().item(
                Response.status(Response.Status.BAD_REQUEST)
                    .entity(new ErrorResponse("Refresh token is required"))
                    .build()
            );
        }
        
        return jwtService.refreshAccessToken(refreshToken)
            .onItem().transform(tokens -> {
                RefreshTokenResponse response = new RefreshTokenResponse();
                response.accessToken = tokens.accessToken;
                response.tokenType = tokens.tokenType;
                response.expiresIn = tokens.expiresIn;
                
                return Response.ok(response).build();
            })
            .onFailure().recoverWithItem(throwable -> {
                if (throwable instanceof InvalidTokenException || 
                    throwable instanceof TokenExpiredException) {
                    return Response.status(Response.Status.UNAUTHORIZED)
                        .entity(new ErrorResponse("Invalid or expired refresh token"))
                        .build();
                } else {
                    return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                        .entity(new ErrorResponse("Token refresh failed"))
                        .build();
                }
            });
    }
    
    @POST
    @Path("/logout")
    @Authenticated
    @Operation(summary = "User logout", description = "Logout user and revoke tokens")
    public Uni<Response> logout() {
        String username = getCurrentUsername();
        String sessionId = getCurrentSessionId();
        
        return jwtService.revokeRefreshToken(sessionId)
            .onItem().transform(ignored -> {
                return Response.ok(new MessageResponse("Logout successful"))
                    .cookie(clearRefreshTokenCookie())
                    .build();
            })
            .onFailure().recoverWithItem(throwable -> 
                Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(new ErrorResponse("Logout failed"))
                    .build()
            );
    }
    
    @POST
    @Path("/logout-all")
    @Authenticated
    @Operation(summary = "Logout from all devices", description = "Logout user from all devices and revoke all tokens")
    public Uni<Response> logoutAll() {
        Long userId = getUserId();
        
        return jwtService.revokeAllUserTokens(userId)
            .onItem().transform(ignored -> {
                return Response.ok(new MessageResponse("Logged out from all devices"))
                    .cookie(clearRefreshTokenCookie())
                    .build();
            })
            .onFailure().recoverWithItem(throwable -> 
                Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(new ErrorResponse("Logout failed"))
                    .build()
            );
    }
    
    // 辅助方法
    private String getCurrentUsername() {
        return securityContext.getUserPrincipal().getName();
    }
    
    private String getCurrentSessionId() {
        JsonWebToken jwt = (JsonWebToken) securityContext.getUserPrincipal();
        return jwt.getClaim("session_id");
    }
    
    private Long getUserId() {
        JsonWebToken jwt = (JsonWebToken) securityContext.getUserPrincipal();
        return Long.valueOf(jwt.getClaim("user_id").toString());
    }
    
    private NewCookie createRefreshTokenCookie(String refreshToken) {
        return new NewCookie("refresh_token", refreshToken, "/", null, null, 
                           7 * 24 * 60 * 60, true, true);
    }
    
    private NewCookie clearRefreshTokenCookie() {
        return new NewCookie("refresh_token", "", "/", null, null, 
                           0, true, true);
    }
    
    private String getRefreshTokenFromCookie() {
        // 从 HTTP 请求的 Cookie 中获取刷新令牌
        // 实际实现需要注入 HttpServletRequest 或使用其他方式
        return null;
    }
}

6.4 本章小结

6.4.1 核心概念回顾

本章深入探讨了 Quarkus 中的安全认证与授权机制:

  1. 安全架构:理解 Quarkus 安全模型的核心组件
  2. 用户管理:实现完整的用户、角色和权限管理系统
  3. JWT 认证:掌握 JWT 令牌的生成、验证和管理
  4. 基础认证:学习基本的用户名密码认证流程

6.4.2 技术要点总结

  • 多层安全防护:从传输层到应用层的全方位安全保护
  • 灵活的认证方式:支持多种认证机制和身份提供者
  • 细粒度授权:基于角色和权限的精确访问控制
  • 安全审计:完整的安全事件记录和追踪
  • 性能优化:高效的安全检查和令牌管理

6.4.3 最佳实践

  1. 密码安全:使用强密码策略和安全的哈希算法
  2. 令牌管理:合理设置令牌过期时间和刷新机制
  3. 会话安全:实现安全的会话管理和并发控制
  4. 审计日志:记录所有安全相关的操作和事件
  5. 错误处理:避免泄露敏感信息的错误消息

6.4.4 下一章预告

下一章我们将学习 监控、日志与健康检查,包括: - 应用监控和指标收集 - 结构化日志和日志聚合 - 健康检查和就绪检查 - 分布式追踪和性能分析 - 告警和通知机制