本文目录导读:

SCIM(System for Cross-domain Identity Management)是一种用于身份管理的标准化协议,主要用于用户、组等身份资源的跨域同步,在PHP中实现SCIM协议,主要有以下几种方式:
使用现成的库
SCIM 2.0 PHP库
// 使用 composer 安装 composer require "scim2/core" composer require "scim2/server" composer require "scim2/client"
服务端实现示例:
use SCIM\Server\SCIMServer;
use SCIM\Server\Adapter\InMemoryAdapter;
// 创建内存适配器
$adapter = new InMemoryAdapter();
// 创建用户
$userData = [
'userName' => 'john.doe',
'name' => [
'givenName' => 'John',
'familyName' => 'Doe'
],
'emails' => [
['value' => 'john.doe@example.com', 'type' => 'work']
]
];
$user = $adapter->createUser($userData);
// 获取用户
$retrievedUser = $adapter->getUser($user['id']);
Symfony SCIM Bundle
// 使用 Symfony 的 SCIM bundle
composer require "tobiri/scim-bundle"
// config/packages/scim.yaml
scim:
user_class: App\Entity\User
group_class: App\Entity\Group
手动实现 SCIM REST API
基本架构
class SCIMController extends AbstractController
{
#[Route('/scim/v2/Users', name: 'scim_users')]
public function handleUsers(Request $request): JsonResponse
{
$method = $request->getMethod();
switch ($method) {
case 'GET':
return $this->listUsers($request);
case 'POST':
return $this->createUser($request);
default:
return $this->json(['error' => 'Method not allowed'], 405);
}
}
#[Route('/scim/v2/Users/{id}', name: 'scim_user_detail')]
public function handleUserDetail(string $id, Request $request): JsonResponse
{
$method = $request->getMethod();
switch ($method) {
case 'GET':
return $this->getUser($id);
case 'PUT':
return $this->updateUser($id, $request);
case 'PATCH':
return $this->patchUser($id, $request);
case 'DELETE':
return $this->deleteUser($id);
default:
return $this->json(['error' => 'Method not allowed'], 405);
}
}
}
数据模型
class SCIMUser
{
private string $id;
private string $userName;
private array $name;
private array $emails;
private array $roles;
private bool $active;
// 转换为 SCIM 格式
public function toSCIM(): array
{
return [
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User'],
'id' => $this->id,
'userName' => $this->userName,
'name' => $this->name,
'emails' => $this->emails,
'active' => $this->active,
'meta' => [
'resourceType' => 'User',
'created' => $this->createdAt->format(DATE_ATOM),
'lastModified' => $this->updatedAt->format(DATE_ATOM)
]
];
}
}
完整的SCIM服务端实现
class SCIMService
{
private EntityManagerInterface $em;
private UserRepository $userRepository;
private GroupRepository $groupRepository;
// 用户CRUD操作
public function createUser(SCIMRequest $request): SCIMResponse
{
$data = $request->getAttributes();
// 验证 schema
if (!$this->isValidUserSchema($data)) {
throw new SCIMValidationException('Invalid schema');
}
// 检查用户名唯一性
if ($this->userRepository->findOneBy(['username' => $data['userName']])) {
throw new SCIMConflictException('Username already exists');
}
// 创建用户实体
$user = new User();
$user->setUsername($data['userName']);
$user->setEmail($data['emails'][0]['value']);
// 映射自定义属性
if (isset($data['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'])) {
$enterpriseData = $data['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'];
$user->setDepartment($enterpriseData['department'] ?? null);
// 更多自定义属性映射
}
$this->em->persist($user);
$this->em->flush();
// 返回 SCIM 格式响应
return new SCIMResponse(
$this->userToSCIM($user),
201
);
}
// PATCH 请求处理
public function patchUser(string $id, SCIMRequest $request): SCIMResponse
{
$user = $this->userRepository->find($id);
if (!$user) {
throw new SCIMNotFoundException('User not found');
}
// 使用 JSON Patch 或 SCIM Patch 操作
$operations = $request->getOperations();
foreach ($operations as $operation) {
switch ($operation['op']) {
case 'add':
$this->applyAddOperation($user, $operation);
break;
case 'replace':
$this->applyReplaceOperation($user, $operation);
break;
case 'remove':
$this->applyRemoveOperation($user, $operation);
break;
}
}
$this->em->flush();
return new SCIMResponse($this->userToSCIM($user));
}
// 查询和过滤
public function searchUsers(SCIMSearchRequest $request): SCIMResponse
{
$filters = $request->getFilters();
$startIndex = $request->getStartIndex() ?? 1;
$count = $request->getCount() ?? 100;
// 构建查询
$qb = $this->em->createQueryBuilder();
$qb->select('u')
->from(User::class, 'u');
// 应用过滤条件
if (!empty($filters)) {
foreach ($filters as $filter) {
switch ($filter['field']) {
case 'userName':
$qb->andWhere('u.username = :username')
->setParameter('username', $filter['value']);
break;
case 'email':
$qb->andWhere('u.email LIKE :email')
->setParameter('email', '%' . $filter['value'] . '%');
break;
// 更多过滤条件
}
}
}
// 分页
$qb->setFirstResult($startIndex - 1)
->setMaxResults($count);
$users = $qb->getQuery()->getResult();
// 格式化响应
return new SCIMResponse([
'schemas' => [
'urn:ietf:params:scim:api:messages:2.0:ListResponse'
],
'totalResults' => count($users),
'startIndex' => $startIndex,
'itemsPerPage' => $count,
'Resources' => array_map(fn($user) => $this->userToSCIM($user), $users)
]);
}
// 批量操作
public function bulkOperation(SCIMBulkRequest $request): SCIMResponse
{
$operations = $request->getOperations();
$results = [];
foreach ($operations as $index => $operation) {
try {
switch ($operation['method']) {
case 'POST':
$results[] = $this->createUser(new SCIMRequest($operation['data']));
break;
case 'PUT':
$results[] = $this->updateUser(
$operation['path'],
new SCIMRequest($operation['data'])
);
break;
case 'DELETE':
$results[] = $this->deleteUser($operation['path']);
break;
}
} catch (Exception $e) {
$results[] = [
'bulkId' => $operation['bulkId'],
'status' => $e->getCode(),
'response' => ['error' => $e->getMessage()]
];
}
}
return new SCIMResponse([
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:BulkResponse'],
'Operations' => $results
]);
}
}
SCIM客户端实现
class SCIMClient
{
private string $baseUrl;
private string $bearerToken;
// 创建用户
public function createUser(array $userData): array
{
$ch = curl_init($this->baseUrl . '/Users');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($userData),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->bearerToken
]
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true);
}
// 更新用户 (PATCH)
public function patchUser(string $id, array $patchData): array
{
$ch = curl_init($this->baseUrl . '/Users/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => json_encode($patchData),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->bearerToken
]
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
错误处理和验证
class SCIMErrorHandler
{
public static function handleException(Exception $e): JsonResponse
{
$statusCode = 500;
$scimError = [
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
'detail' => $e->getMessage()
];
switch (get_class($e)) {
case SCIMNotFoundException::class:
$statusCode = 404;
$scimError['status'] = '404';
break;
case SCIMValidationException::class:
$statusCode = 400;
$scimError['status'] = '400';
$scimError['scimType'] = 'invalidSyntax';
break;
case SCIMConflictException::class:
$statusCode = 409;
$scimError['status'] = '409';
$scimError['scimType'] = 'uniqueness';
break;
case SCIMFilterException::class:
$statusCode = 400;
$scimError['status'] = '400';
$scimError['scimType'] = 'invalidFilter';
break;
}
return new JsonResponse($scimError, $statusCode);
}
}
认证和授权
class SCIMAuthenticator
{
public function authenticate(Request $request): ?User
{
// 支持 Bearer Token
$authHeader = $request->headers->get('Authorization');
if (!$authHeader || !preg_match('/^Bearer\s+(.+)$/', $authHeader, $matches)) {
return null;
}
$token = $matches[1];
// 验证 token
return $this->validateToken($token);
}
private function validateToken(string $token): ?User
{
// 检查是否为有效的 Service Token
if (in_array($token, $this->getServiceTokens())) {
return new ServiceUser();
}
// 或通过 OAuth2 验证
$claims = $this->jwtDecoder->decode($token);
return $this->userRepository->findByEmail($claims['email']);
}
}
测试示例
class SCIMServiceTest extends TestCase
{
public function testCreateUser()
{
$payload = [
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User'],
'userName' => 'test.user',
'emails' => [
['value' => 'test@example.com', 'primary' => true]
]
];
$client = $this->createClient();
$client->request(
'POST',
'/scim/v2/Users',
[],
[],
['CONTENT_TYPE' => 'application/json'],
json_encode($payload)
);
$this->assertResponseStatusCodeSame(201);
$response = json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('id', $response);
$this->assertEquals('test.user', $response['userName']);
}
}
最佳实践建议
- 验证所有SCIM Schema - 确保请求符合SCIM标准
- 实现完整的错误处理 - 返回标准化的SCIM错误格式
- 支持过滤和分页 - 实现完整的过滤语法支持
- 处理并发 - 使用版本号实现乐观锁
- 日志记录 - 记录所有SCIM操作用于审计
- 性能优化 - 为常用查询添加索引
如果需要更具体的实现细节或帮助,可以提供更多关于您使用场景的信息。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播