资讯详情

微服务Token方案之ORY Hydra授权中心_Java实现

前言

网上微服务token流程案例很多,但是关于流程的案例很多。hydra但是资料很少,本文主要讲解使用获取令牌token如果你不知道流程Oauth建议百度先了解一下。

这里简单说一下

OAuth2.0授权代码是四种授权中最复杂、最完整、最严格的授权模式。它的特点是通过客户端的后台服务器和"授权服务商"交互认证服务器。

如上图所示,在授权码模式下,第三方软件可以获得资源所有者(用户)的授权码和注册时的授权码 client_id 和 client_secret 换回访问令牌 token 的值,这个token事实上,就像我们的核酸码一样,当核酸码过期时,需要刷新核酸码,重新获取核酸,否则门卫是不会让你进入公司大楼的。

一、hydra简单介绍

ORY Hydra经过强化,经过OpenID认证的OAuth 2.0服务器和OpenID Connect供应商优化了低延迟、高吞吐量和低资源消耗。ORY Hydra 不是身份提供商(用户注册、用户登录、密码重置过程),而是通过登录和同意应用程序连接到您现有的身份提供商。很容易用不同的语言登录和同意应用程序,并提供示例同意应用程序(Go,Node)和 SDK。

hydra源码下载

以上是官方原话:

hydra是go语言开发,支持高性能、高并发性,与其他实现OAuth框架的区别在于它实现了 OAuth 和 OpenID Connect 标准,但不强迫你使用hydra支持我们自定义用户登录和授权流程的用户管理(登录、注销、配置文件管理、注册)、特定模板引擎或预定义前端,只需要对接hydra可以提供标准接口。

二、hydra安装步骤

官方推荐使用PostgreSQL为了教程,我们在这里使用数据库doker在正式环境下不会安装数据库。

官方安装教程:Run Ory Hydra in Docker

# 1.为容器相互通信创建一个独立的网段

docker network create hydradev

# 2.拉取pg镜像 也可以使用mysql 官方推荐PostgreSQL9.6 、MySQL5.7 和SQLite

docker pull postgres:12.1

# 3.拉取 hydra docker pull oryd/hydra:v1.10.6

# 4.运行pg数据库挂载到本机(然后链接测试 帐号:hydradev 密码:1234) docker run -it -d \ --network hydradev \ --name hydra-dev-pg \ --restart=always \ -e POSTGRES_PASSWORD=1234 \ -e POSTGRES_USER=hydradev \ -p 5432:5432 \ -v /usr/local/postgres/pgdata:/var/lib/postgresql/data \ postgres:12.1

可以使用navicat测试工具是否成功启动:

建议在启动成功后建立一个新的模式来区分hydra我在这里新建了数据库hydradev

# 5.为加密数据库设置系统机密环境变量 export SECRETS_SYSTEM=hydra-dev-secret123456789

# 6.设置数据库url环境变量, 默认使用public模式 export DSN='postgres://hydradev:1234@hydra-dev-pg:5432/hydradev?sslmode=disable'

# 7.初始化hydra数据库 docker run -it --rm \ --network hydradev \ oryd/hydra:v1.10.6 \ migrate sql --yes $DSN

# 8.启动Hydra服务 docker run -it -d \ --restart=always \ --name ory-hydra-zsw-dev \ --network hydradev \ -p 4444:4444 \ -p 4445:4445 \ -e SECRETS_SYSTEM=$SECRETS_SYSTEM \ -e DSN=$DSN \ -e URLS_SELF_ISSUER=https://zsw-hydra.com/ \ -e URLS_CONSENT=https://zsw-cloud.com/auth/consent \ -e URLS_LOGIN=https://zsw-cloud.com/auth/login \ -e URLS_LOGOUT=https://zsw-cloud.com/auth/logout \ -e URLS_POST_LOGOUT_REDIRECT=https://zsw-cloud.com/auth/logout/callback \ -e TTL_ACCESS_TOKEN=5m \ -e TTL_REFRESH_TOKEN=10m \ -e TTL_ID_TOKEN=5m \ oryd/hydra:v1.10.6 serve all

# 参数说明 公共API端点服务监控端口号:默认为4444 admin API端点服务监控端口号默认为4445

URLS_SELF_ISSUER 授权服务器地址 URLS_CONSENT 用户同意授权地址 URLS_LOGIN 用户登录认证地址 URLS_LOGOUT 用户退出登录地址 URLS_POST_LOGOUT_REDIRECT 用户成功退出登录后跳转到地址

