> ## Documentation Index
> Fetch the complete documentation index at: https://blog.pig4cloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pigx auth

## 环境说明

| 环境组件 | 版本  | 备注 |
| ---- | --- | -- |
| PigX | 5.9 |    |
| JDK  | 17  |    |

## 概述

本文详细介绍如何在 Spring Authorization Server 中实现自定义认证模式，以邮件验证码登录为例，展示完整的自定义认证流程实现。

![模块架构图](https://minio.pigx.vip/oss/202509/a6ysAp.png)

## 背景与目标

在传统的 OAuth2 授权流程中，通常使用用户名密码进行身份验证。但在某些业务场景下，我们可能需要使用其他方式进行身份验证，比如：

* 邮件验证码登录
* 短信验证码登录
* 第三方平台登录
* 生物识别登录

本文将演示如何基于 Spring Authorization Server 实现邮件验证码登录的自定义认证模式，为其他自定义认证方式提供参考模板。

## 实现架构

自定义认证模式主要涉及以下核心组件：

1. **认证令牌 (Authentication Token)** - 定义自定义的认证令牌类型
2. **认证转换器 (Authentication Converter)** - 处理请求参数并转换为认证令牌
3. **认证提供者 (Authentication Provider)** - 执行实际的认证逻辑
4. **用户详情服务 (User Details Service)** - 根据认证信息加载用户详情
5. **授权服务器配置** - 注册自定义认证模式

## 核心实现

### 1. 认证令牌 (Authentication Token)

首先定义自定义的认证令牌类，用于封装邮件登录的认证信息：

```java theme={null}
/**
 * 邮件验证码认证令牌
 * 继承自 OAuth2ResourceOwnerBaseAuthenticationToken，用于邮件登录流程
 */
public class OAuth2ResourceOwnerEmailAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {

    /**
     * 构造函数 - 固定写法，无需修改
     * @param authorizationGrantType 授权类型，这里为 "email"
     * @param clientPrincipal 客户端认证信息
     * @param scopes 请求的权限范围
     * @param additionalParameters 额外参数，包含邮件地址等信息
     */
    public OAuth2ResourceOwnerEmailAuthenticationToken(AuthorizationGrantType authorizationGrantType,
            Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
        super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
    }
}
```

**关键点说明：**

* 继承 `OAuth2ResourceOwnerBaseAuthenticationToken` 基类
* 构造函数参数固定，无需自定义修改
* 主要用于标识这是一个邮件认证类型的令牌

### 2. 认证转换器 (Authentication Converter)

```
public class OAuth2ResourceOwnerEmailAuthenticationConverter
		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerEmailAuthenticationToken> {

	// 重要 支持email模式
	@Override
	public boolean support(String grantType) {
		return "email".equals(grantType);
	}

	// 校验参数 email不能为空
	@Override
	public void checkParams(HttpServletRequest request) {
		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
		// email (REQUIRED)
		String email = parameters.getFirst("email");
		if (!StringUtils.hasText(email) || parameters.get("email").size() != 1) {
			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "email",
					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
		}
	}

	// ========== 下面是固定写法 =================
	@Override
	public OAuth2ResourceOwnerEmailAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes,
			Map additionalParameters) {
		return new OAuth2ResourceOwnerEmailAuthenticationToken(new AuthorizationGrantType("email"), clientPrincipal,
				requestedScopes, additionalParameters);
	}

}

```

### 3. 认证提供者 (Authentication Provider)

```
public class OAuth2ResourceOwnerEmailAuthenticationProvider
		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerEmailAuthenticationToken> {

	// 获取请求中的 email 参数, 用于构建 UsernamePasswordAuthenticationToken
	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
		String email = (String) reqParameters.get("email");
		return new UsernamePasswordAuthenticationToken(email, null);
	}

	// 判断是否支持此 Authentication Token
	public boolean supports(Class<?> authentication) {
		return OAuth2ResourceOwnerEmailAuthenticationToken.class.isAssignableFrom(authentication);
	}

	// ========== 下面是固定写法 =================
	public OAuth2ResourceOwnerEmailAuthenticationProvider(AuthenticationManager authenticationManager,
			OAuth2AuthorizationService authorizationService,
			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
		super(authenticationManager, authorizationService, tokenGenerator);
	}

	public void checkClient(RegisteredClient registeredClient) {
	}

}

```

## 授权服务器配置

### 注册邮件登录自定义授权方式

在 `AuthorizationServerConfiguration` 中注册自定义的邮件认证模式：

![授权服务器配置](https://minio.pigx.vip/oss/202509/hte3Kj.png)

**配置要点：**

* 注册自定义的认证转换器
* 注册自定义的认证提供者
* 确保授权类型 "email" 被正确识别

## 资源服务配置

### 用户详情服务

#### PigxEmailUserDetailServiceImpl

Spring Security 回调业务的入口，负责根据邮件地址加载用户信息：

**核心功能：**

* 验证邮件验证码
* 根据邮件地址查询用户信息
* 构造用户详情对象

```
@Slf4j
@RequiredArgsConstructor
public class PigxEmailUserDetailServiceImpl implements PigxUserDetailsService {

	private final UserDetailsService pigxDefaultUserDetailsServiceImpl;

	private final RemoteUserService remoteUserService;

	@Override
	public UserDetails loadUserByUsername(String email) {
		// 获取用户传递的验证码
		String code = WebUtils.getRequest().getParameter("code");
		// 校验验证码 , 验证码和发送的保持不一致 throw new OAuth2AuthenticationException("验证码错误");

		// 根据 email 查询 UPMS 用户信息 构造用户信息
		R<UserInfo> result = remoteUserService.emailinfo(email);
		return getUserDetails(RetOps.of(result).getData());
	}

	// 自定义的 email 授权方式都走这个
	public boolean support(String clientId, String grantType) {
		return "email".equals(grantType);
	}

	// === 固定写法 ===
	public UserDetails loadUserByUser(PigxUser pigxUser) {
		return pigxDefaultUserDetailsServiceImpl.loadUserByUsername(pigxUser.getUsername());
	}

}
```

#### SPI 文件注册

通过 SPI 机制注册自定义的 UserDetailService：

![SPI 注册配置](https://minio.pigx.vip/oss/202509/18CHkR.png)

**注册步骤：**

1. 在 `META-INF/services` 目录下创建配置文件
2. 指定自定义 UserDetailService 的实现类
3. 确保 Spring 能够正确加载和实例化服务

#### 认证提供者配置

配置 `PigxDaoAuthenticationProvider` 跳过密码校验：

![认证提供者配置](https://minio.pigx.vip/oss/202509/P3BhqO.png)

**配置说明：**

* 针对邮件认证模式，跳过传统的密码验证
* 直接使用邮件地址进行用户身份验证
* 确保认证流程的完整性

## 测试使用

### 1. 数据库配置

首先在数据库中插入支持邮件认证的客户端配置：

```sql theme={null}
INSERT INTO `pigxx`.`sys_oauth_client_details` 
(`id`, `client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, 
 `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, 
 `additional_information`, `autoapprove`, `del_flag`, `create_by`, `update_by`, 
 `create_time`, `update_time`, `tenant_id`) 
VALUES (111, 'emailtest', NULL, 'emailtest', 'server', 'email', NULL, NULL, 
        43200, 2592001, '{\"captcha_flag\":\"0\",\"enc_flag\":\"0\",\"online_quantity\":\"1\"}', 
        'false', '0', NULL, NULL, NULL, NULL, 1);
```

**配置说明：**

* `client_id`: 客户端标识符
* `authorized_grant_types`: 授权类型设置为 "email"
* `scope`: 权限范围
* `access_token_validity`: 访问令牌有效期（秒）

### 2. API 测试

使用 curl 命令测试邮件认证接口：

```bash theme={null}
curl --location --request POST 'http://127.0.0.1:9999/auth/oauth2/token?grant_type=email&code=1234' \
--header 'TENANT-ID: 1' \
--header 'Authorization: Basic ZW1haWx0ZXN0OmVtYWlsdGVzdA==' \
--data-urlencode 'email=sw@pigx.vip' \
--data-urlencode 'scope=server'
```

**请求参数说明：**

* `grant_type=email`: 指定使用邮件认证模式
* `code=1234`: 邮件验证码
* `email=sw@pigx.vip`: 用户邮件地址
* `scope=server`: 请求的权限范围
* `Authorization`: 客户端认证信息（Base64 编码）

**预期响应：**

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 43200,
  "scope": "server"
}
```
