IBPS认证服务实现原理
本文描述IBPS认证服务怎么实现的;
原理
什么是 OAuth 2.0
OAuth 2.0 是一个行业的标准授权协议。OAuth 2.0 专注于简化客户端开发人员,同时为 Web 应用程序,桌面应用程序,手机和客厅设备提供特定的授权流程。
它的最终目的是为第三方应用颁发一个有时效性的令牌 token。使得第三方应用能够通过该令牌获取相关的资源。常见的场景就是:第三方登录。当你想要登录某个论坛,但没有账号,而这个论坛接入了如 QQ、Facebook 等登录功能,在你使用 QQ 登录的过程中就使用的 OAuth 2.0 协议。
如果你想了解更多,其官方网址为:https://oauth.net/2/ 。下面我们来了解下 OAuth 协议的基本原理
角色
首先需要介绍的是 OAuth 2.0 协议中的一些角色,整个授权协议的流程都将围绕着这些角色:
- resource owner,资源所有者,能够允许访问受保护资源的实体。如果是个人,被称为 end-user。
- resource server,资源服务器,托管受保护资源的服务器。
- client,客户端,使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。如:web网站,移动应用等。
- authorization server,授权服务器,能够向客户端颁发令牌。
- user-agent,用户代理,帮助资源所有者与客户端沟通的工具,一般为 web 浏览器,移动 APP 等。
举例说明:假如我想要在 coding.net 这个网站上用 github.com 的账号登录。那么 coding 相对于 github 就是一个客户端。而我们用什么操作的呢?浏览器,这就是一个用户代理。当从 github 的授权服务器获得 token 后,coding 是需要请求 github 账号信息的,从哪请求?从 github 的资源服务器。
协议流程
上图详细的描述了这四个角色之间的步骤流程:
- (A) Client 请求 Resource Owner 的授权。授权请求可以直接向 Resource Owner 请求,也可以通过 Authorization Server 间接的进行。
- (B) Client 获得授权许可。
- (C) Client 向 Authorization Server 请求访问令牌。
- (D) Authorization Server 验证授权许可,如果有效则颁发访问令牌。
- (E) Client 通过访问令牌从 Resource Server 请求受保护资源。
- (F) Resource Server 验证访问令牌,有效则响应请求。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Figure 1: Abstract Protocol Flow
授权
一个客户端想要获得授权,就需要先到服务商那注册你的应用。一般需要你提供下面这些信息:
- 应用名称
- 应用网站
- 重定向 URI 或回调 URL(redirect_uri)
重定向 URI 是服务商在用户授权(或拒绝)应用程序之后重定向用户的地址,因此也是用于处理授权代码或访问令牌的应用程序的一部分。在你注册成功之后,你会从服务商那获取到你的应用相关的信息:
- 客户端标识 client_id
- 客户端密钥 client_secret
- client_id 用来表识客户端(公开),client_secret 用来验证客户端身份(保密)。
授权类型
OAuth 2.0 列举了四种授权类型,分别用于不同的场景:
Authorization Code(授权码 code):服务器与客户端配合使用。
Implicit(隐式 token):用于移动应用程序或 Web 应用程序(在用户设备上运行的应用程序)。
Resource Owner Password Credentials(资源所有者密码凭证 password):资源所有者和客户端之间具有高度信任时(例如,客户端是设备的操作系统的一部分,或者是一个高度特权应用程序),以及当其他授权许可类型(例如授权码)不可用时被使用。
Client Credentials(客户端证书 client_credentials):当客户端代表自己表演(客户端也是资源所有者)或者基于与授权服务器事先商定的授权请求对受保护资源的访问权限时,客户端凭据被用作为授权许可。
提供了哪些客户端?
原生客户端,完全基于IBPS框架的实现、底层使用feignclient;
Filter客户端,基于第三方框架的实现(轻度依赖IBPS基础框架)、底层使用httpclient;
Servlet客户端,基于Servlet(Filter)实现(适用老系统,如jsp系统)、带跳转、底层使用httpclient;
原生客户端
实现
具体实现可以看ibps-oauth-client2
、ibps-oauth-client-local
模块
# com/lc/ibps/cloud/oauth/client/config/OauthFilterInitialConfigure.java
# 注入相关filter
@Bean
public FilterRegistrationBean<Filter> logRegistration() {
FilterRegistrationBean<Filter> log = new FilterRegistrationBean<Filter>();
log.setFilter(logFilter());
log.addUrlPatterns("/*");
log.setName("logFilter");
log.setOrder(0);
return log;
}
@Bean
public Filter logFilter() {
return new LogFilter();
}
@Bean
public FilterRegistrationBean<Filter> gatewayRegistration() {
FilterRegistrationBean<Filter> gateway = new FilterRegistrationBean<Filter>();
gateway.setFilter(gatewayFilter());
gateway.addUrlPatterns("/*");
gateway.setName("gatewayFilter");
gateway.setOrder(1);
return gateway;
}
@Bean
public Filter gatewayFilter() {
return new GatewayFilter();
}
@Bean
public FilterRegistrationBean<Filter> whiteIpRegistration() {
FilterRegistrationBean<Filter> gateway = new FilterRegistrationBean<Filter>();
gateway.setFilter(whiteIpFilter());
gateway.addUrlPatterns("/*");
gateway.setName("whiteIpFilter");
gateway.setOrder(2);
return gateway;
}
@Bean
public Filter whiteIpFilter() {
return new ClientWhiteIpFilter();
}
@Bean
public FilterRegistrationBean<Filter> accessTokenGeneratorRegistration() {
FilterRegistrationBean<Filter> gateway = new FilterRegistrationBean<Filter>();
gateway.setFilter(accessTokenGeneratorFilter());
gateway.addUrlPatterns("/*");
gateway.setName("accessTokenGeneratorFilter");
gateway.setOrder(10);
return gateway;
}
@Bean
public Filter accessTokenGeneratorFilter() {
return new AccessTokenGeneratorFilter();
}
@Bean
public FilterRegistrationBean<Filter> accessTokenValidatorRegistration() {
FilterRegistrationBean<Filter> gateway = new FilterRegistrationBean<Filter>();
gateway.setFilter(accessTokenValidatorFilter());
gateway.addUrlPatterns("/*");
gateway.setName("accessTokenValidatorFilter");
gateway.setOrder(20);
return gateway;
}
@Bean
public Filter accessTokenValidatorFilter() {
return new AccessTokenValidatorFilter();
}
@Bean
public FilterRegistrationBean<Filter> permissionValidatorRegistration() {
FilterRegistrationBean<Filter> gateway = new FilterRegistrationBean<Filter>();
gateway.setFilter(permissionValidatorFilter());
gateway.addUrlPatterns("/*");
gateway.setName("permissionValidatorFilter");
gateway.setOrder(30);
return gateway;
}
@Bean
public Filter permissionValidatorFilter() {
return new PermissionValidatorFilter();
}
@Bean
public ITenantTokenService defaultTenantTokenService() {
return new DefaultTenantTokenService();
}
# com/lc/ibps/cloud/oauth/client/service/TokenVerify.java
# 注入oauth2server相关service进行调用
@Autowired
protected AuthorizationConfig authorizationConfig;
@Autowired
private ITokenService tokenService;
@Autowired
private IUserService userService;
使用
可参考骨架包中的provider-skeleton
# 添加pom依赖
<dependency>
<groupId>com.lc.ibps.cloud</groupId>
<artifactId>ibps-oauth-client-feignclient</artifactId>
</dependency>
Filter客户端
实现
具体实现可以看ibps-oauth-client2
、ibps-oauth-client-httpclient
模块
# 与原生客户端的区别在于,通过HTTPclient方式去调用,而不是通过注入service
# com/lc/ibps/cloud/oauth/client/service/TokenVerify.java
@Autowired
protected AuthorizationConfig authorizationConfig;
@SuppressWarnings("unchecked")
public APIResult<TokenEntity> accessToken(String grantType, String clientId, String clientSecret, String username,
String password, String refreshToken, String authorizeCode, String redirectUri){
AccessTokenVo vo = new AccessTokenVo(grantType, clientId, clientSecret, username, password, refreshToken, authorizeCode, redirectUri);
logger.debug("accessTokenUrl =====> {}", authorizationConfig.getHttpAccessTokenUrl());
String responseData = ApacheHttpClient.doPost(authorizationConfig.getHttpAccessTokenUrl(), vo, ApacheHttpClient.HearderBuilder.create()
.a(ParameterKey.HEADER_INNER, SecrectUtil.getInnerSecretValue())
.build());
return JacksonUtil.getDTO(responseData, APIResult.class);
}
使用
可参考骨架包中的provider-skeleton-spring-boot
# 添加pom依赖
<dependency>
<groupId>com.lc.ibps.cloud</groupId>
<artifactId>ibps-oauth-client-httpclient</artifactId>
</dependency>
Servlet客户端
实现
具体实现可以看oauth-client-filterclient
、oauth-client-filterbase
模块
# com/lc/ibps/cloud/oauth/client/service/TokenVerify.java
# 基于Servlet(Filter)实现
public static boolean verify(String accessToken) {
logger.debug("accessTokenVerifyUrl =====> {}", SsoBaseUtil.getSso().getHttpAccessTokenVerifyUrl());
Map<String, Object> params = new HashMap<>();
params.put(SsoBaseUtil.getSso().getHttpAccessTokenKey(), accessToken);
String responseData = HttpUtil.post(SsoBaseUtil.getSso().getHttpAccessTokenVerifyUrl(), params);
if(JSONUtil.isJsonObj(responseData)) {
JSONObject responseJsonObj = JSONUtil.parseObj(responseData);
int state = responseJsonObj.get("state", Integer.class);
if(200 == state) {
Map<String, Object> variables = (Map<String, Object>) responseJsonObj.get("variables", Map.class);
String grantType = variables.get("grant.type").toString();
String username = responseJsonObj.get("data", String.class);
String clientIdObj = variables.get("clientId").toString();
String clientId = BeanUtil.isEmpty(clientIdObj) ? "" : clientIdObj.toString();
logger.debug("authorization grant type is {}.", grantType);
logger.debug("authorization value is {}.", username);
Context.setClientId(clientId);
Context.setAccessToken(accessToken);
Context.setUsername(username);
context(accessToken);
return true;
}
}
return false;
}
使用
可参考骨架包中的provider-skeleton-spring-boot-sample
# 添加pom依赖
<dependency>
<groupId>com.lc.ibps.cloud</groupId>
<artifactId>ibps-oauth-client-filterclient</artifactId>
</dependency>