TTL_ACCESS_TOKEN 配置刷新令牌的有效时间。默认值为720h。设置为-1,使刷新令牌永不过期。 TTL_REFRESH_TOKEN 配置标志令有效时间。默认为1小时。 TTL_ID_TOKEN 配置标志令有效时间。默认为1小时。 TTL配置过期时间的设置单位 h m s 这里主要是为了演示效果,所以只设置了5分钟,正式环境不会设置这么短

hydra安装授权服务器地址hydra的机器ip映射路径

前端项目域名微服务

:微服务后台auth认证中心服务请求地址

hydra默认使用,由于https会有证书问题,所以我在这里用。 将https转换成http请求,当然,如果你有域名,你可以去免费申请ssl证书不需要像被迫的博主那样使用openssl本地申请证书。

当然,如果想直接使用http也是可以的,只需要在命令最后加上   相应的url也要改成http。

# 9.最后查看日志是否正常启动 docker logs ory-hydra-zsw-dev

这里推荐使用docker可视化工具查看,比命令行要方便很多,安装教程网上有很多,这个请自行百度:

 启动成功后hydra服务端就搭建成功了,为了我们后面的认证流程能够畅通无阻,这里建议

暂时关闭防火墙命令:systemctl stop firewalld

三、客户端相关接口

Hydra服务端搞定后,我们就需要操作客户端了,官方提供了对应的restful API文档

客户端可以这么理解:每个客户端都对应我们正常开发的dev、test、uat、prod环境。

Client API官方文档地址

以下代码中出现的JavaBean取自官方Java SDK

:https://zsw-hydra.com:4445/clients

:POST

变量值:

 

这里需要注意的https请求默认是开启ssl验证的,所以我在ResTemplate配置中设置了绕过ssl验证,不然请求会报错,具体配置参考: RestTemplate绕过SSL证书校验

细心的同学可能会注意到swagger的参数是驼峰式,而我的代码日志中是下划线分割的,这里我用Gson反序列化了一下,因为

  public OAuth2Client createOAuth2ClientCall(OAuth2Client oAuth2Client) {
        Gson gson = new Gson();
        String hydraAdminCreateOAuth2ClientCallUrl = adminUrl+"/clients";
        log.info("============POST================请求路径:{}", hydraAdminCreateOAuth2ClientCallUrl);
        String params = gson.toJson(oAuth2Client);
        log.info("============================/clients入参:{}", params);
        ResponseEntity<String> createAuth2ClientResponse = restTemplateIgnoreSSL.postForEntity(hydraAdminCreateOAuth2ClientCallUrl,  params, String.class);
        if (!ObjectUtils.isEmpty(createAuth2ClientResponse.getBody())){
            OAuth2Client oAuth2ClientResponse = gson.fromJson(createAuth2ClientResponse.getBody(), OAuth2Client.class);
            log.info("============================/clients出参:{}", gson.toJson(oAuth2ClientResponse));
            return oAuth2ClientResponse;
        }
        return null;
    }

下面是请求参数和响应值:


// 请求参数
{
    "client_id": "zsw-cloud-dev", 
    "client_name": "zsw-cloud-devName", 
    "client_secret": "507e1d29-1460-4eab-b706-f3b411bc1717", 
    "client_secret_expires_at": 0, 
    "frontchannel_logout_session_required": true, 
    "frontchannel_logout_uri": "https://zsw-cloud.com/auth/logout/callback", 
    "grant_types": [
        "authorization_code", 
        "refresh_token", 
        "implicit", 
        "client_credentials"
    ], 
    "post_logout_redirect_uris": [
        "https://zsw-cloud.com/auth/logout"
    ], 
    "redirect_uris": [
        "https://zsw-cloud.com/auth.html"
    ], 
    "response_types": [
        "code", 
        "id_token", 
        "token"
    ], 
    "scope": "openid offline offline_access", 
    "token_endpoint_auth_method": "client_secret_basic", 
    "userinfo_signed_response_alg": "none"
}


