Keycloak

1. create realm

用于对接不同网站的登录用户,隔离区域

2. how to use

php

composer require stevenmaguire/oauth2-keycloak

SsoKeycloakClient.php

class SsoKeycloakClient implements SsoClientInterface
{
    use ApiConfigTrait;
    use ApiLoggerTrait;
    const API_NAME = "keycloak";

    private $server_access_token_path =  "/data/keycloak_server_access_token.log";

    /**
     * @var Keycloak
     */
    protected $provider;

    /**
     * @var GuzzleHttp\Client
     */
    private $client;


    public function __construct()
    {
        $this->initLogger(-3, 2);
        $this->loadApiConfig(self::API_NAME);
        try {
            $redirectUri = ShareSession::get('redirect_uri') ? ShareSession::get('redirect_uri') : $this->apiConfig->ExtraConfig['redirect_uri'];
        } catch (\Throwable $e) {
            $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        }
        $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        $this->provider = new Keycloak([
            'authServerUrl' => $this->apiConfig->Host,
            'realm' => $this->apiConfig->ExtraConfig['realm'],
            'clientId' => $this->apiConfig->ExtraConfig['login_client_id'],
            'clientSecret' => $this->apiConfig->ExtraConfig['login_client_secret'],
            'redirectUri' => $redirectUri,
        ]);
        $this->client = new GuzzleHttp\Client([
            'timeout' => 30.0,
        ]);
    }

    public function login()
    {

        $url = "{$this->apiConfig->Host}/realms/{$this->apiConfig->ExtraConfig['realm']}/protocol/openid-connect/auth";
        try {
            ShareSession::set("redirect_uri", $_SERVER['HTTP_REFERER']);
            $redirectUri = $_SERVER['HTTP_REFERER'];
        } catch (\Throwable $e) {
            $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        }

        //http://www.taoa.com/user/login.php
        $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        $p = [
            'client_id' => $this->apiConfig->ExtraConfig['login_client_id'],
//            'redirect_uri'=>$this->apiConfig->ExtraConfig['redirect_uri'],
            'redirect_uri' => $redirectUri,
            'state' => 'keycloak',
            'response_type' => 'code',
            'scope' => 'openid',
        ];
        return $url . "?" . http_build_query($p);
    }

    public function register()
    {

        $url = "{$this->apiConfig->Host}/realms/{$this->apiConfig->ExtraConfig['realm']}/protocol/openid-connect/registrations";
        try {
            ShareSession::set("redirect_uri", $_SERVER['HTTP_REFERER']);
            $redirectUri = $_SERVER['HTTP_REFERER'];
        } catch (\Throwable $e) {
            $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        }

        //http://www.taoa.com/user/login.php
        $redirectUri = $this->apiConfig->ExtraConfig['redirect_uri'];
        $p = [
            'client_id' => $this->apiConfig->ExtraConfig['login_client_id'],
//            'redirect_uri'=>$this->apiConfig->ExtraConfig['redirect_uri'],
            'redirect_uri' => $redirectUri,
            'state' => 'keycloak',
            'response_type' => 'code',
            'scope' => 'openid',
        ];
        return $url . "?" . http_build_query($p);
    }

    public function logout($user_id)
    {
        $action = "/admin/realms/{realms}/users/{$user_id}/logout";
        $resp = $this->request($action, []);
        return !$resp ? true : $resp['errorMessage'];
    }


    public function create($param)
    {
        $p = [
            "enabled" => true,
            "attributes" => [
                'user_id' => [$param['user_id']]
            ],
            "username" => $param['Username'],
            "emailVerified" => true,
            'email' => $param['Email'],
            "credentials" => [[
                'temporary' => false,
                'type' => 'password',
                'value' => $param['Password'],
            ]],
        ];
        $resp = $this->request("/admin/realms/{realms}/users", $p);
        return !$resp ? true : (isset($resp['errorMessage']) ? $resp['errorMessage'] : $resp['error']);
    }

    public function authorization_code($code)
    {
        try {
            $token = $this->provider->getAccessToken('authorization_code', [
                'code' => $code
            ]);
            return $token;
        } catch (\Throwable $e) {
            return $e->getMessage();
        }
    }

    public function userinfo($param)
    {
        if (isset($param['token']) && $param['token']) {
            $token = new AccessToken([
                'access_token' => $param['token'],
            ]);
        } elseif (isset($param['code']) && $param['code']) {
            $token = $this->authorization_code($param['code']);
        }
        if (is_string($token)) {
            return $token;
        }
        $user = $this->provider->getResourceOwner($token);
        return $user;
    }

    public function UpdateUser($remote_user_id, $param = [])
    {
        $p = [];
        if (isset($param['user_id'])&&$param['user_id']) {
            $p["attributes"] = [
                'user_id' => [$param['user_id']]
            ];
        }
        if (isset($param['email'])&&$param['email']) {
            $p["email"] = $param['email'];
        }
        if (isset($param['username'])&&$param['username']) {
            $p["username"] = $param['username'];
        }
        $resp = $this->request("/admin/realms/{realms}/users/{$remote_user_id}", $p,"PUT");
        return !$resp ? true : (isset($resp['errorMessage']) ? $resp['errorMessage'] : $resp['error']);
    }

    public function access_token($param)
    {
        $token = $this->provider->getAccessToken('authorization_code', [
            'code' => $param['code']
        ]);
        return $token;
    }

    public function server_access_token()
    {
        if (file_exists($this->server_access_token_path) && file_get_contents($this->server_access_token_path)) {
            $json = json_decode(file_get_contents($this->server_access_token_path), true);
            if ($json['expires_in'] > date("Y-m-d H:i:s", strtotime("+ 60 seconds"))) {
                return $json;
            }
        }
        try {
            $resp = $this->client->request("POST", "{$this->apiConfig->Host}/realms/{$this->apiConfig->ExtraConfig['realm']}/protocol/openid-connect/token",
                [
                    'form_params' => [
                        'client_id' => $this->apiConfig->ExtraConfig['api_clietnt_id'],
                        'client_secret' => $this->apiConfig->ExtraConfig['api_client_secret'],
                        'grant_type' => 'client_credentials',
                    ],
                ])->getBody()->getContents();
            $resp = json_decode($resp, true);
            file_put_contents($this->server_access_token_path, json_encode([
                'access_token' => $resp['access_token'],
                'expires_in' => date("Y-m-d H:i:s", strtotime("+ {$resp['expires_in']} seconds"))
            ]));
            return ['access_token' => $resp['access_token'],];
        } catch (\Throwable $e) {
            return $e->getMessage();
        }
    }

    public function request($action, $param,$method="POST")
    {

        $action = str_replace("{realms}", $this->apiConfig->ExtraConfig['realm'], $action);
        $server_access_token = $this->server_access_token();
        if (is_string($server_access_token)) {
            return $server_access_token;
        }
        try {
            $resp = $this->client->request($method, "{$this->apiConfig->Host}{$action}", [
                'json' => $param,
                'headers' => [
                    'Authorization' => "Bearer {$server_access_token['access_token']}",
                ],
            ])->getBody()->getContents();
            return $resp ? json_decode($resp, 1) : $resp;
        } catch (GuzzleHttp\Exception\RequestException $e) {
            return json_decode($e->getResponse()->getBody()->getContents(), 1);
        } catch (\Throwable $e) {
            return ['errorMessage' => $e->getMessage()];
        }
    }
}