跳转至

第五章:服务端实现

本章介绍如何实现 OAuth2/OIDC 服务端。

Keycloak 部署

Docker 部署

docker run -d --name keycloak \
  -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:23.0 \
  start-dev

Kubernetes 部署

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: auth
spec:
  replicas: 3
  selector:
    matchLabels:
      app: keycloak
  template:
    spec:
      containers:
      - name: keycloak
        image: quay.io/keycloak/keycloak:23.0
        args: ["start"]
        env:
        - name: KEYCLOAK_ADMIN
          value: admin
        - name: KEYCLOAK_ADMIN_PASSWORD
          valueFrom:
            secretKeyRef:
              name: keycloak-admin
              key: password
        - name: KC_DB
          value: postgres
        - name: KC_DB_URL
          value: jdbc:postgresql://postgres:5432/keycloak
        - name: KC_DB_USERNAME
          value: keycloak
        - name: KC_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: keycloak-db
              key: password
        - name: KC_HOSTNAME
          value: auth.example.com
        - name: KC_HOSTNAME_STRICT
          value: "false"
        - name: KC_PROXY
          value: edge
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 2000m
            memory: 2Gi

配置 Realm

创建 Realm

# 使用 CLI
/opt/keycloak/bin/kcadm.sh create realms \
  -s realm=my-app \
  -s enabled=true \
  --server http://localhost:8080 \
  --realm master \
  --user admin \
  --password admin

创建客户端

/opt/keycloak/bin/kcadm.sh create clients \
  -r my-app \
  -s clientId=my-client \
  -s secret=my-secret \
  -s redirectUris='["https://app.example.com/*"]' \
  -s webOrigins='["https://app.example.com"]' \
  -s publicClient=false \
  -s standardFlowEnabled=true \
  -s directAccessGrantsEnabled=true

创建用户

/opt/keycloak/bin/kcadm.sh create users \
  -r my-app \
  -s username=john \
  -s email=john@example.com \
  -s enabled=true

/opt/keycloak/bin/kcadm.sh set-password \
  -r my-app \
  --username john \
  --new-password password

自定义认证流程

添加 MFA

# Keycloak 配置
authenticationFlows:
  - alias: "browser-with-mfa"
    description: "Browser flow with MFA"
    providerId: "basic-flow"
    authenticationExecutions:
      - authenticator: "auth-cookie"
        requirement: "ALTERNATIVE"
      - authenticator: "basic-auth"
        requirement: "DISABLED"
      - authenticator: "auth-username-password-form"
        requirement: "REQUIRED"
      - authenticator: "authenticator-mfa"
        requirement: "REQUIRED"

自定义认证器

public class CustomAuthenticator implements Authenticator {

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        // 自定义认证逻辑
        String customHeader = context.getHttpRequest().getHeaders()
            .getFirstString("X-Custom-Auth");

        if (customHeader != null && validateCustomAuth(customHeader)) {
            context.success();
        } else {
            context.failure(AuthenticationFlowError.INVALID_USER);
        }
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        // 处理表单提交
    }

    @Override
    public void close() {
    }
}

自定义 Claims

Protocol Mapper

public class CustomClaimMapper extends AbstractOIDCProtocolMapper 
    implements OIDCAccessTokenMapper, OIDCIDTokenMapper {

    @Override
    protected void setClaim(IDToken token, ProtocolMapperModel mappingModel, 
                           UserSessionModel userSession) {
        // 添加自定义 Claim
        UserModel user = userSession.getUser();
        token.setOtherClaims("custom_claim", user.getFirstAttribute("custom_attribute"));
    }
}

配置 Mapper

/opt/keycloak/bin/kcadm.sh create protocol-mappers \
  -r my-app \
  --client my-client \
  -s name="custom-claim" \
  -s protocolMapper="oidc-custom-claim-mapper" \
  -s protocol="openid-connect" \
  -s consentRequired=false \
  -s config."claim.name"="custom_claim" \
  -s config."jsonType.label"="String"

Token 配置

Access Token 配置

/opt/keycloak/bin/kcadm.sh update realms/my-app \
  -s accessTokenLifespan=300 \
  -s ssoSessionMaxLifespan=36000 \
  -s ssoSessionIdleTimeout=1800 \
  -s offlineSessionMaxLifespan=5184000 \
  -s offlineSessionIdleTimeout=2592000

Refresh Token 配置

/opt/keycloak/bin/kcadm.sh update realms/my-app \
  -s refreshTokenMaxReuse=3 \
  -s revokeRefreshToken=true

API 集成

管理 API

import requests

class KeycloakAdmin:
    def __init__(self, server_url, realm, client_id, client_secret):
        self.server_url = server_url
        self.realm = realm
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None

    def get_admin_token(self):
        """获取管理令牌"""
        response = requests.post(
            f"{self.server_url}/realms/master/protocol/openid-connect/token",
            data={
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret
            }
        )
        self.token = response.json()["access_token"]
        return self.token

    def create_user(self, username, email, password):
        """创建用户"""
        headers = {"Authorization": f"Bearer {self.token}"}
        response = requests.post(
            f"{self.server_url}/admin/realms/{self.realm}/users",
            headers=headers,
            json={
                "username": username,
                "email": email,
                "enabled": True,
                "credentials": [{
                    "type": "password",
                    "value": password,
                    "temporary": False
                }]
            }
        )
        return response.status_code == 201

小结

服务端实现要点:

  • Keycloak 部署:Docker、Kubernetes
  • Realm 配置:客户端、用户
  • 认证流程:MFA、自定义认证器
  • Token 配置:生命周期、刷新

下一章我们将学习客户端集成。