// 响应内容
{
    "allowed_cors_origins": [ ], 
    "audience": [ ], 
    "client_id": "zsw-cloud-dev", 
    "client_name": "zsw-cloud-devName", 
    "client_secret": "507e1d29-1460-4eab-b706-f3b411bc1717", 
    "client_secret_expires_at": 0, 
    "client_uri": "", 
    "created_at": "2022-07-20T11:42:37Z", 
    "frontchannel_logout_session_required": true, 
    "frontchannel_logout_uri": "https://zsw-cloud.com/auth/logout/callback", 
    "grant_types": [
        "authorization_code", 
        "refresh_token", 
        "implicit", 
        "client_credentials"
    ], 
    "jwks": { }, 
    "logo_uri": "", 
    "metadata": { }, 
    "owner": "", 
    "policy_uri": "", 
    "post_logout_redirect_uris": [
        "https://zsw-cloud.com/auth/logout"
    ], 
    "redirect_uris": [
        "https://zsw-cloud.com/auth.html"
    ], 
    "response_types": [
        "code", 
        "id_token", 
        "token"
    ], 
    "scope": "openid offline offline_access", 
    "subject_type": "public", 
    "token_endpoint_auth_method": "client_secret_basic", 
    "tos_uri": "", 
    "updated_at": "2022-07-20T11:42:36.909294Z", 
    "userinfo_signed_response_alg": "none"
}

这里简单说明一下几个重要的参数:

client_id:客户端id,全局唯一

client_secret:客户端密钥,会以加密的方式存进数据库

grant_types:授权类型

redirect_uris:code重定向路径(下面授权流程会介绍)

token_endpoint_auth_method:这个是认证密钥传递方式,有两种写法,官方推荐使用client_secret_basic,也可以写成client_secret_post(下面授权流程也会介绍)

创建完成后就可以看到数据库中表里已经存在我们新建的client了

:https://zsw-hydra.com:4445/clients?limit=?&offset=?

:GET

    public List<OAuth2Client> listOAuth2ClientsCall(Long limit, Long offset) {
        Gson gson = new Gson();
        String hydraAdminQueryClientListUrl = String.format(adminUrl + "/clients" + "?limit=%s&offset=%s",limit,offset);
        log.info("============GET================请求路径:{}", hydraAdminQueryClientListUrl);
        ResponseEntity<String> auth2ClientListResponse = restTemplateIgnoreSSL.getForEntity(hydraAdminQueryClientListUrl, String.class);
        if (!ObjectUtils.isEmpty(auth2ClientListResponse.getBody())){
            Type type = new TypeToken<List<OAuth2Client>>() {}.getType();
            return gson.fromJson(auth2ClientListResponse.getBody(), type);
        }
        return Collections.emptyList();
    }

:https://zsw-hydra.com:4445/clients/{id}

:GET

    public OAuth2Client getOAuth2ClientCall(String clientId) {
        Gson gson = new Gson();
        String hydraAdminQueryClientLByIdUrl = adminUrl + "/clients/{id}" ;
        ResponseEntity<String> auth2ClientResponse = restTemplateIgnoreSSL.getForEntity(hydraAdminQueryClientLByIdUrl, String.class,clientId);
        if (!ObjectUtils.isEmpty(auth2ClientResponse.getBody())){
            OAuth2Client oAuth2ClientResponse = gson.fromJson(auth2ClientResponse.getBody(), OAuth2Client.class);
            log.info("============================/clients/{id}:{}", oAuth2ClientResponse.toString());
            return oAuth2ClientResponse;
        }
        return null;
    }

:https://zsw-hydra.com:4445/clients/{id}

:PUT

    public OAuth2Client updateOAuth2ClientCall(String clientId, OAuth2Client oAuth2Client) {
        Gson gson = new Gson();
        String hydraAdminUpdateClientLByIdUrl = adminUrl + "/clients/{id}" ;
        HttpEntity<String> httpEntity = new HttpEntity<>(gson.toJson(oAuth2Client));
        ResponseEntity<String> updateAuth2ClientResponse = restTemplateIgnoreSSL.exchange(hydraAdminUpdateClientLByIdUrl, HttpMethod.PUT,httpEntity, String.class,clientId);
        if (!ObjectUtils.isEmpty(updateAuth2ClientResponse.getBody())){
            OAuth2Client oAuth2ClientResponse = gson.fromJson(updateAuth2ClientResponse.getBody(), OAuth2Client.class);
            log.info("==============PUT==============/clients/{id}:{}", oAuth2ClientResponse.toString());
            return oAuth2ClientResponse;
        }
        return null;
    }

:https://zsw-hydra.com:4445/clients/{id}

