第五章:服务端实现¶
本章介绍如何实现 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 配置:生命周期、刷新
下一章我们将学习客户端集成。