:DELETE

    public void deleteOAuth2ClientCall(String clientId) {
        String hydraAdminUpdateDeleteLByIdUrl = adminUrl + "/clients/{id}" ;
        restTemplateIgnoreSSL.delete(hydraAdminUpdateDeleteLByIdUrl, clientId);
    }

当然client相关还有其他接口,这里就不一一介绍了,具体可以参考官方API。client接口相对来说比较简单,都是crud基本功,下面主要介绍下授权流程。

四、认证授权流程及相关接口

先去官方取图:

 从上图可以看出,获取授权码一共需要两个流程:

下面我们就开始完整的演示一遍获取授权码的接口流程:

login_challenge:

https://zsw-hydra.com:4444/oauth2/auth?&client_id=zsw-cloud-dev&response_type=code&scope=openid&state=zsw123456789&redirect_uri=https://zsw-cloud.com/auth.html

 参数说明:

client_id:就是我们上面新建的客户端id

response_type:响应值类型

scope:作用范围

state:这个可以随便写

redirect_uri:授权码重定向url

通过官网api介绍可以看出这是一个302重定向请求,调用成功后会携带重定向到我们设置的URLS_LOGIN:

https://zsw-cloud.com/auth/login

代码截图:

这里我简单画了一个登录from,并且重定向到这个页面(主要是为了模拟登录场景)

登录页面如下

用户在前端输入用户密码后,点击登录会调用上面截图中的/userLogin接口,在用户登录接口中可以写一些账号密码、ip校验相关的校验,如果校验通过,下一步就是携带用户信息和login_challenge调用接口:

https://zsw-hydra.com:4445/oauth2/auth/requests/login/accept?login_challenge=cdf848bb89da4d56802dd1bb52c322c7

PUT

    public RequestWasHandledResponse acceptLoginRequestCall(String loginChallenge, AcceptLoginRequest acceptLoginRequest) {
        Gson gson = new Gson();
        HttpEntity<String> httpEntity = new HttpEntity<>(gson.toJson(acceptLoginRequest));

        String hydraAdminLoginAcceptUrl = adminUrl +"/oauth2/auth/requests/login/accept?login_challenge="+loginChallenge;
        log.info("============PUT================请求路径:{}", hydraAdminLoginAcceptUrl);
        log.info("============PUT================/login/accept请求参数:{}", gson.toJson(acceptLoginRequest));
        ResponseEntity<String> hydraAdminLoginAcceptResponse = restTemplateIgnoreSSL.exchange(hydraAdminLoginAcceptUrl, HttpMethod.PUT, httpEntity, String.class);
        log.info("============PUT================请求路径出参hydraAdminLoginAcceptResponse:{}", hydraAdminLoginAcceptResponse);
        if (!ObjectUtils.isEmpty(hydraAdminLoginAcceptResponse.getBody())){
            RequestWasHandledResponse loginAcceptResponse = gson.fromJson(hydraAdminLoginAcceptResponse.getBody(), RequestWasHandledResponse.class);
            return loginAcceptResponse;
        }
        return null;
    }

{"subject":"zsw"}

{"redirect_to":"https://zsw-hydra.com/oauth2/auth?client_id=zsw-cloud-dev\u0026login_verifier=317781532a8b4c4dabd27c4b533afb5a\u0026redirect_uri=https%3A%2F%2Fzsw-cloud.com%2Fauth.html\u0026response_type=code\u0026scope=openid\u0026state=zsw123456789"}

// 这里域名后面需要拼接上端口,实际响应值如下
https://zsw-hydra.com:4444/oauth2/auth?client_id=zsw-cloud-dev&login_verifier=317781532a8b4c4dabd27c4b533afb5a&redirect_uri=https://zsw-cloud.com/auth.html&response_type=code&scope=openid&state=zsw123456789

转换响应值后,Login认证流程就结束了,下一步就是重定向到响应值接口,进入Consent流程:

https://zsw-hydra.com:4444/oauth2/auth?client_id=zsw-cloud-dev&login_verifier=317781532a8b4c4dabd27c4b533afb5a&redirect_uri=https://zsw-cloud.com/auth.html&response_type=code&scope=openid&state=zsw123456789

该接口也是302请求,会携带重定向到我们设置的URLS_CONSENT:

https://zsw-cloud.com/auth/consent

代码截图:

 在/consent接口中我们需要携带和授权范围参数调用接口完成Consent流程:

https://zsw-hydra.com:4445/oauth2/auth/requests/consent/accept?consent_challenge=ae12fb06fecc46c885983f9451490140

PUT

    public RequestWasHandledResponse acceptConsentRequestCall(String consentChallenge, AcceptConsentRequest acceptConsentRequest) {
        Gson gson = new Gson();
        HttpEntity<String> httpEntity = new HttpEntity<>(gson.toJson(acceptConsentRequest));
        String hydraAdminConsentAcceptUrl = adminUrl +"/oauth2/auth/requests/consent/accept?consent_challenge="+consentChallenge;
        log.info("============PUT================请求路径:{}", hydraAdminConsentAcceptUrl);
        log.info("============PUT================/consent/accept入参:{}", gson.toJson(acceptConsentRequest));
        ResponseEntity<String> hydraAdminConsentAcceptResponse = restTemplateIgnoreSSL.exchange(hydraAdminConsentAcceptUrl, HttpMethod.PUT, httpEntity, String.class);
        log.info("============PUT================请求路径出参hydraAdminConsentAcceptResponse:{}", hydraAdminConsentAcceptResponse);
        if (!ObjectUtils.isEmpty(hydraAdminConsentAcceptResponse.getBody())){
            RequestWasHandledResponse consentAcceptResponse = gson.fromJson(hydraAdminConsentAcceptResponse.getBody(), RequestWasHandledResponse.class);
            return consentAcceptResponse;
        }
        return null;
    }

// 注意这里如果不加offline,后面的token接口不会返回刷新令牌
{"grant_scope":["openid","offline"]}

{"redirect_to":"https://zsw-hydra.com/oauth2/auth?client_id=zsw-cloud-dev\u0026consent_verifier=c61383e8186341ff923f7c338483b30a\u0026redirect_uri=https%3A%2F%2Fzsw-cloud.com%2Fauth.html\u0026response_type=code\u0026scope=openid\u0026state=zsw123456789"}

// 这里域名后面需要拼接上端口,实际响应值如下
https://zsw-hydra.com:4444/oauth2/auth?client_id=zsw-cloud-dev&consent_verifier=c61383e8186341ff923f7c338483b30a&redirect_uri=https://zsw-cloud.com/auth.html&response_type=code&scope=openid&state=zsw123456789

转换响应值后,Consent认证流程也结束了,下一步我们继续重定向到响应值接口:

https://zsw-hydra.com:4444/oauth2/auth?client_id=zsw-cloud-dev&consent_verifier=c61383e8186341ff923f7c338483b30a&redirect_uri=https://zsw-cloud.com/auth.html&response_type=code&scope=openid&state=zsw123456789

该接口会携带重定向到我们的redirect_uri地址,然后我们就可以通过授权码获取认证token了

重定向路径如下:

https://zsw-cloud.com/auth.html?code=Qk4jf3dZ_DSkAAtlbS9pTilVFTRCeAYHdPpUN-aSp50.ERmaVzBEgjbZ84FaGzQi4BMseKj8tz6kKVbBUh4PDiw&scope=openid+offline&state=zsw123456789

auth.html是我们的授权认证页面,这里我简单的画了一个

具体逻辑代码如下图:

 页面效果如下图:

得到授权码code后,下面进入token流程。

五、token相关接口

我们在授权流程结束后拿到code授权码,下一步就是调用后端接口获取token并返回给前端https://zsw-cloud.com/auth/login/callback

接口代码截图:

上面我在新建client时参数的区别就在于一种需要将client_secret放到请求体中,一种则是加密后放到请求头中。

下一步调用https://zsw-hydra.com:4444/oauth2/token获取token

    public Oauth2TokenResponse oauth2TokenCall(Oauth2TokenRequest tokenRequest, HttpHeaders httpHeaders) {
        Gson gson = new Gson();
        String hydraPublicTokenUrl = publicUrl + "/oauth2/token";
        log.info("============POST================请求路径:{}", hydraPublicTokenUrl);
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        map.put("grant_type", tokenRequest.getGrantType());
        map.put("client_id", tokenRequest.getClientId());
        map.put("redirect_uri", tokenRequest.getRedirectUri());
        if (!ObjectUtils.isEmpty(tokenRequest.getCode())){
            // 获取token
            map.put("code", tokenRequest.getCode());
        }
        if (!ObjectUtils.isEmpty(tokenRequest.getRefreshToken())){
            // 刷新token
            map.put("refresh_token", tokenRequest.getRefreshToken());
        }
        log.info("============POST================/oauth2/token入参:{}", gson.toJson(map));
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, httpHeaders);
        ResponseEntity<String> tokenResponse = restTemplateIgnoreSSL.exchange(hydraPublicTokenUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("============POST================/oauth2/token出参:{}", tokenResponse);
        if (!ObjectUtils.isEmpty(tokenResponse.getBody())){
            Oauth2TokenResponse oauth2TokenResponse = gson.fromJson(tokenResponse.getBody(), Oauth2TokenResponse.class);
            return oauth2TokenResponse;
        }
        return null;
    }

{
    "grant_type": [
        "authorization_code"
    ], 
    "client_id": [
        "zsw-cloud-dev"
    ], 
    "redirect_uri": [
        "https://zsw-cloud.com/auth.html"
    ], 
    "code": [
        "Qk4jf3dZ_DSkAAtlbS9pTilVFTRCeAYHdPpUN-aSp50.ERmaVzBEgjbZ84FaGzQi4BMseKj8tz6kKVbBUh4PDiw"
    ]
}

{
    "access_token": "nRNAO1g8LNMI2i_FJXQDoLxvHL7aLz4sILhWoL_de4w.EcGjSbAlCmsBSPlE_KtNk99AcFVaE_eqye0Sh41dXSk", 
    "expires_in": 299, 
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpjNDk2N2ZkYi0yYTg0LTRhNjAtODRlZi02M2Q3MGFmNTRmNzIifQ.eyJhdF9oYXNoIjoia05wVkVkWGhTa2U4MmdvSk81SUFqUSIsImF1ZCI6WyJ6c3ctY2xvdWQtZGV2Il0sImF1dGhfdGltZSI6MTY1ODYyOTIxOSwiZXhwIjoxNjU4NjI5NTI2LCJpYXQiOjE2NTg2MjkyMjYsImlzcyI6Imh0dHBzOi8venN3LWh5ZHJhLmNvbS8iLCJqdGkiOiI1ZmIwMWM4NC1mNTZmLTQ0MGUtYjFmZC1iOTBjODNhY2ZkODIiLCJyYXQiOjE2NTg2MjkyMTIsInNpZCI6IjU2OWY2ZDczLTgzZDEtNDBlOS04YmMxLWFlMjFhY2QzOGYyZCIsInN1YiI6InpzdyJ9.OaAlvwFY84BU2_fF8RxsXK_ueoURmvIMl_Xa7xZ566laeZdJ8GyONzrlGDSLwNNhdKV8Mcl3U8aNoGZDb5w3DRca9C0rqaedo-r4zMrsAZ-YNUAXvuv_Ga-n_MDPA2FxLF0vz1Til48jkbWhQ0QmJnT_m6DvUo4veVjtbU6Ggbz2-rYO7adW2rp1gf4I_AwwUOjfBtQmqZRPNvQIkX-Md-bQfhqnGikMEkeoZdYuZP3ags6H1cm3E8eMLyJk4kGXGkMosSKLE8LFh1HrXYQfCDwCVpL1dy_-b0ZKyj20RVVdusBzdb97MV4QFeKleuyGIRBXHI0etW9EELOVjPWcz59tuE29uToSopiEArFpeCotsh4nllFxqtvqRM4zh5ZMjf6MIHpm74IW8nVlXdCVjBjzZp3Lg3th7iWEDrZm_9tZ1o0SmYYwf9IbjjttrIaBbph-iTm5aijN6WHrKM0HNOcrERrcK4REcSFKueL46-yHRKmOhwXNROJHZQu3mTpZRO8BnR3eWBsRuFmVGLt8BKi8s_fAR7AI__WN1y8rek2_34LnAVrh8CJQnzBAIB-9y6AeGH8a9t_tqxkJWeLa8ohXVH8VTceKkCMNm_7x9vvhhqlb8lyVau9ktvkIgoalyGRmBf66FZQkxpDFht0XiC7ZGq9IusI-fDSIcGRuJa8", 
    "refresh_token": "emWnXUsZ43Sb9_eR0HyxirTltitFX0rvv2ouwRHdZ6U.4zKlg3iCdoHco02jryMHj432xzz0yQrh41zaQejp52M", 
    "scope": "openid offline", 
    "token_type": "bearer"
}

至此我们就获取到了访问令牌access_token,前端可以将令牌缓存在cookie或session中,相应的后台也会缓存,后面前端调用其他服务时携带令牌调用接口,后台校验根据token来判断是否放行。

光看接口大家可能不是特别明白,以下是具体的交互流程图:

以上就是获取token的整个流程。

当然token也有过期的时候,下面说一下刷新令牌,接口和上面获取token是同一个https://zsw-hydra.com:4444/oauth2/token,只是传参不同

{
    "grant_type": [
        "refresh_token"
    ], 
    "client_id": [
        "zsw-cloud-dev"
    ], 
    "redirect_uri": [
        "https://zsw-cloud.com/auth.html"
    ], 
    // refresh_token是获取token时的refresh_token,不是access_token
    "refresh_token": [
        "emWnXUsZ43Sb9_eR0HyxirTltitFX0rvv2ouwRHdZ6U.4zKlg3iCdoHco02jryMHj432xzz0yQrh41zaQejp52M"
    ]
}

{
    "access_token": "1ZhsazuFh-BLieBjJx6TsAGu2UxEvaG3obnbOTVdgNs.lF8mOodvdlNv9qWfBJHLEuONC3QdAub6WEoYBzMyEwo", 
    "expires_in": 299, 
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpjNDk2N2ZkYi0yYTg0LTRhNjAtODRlZi02M2Q3MGFmNTRmNzIifQ.eyJhdF9oYXNoIjoia3VMQU1hTml1RGV2aDdpZEJvckVPQSIsImF1ZCI6WyJ6c3ctY2xvdWQtZGV2Il0sImF1dGhfdGltZSI6MTY1ODYzMDQ0NSwiZXhwIjoxNjU4NjMwOTY1LCJpYXQiOjE2NTg2MzA2NjUsImlzcyI6Imh0dHBzOi8venN3LWh5ZHJhLmNvbS8iLCJqdGkiOiI2ODVmMGNjNi04NmIwLTQwMzktOGVkMi0xMjM4ZGNmYTQ1ZGUiLCJyYXQiOjE2NTg2MzA0NDEsInNpZCI6Ijc4NzhjOWU2LTNlYzgtNDNmNC05OWNmLTBjNTZiYzM5NjFkOCIsInN1YiI6InpzdyJ9.oIo3uPJ_ZrKCUGymqSwHqAODvIziZvA3lVMAoONdDtF038zklOrSrDQlcsuUBILw_KJWJ4Ugu1HtcRroNDbX7FOo81r8IlEOKLJAgEG_ZL7TKbbNO6X7NYYyklL-85_OAzaerNhtbqtnwrJh0B9CjZIBQJDaeq4IYXE-otAdqtUcPq6ioG8tkaSyGfWHRtU_sdAKV03jXuJ6FlxihGNyt1Ei-BQdJrzr9Vt7lq4DKE1SF3OoAO9Yz7EIDlEnqL7vlbvvKXXIbH3QniqRqUszFCBZKkYdzCG12UDw3k0QYaE070YjeyKou6ZS4udOpXhoudKZYCHHxyx-Gyms_SkiBVGbqQHbYIflDest5mHgnTWD8ilnelBE7N0SznPw9OFWMzSC_DX0fv2N79xZA8r_hsB_Ul7nU5sNaH6QtZcmo0QMqUgPFAT7KQ9SFlyZ8dbFH4E6RyJzf31uDjHMc1ifhOQApprG6oY6ckgv1emkpapQB-lnVYTu-CDtB3MW8mQzrtL0SX9CPAsqYPeQf2050NFxWHl7jp-THoykTsbXRQZZHt0P95Y2mc-1fWwJ0haPcGqYsGsht1Gvm-dcITmtptWCmy0oEb0z_n9uYN_gLbxdxEXoB92FzEZufkd8xUTyBaLhYajlki0NFKJiBHFssIG13QD9ZRARKiqorKYZURU", 
    "refresh_token": "PL-InQes9HaSgVMH__c2ZqYfN9NTww2pbfooP3aqCp8.aS69mBivZgjdtK2vheZEjStIbnS7r3yo15C4gxSxzFc", 
    "scope": "openid offline", 
    "token_type": "bearer"
}

刷新token需要注意的是之前的

下面说一下另外几个跟token相关的接口:

,可用于用户登出

https://zsw-hydra.com:4444/oauth2/revoke

// client_id和client_secret 放在请求头中
{
    "token": [
        "1ZhsazuFh-BLieBjJx6TsAGu2UxEvaG3obnbOTVdgNs.lF8mOodvdlNv9qWfBJHLEuONC3QdAub6WEoYBzMyEwo"
    ]
}

    public void revokeToken(String token) {
        Gson gson = new Gson();
        String client_id = "zsw-cloud-test";
        String client_secret = "507e1d29-1460-4eab-b706-f3b411bc1717";

        String hydraPublicRevokeTokenUrl = publicUrl + "/oauth2/revoke";
        log.info("============POST================请求路径:{}", hydraPublicRevokeTokenUrl);
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        map.put("token",Lists.newArrayList(token));
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setBasicAuth(client_id, client_secret);
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        log.info("============POST================/oauth2/revoke入参:{}", gson.toJson(map));
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, httpHeaders);
        ResponseEntity<String> revokeTokenResponse = restTemplateIgnoreSSL.exchange(hydraPublicRevokeTokenUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("============POST================/oauth2/revoke出参:{}", revokeTokenResponse);
        if (Objects.equals(revokeTokenResponse.getStatusCode(),HttpStatus.OK)){
            log.info("================================删除成功!=====token:{}",token);
        }

,可用于获取用户标识或校验token是否存活

https://zsw-hydra.com:4445/oauth2/introspect

{
    "token": [
        "1ZhsazuFh-BLieBjJx6TsAGu2UxEvaG3obnbOTVdgNs.lF8mOodvdlNv9qWfBJHLEuONC3QdAub6WEoYBzMyEwo"
    ]
}

{
    "active": true, 
    "scope": "openid offline", 
    "client_id": "zsw-cloud-dev", 
    "sub": "zsw", 
    "exp": 1658631946, 
    "iat": 1658631645, 
    "nbf": 1658631645, 
    "aud": [ ], 
    "iss": "https://zsw-hydra.com/", 
    "token_type": "Bearer", 
    "token_use": "access_token"
}

    public OAuth2TokenIntrospection introspectOAuth2TokenCall(OAuth2TokenIntrospectionRequest tokenIntrospectionRequest) {
        Gson gson = new Gson();
        String hydraAdminIntrospectTokenUrl = adminUrl + "/oauth2/introspect";
        log.info("============POST================请求路径:{}", hydraAdminIntrospectTokenUrl);
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        map.put("token",tokenIntrospectionRequest.getToken());
        if (!ObjectUtils.isEmpty(tokenIntrospectionRequest.getScope())){
            map.put("scope", Lists.newArrayList(tokenIntrospectionRequest.getScope()));
        }
        log.info("============POST================/oauth2/introspect入参:{}", gson.toJson(map));
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, httpHeaders);
        ResponseEntity<String> introspectTokenResponse = restTemplateIgnoreSSL.exchange(hydraAdminIntrospectTokenUrl, HttpMethod.POST, httpEntity, String.class);
        log.info("============POST================/oauth2/introspect出参:{}", introspectTokenResponse);
        if (!ObjectUtils.isEmpty(introspectTokenResponse.getBody())){
            OAuth2TokenIntrospection tokenIntrospection = gson.fromJson(introspectTokenResponse.getBody(), OAuth2TokenIntrospection.class);
            if (tokenIntrospection.getActive()){
                String iat = LocalDateTime.ofEpochSecond(tokenIntrospection.getIat(),0, ZoneOffset.of("+8")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                String exp = LocalDateTime.ofEpochSecond(tokenIntrospection.getExp(),0, ZoneOffset.of("+8")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                log.info("=====================================token生成时间:{}",iat);
                log.info("=====================================token过期时间:{}",exp);
                log.info("=====================================用户标识:{}",tokenIntrospection.getSub());
            }else {
                log.info("=====================================token已失效:{}",tokenIntrospectionRequest.getToken());
            }

            return tokenIntrospection;
        }

        return null;
    }

总结

以上就是博主总结的hydra获取授权token流程,由于网上关于hydra的资料非常少,中间碰到了很多大大小小的坑,在官网搜了半天才搜到。本文并没有讲述登录、续期、登出完整流程,只是说明一些API具体的交互过程与细节,实际如何搭配使用,还是要根据项目环境进行适配。大家如果在教程中遇到了那些问题也可以评论或者私信我,我看到后可以跟你一起去百度探索😁

说到最后推荐一款同样优秀的OAuth 2.0开源框架 Keycloak

相比于hydra网上资料要丰富很多,并且还带有可视化页面,感兴趣的同学可以自行了解一下。


本文参考:Ory Hydra 详解之入门

标签: 7jb4继电器3th2040

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台