SpringOauth2.0源码分析之Token持久化(五)

1.概述

前面几个章节所述内容如下:

  1. SpringOauth2.0源码分析之认证流程分析(一)
  2. SpringOauth2.0源码分析之ProviderManager(二)
  3. SpringOauth2.0源码分析之客户端认证(三)
  4. SpringOauth2.0源码分析之获取access_token(四)

本章节主要叙说Token的存储情况。默认的情况下,SpringOauth2.0 提供4种方式存储。第一种是提供了基于mysql的存储,第二种是基于redis的存储。第三种基于jvm的存储,第四种基于Jwt的存储方式。这里我们主要分析的是mysql的持久化和redis的持久化。首先分析下存储的实现类。然后分析下不同存储方式下,表元数据的信息。


2.Token存储的实现
2.1 token存储的接口详解

token的存储是通过TokenStore这个接口实现的,下面我们分析下TokenStore的方法参数。

public interface TokenStore {
//读取指定的用户身份认证
	OAuth2Authentication readAuthentication(OAuth2AccessToken token);
// 根据token读取指定的用户身份认证
	OAuth2Authentication readAuthentication(String token);
// 存储token信息和用户认证信息
	void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
// 根据tokenValue读取token信息
	OAuth2AccessToken readAccessToken(String tokenValue);
// 移除token信息
	void removeAccessToken(OAuth2AccessToken token);
// 存储刷新token信息
	void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);
// 读取刷新token信息
	OAuth2RefreshToken readRefreshToken(String tokenValue);
// 读取Token详细信息
	OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
// 通过客户端和用户名查询当前授权的所有token信息
	Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);
// 查询当前客户端下的所有用认证的token信息
	Collection<OAuth2AccessToken> findTokensByClientId(String clientId);
}

通过上一章节的分析,我们可以知道,当前存储的主键:token_id的生成规则是根据的:

	private static final String CLIENT_ID = "client_id";
	private static final String SCOPE = "scope";
	private static final String USERNAME = "username";

传输的这三个值做MD5生成的。所以,同一个客户端下,可以存在多个用户的token的信息。

2.2 TokenStore接口的实现详解

在这里插入图片描述
通过接口的实现可以得出,其主要的有四种方式来存储Token。

  1. RedisTokenStore 通过Redis的方式进行存储
  2. JdbcTokenStore 通过Jdbc序列化的方式进行存储
  3. InMemoryTokenStore 直接将当前的Token信息存储在JVM中。
  4. JwtTokenStroe 通过Jwt的方式进行存储

上面的四种方式进行Token的持久化存储。其中InMemoryTokenStore是将当前的token信息存储到jvm中,重启服务后当前token信息将不复存在。所以,只能在测试开发时候使用。


3.Token存储的元数据信息
3.1 mysql中token存储的元数据详解
CREATE SCHEMA IF NOT EXISTS `oauth2` DEFAULT CHARACTER SET utf8 ;
USE `oauth2` ;

-- -----------------------------------------------------
-- Table `oauth2`.`clientdetails`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`clientdetails` (
  `appId` VARCHAR(128) NOT NULL,
  `resourceIds` VARCHAR(256) NULL DEFAULT NULL,
  `appSecret` VARCHAR(256) NULL DEFAULT NULL,
  `scope` VARCHAR(256) NULL DEFAULT NULL,
  `grantTypes` VARCHAR(256) NULL DEFAULT NULL,
  `redirectUrl` VARCHAR(256) NULL DEFAULT NULL,
  `authorities` VARCHAR(256) NULL DEFAULT NULL,
  `access_token_validity` INT(11) NULL DEFAULT NULL,
  `refresh_token_validity` INT(11) NULL DEFAULT NULL,
  `additionalInformation` VARCHAR(4096) NULL DEFAULT NULL,
  `autoApproveScopes` VARCHAR(256) NULL DEFAULT NULL,
  PRIMARY KEY (`appId`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_access_token`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_access_token` (
  `token_id` VARCHAR(256) NULL DEFAULT NULL,
  `token` BLOB NULL DEFAULT NULL,
  `authentication_id` VARCHAR(128) NOT NULL,
  `user_name` VARCHAR(256) NULL DEFAULT NULL,
  `client_id` VARCHAR(256) NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL,
  `refresh_token` VARCHAR(256) NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_approvals`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_approvals` (
  `userId` VARCHAR(256) NULL DEFAULT NULL,
  `clientId` VARCHAR(256) NULL DEFAULT NULL,
  `scope` VARCHAR(256) NULL DEFAULT NULL,
  `status` VARCHAR(10) NULL DEFAULT NULL,
  `expiresAt` DATETIME NULL DEFAULT NULL,
  `lastModifiedAt` DATETIME NULL DEFAULT NULL)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_client_details`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_client_details` (
  `client_id` VARCHAR(128) NOT NULL,
  `resource_ids` VARCHAR(256) NULL DEFAULT NULL,
  `client_secret` VARCHAR(256) NULL DEFAULT NULL,
  `scope` VARCHAR(256) NULL DEFAULT NULL,
  `authorized_grant_types` VARCHAR(256) NULL DEFAULT NULL,
  `web_server_redirect_uri` VARCHAR(256) NULL DEFAULT NULL,
  `authorities` VARCHAR(256) NULL DEFAULT NULL,
  `access_token_validity` INT(11) NULL DEFAULT NULL,
  `refresh_token_validity` INT(11) NULL DEFAULT NULL,
  `additional_information` VARCHAR(4096) NULL DEFAULT NULL,
  `autoapprove` VARCHAR(256) NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_client_token`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_client_token` (
  `token_id` VARCHAR(256) NULL DEFAULT NULL,
  `token` BLOB NULL DEFAULT NULL,
  `authentication_id` VARCHAR(128) NOT NULL,
  `user_name` VARCHAR(256) NULL DEFAULT NULL,
  `client_id` VARCHAR(256) NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_code`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_code` (
  `code` VARCHAR(256) NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- -----------------------------------------------------
-- Table `oatuh2`.`oauth_refresh_token`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `oauth2`.`oauth_refresh_token` (
  `token_id` VARCHAR(256) NULL DEFAULT NULL,
  `token` BLOB NULL DEFAULT NULL,
  `authentication` BLOB NULL DEFAULT NULL)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
--------------------- 

表字段和元数据说明:

表名字段名字段说明
oauth_client_detailsclient_id主键,必须唯一,不能为空.
用于唯一标识每一个客户端(client);在注册时必须填写(也可由服务端自动生成).
对于不同的grant_type,该字段都是必须的.在实际应用中的另一个名称叫appKey,与client_id是同一个概念.
resource_ids客户端所能访问的资源id集合,多个资源时用逗号(,)分隔,如:"unity-resource,mobile-resource".
该字段的值必须来源于与security.xml中标签‹oauth2:resource-server的属性resource-id值一致.在security.xml配置有几个‹oauth2:resource-server标签,则该字段可以使用几个该值.
在实际应用中,我们一般将资源进行分类,并分别配置对应的‹oauth2:resource-server,如订单资源配置一个‹oauth2:resource-server,用户资源又配置一个‹oauth2:resource-server.当注册客户端时,根据实际需要可选择资源id,也可根据不同的注册流程,赋予对应的资源id.
client_secret用于指定客户端(client)的访问密匙;在注册时必须填写(也可由服务端自动生成).
对于不同的grant_type,该字段都是必须的.在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.
scope指定客户端申请的权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔,如:"read,write".
scope的值与security.xml中配置的‹intercept-urlaccess属性有关系.如‹intercept-url的配置为
‹intercept-url pattern="/m/**"access="ROLE_MOBILE,SCOPE_READ"/>
则说明访问该URL时的客户端必须有read权限范围.write的配置值为SCOPE_WRITE,trust的配置值为SCOPE_TRUST.
在实际应该中,该值一般由服务端指定,常用的值为read,write.
authorized_grant_types指定客户端支持的grant_type,可选值包括authorization_code,password,refresh_token,implicit,client_credentials,若支持多个grant_type用逗号(,)分隔,如:"authorization_code,password".
在实际应用中,当注册时,该字段是一般由服务器端指定的,而不是由申请者去选择的,最常用的grant_type组合有:"authorization_code,refresh_token"(针对通过浏览器访问的客户端);"password,refresh_token"(针对移动设备的客户端).
implicitclient_credentials在实际中很少使用.
web_server_redirect_uri客户端的重定向URI,可为空,当grant_type为authorization_codeimplicit时,在Oauth的流程中会使用并检查与注册时填写的redirect_uri是否一致.下面分别说明:
  • 当grant_type=authorization_code时,第一步从spring-oauth-server获取'code'时客户端发起请求时必须有redirect_uri参数,该参数的值必须与web_server_redirect_uri的值一致.第二步用'code'换取'access_token'时客户也必须传递相同的redirect_uri.
    在实际应用中,web_server_redirect_uri在注册时是必须填写的,一般用来处理服务器返回的code,验证state是否合法与通过code去换取access_token值.
    spring-oauth-client项目中,可具体参考AuthorizationCodeController.java中的authorizationCodeCallback方法.
  • 当grant_type=implicit时通过redirect_uri的hash值来传递access_token值.如:
    http:
    然后客户端通过JS等从hash值中取到access_token值.
authorities指定客户端所拥有的Spring Security的权限值,可选,若有多个权限值,用逗号(,)分隔,如:"ROLE_UNITY,ROLE_USER".
对于是否要设置该字段的值,要根据不同的grant_type来判断,若客户端在Oauth流程中需要用户的用户名(username)与密码(password)的(authorization_code,password),
则该字段可以不需要设置值,因为服务端将根据用户在服务端所拥有的权限来判断是否有权限访问对应的API.
但如果客户端在Oauth流程中不需要用户信息的(implicit,client_credentials),
则该字段必须要设置对应的权限值,因为服务端将根据该字段值的权限来判断是否有权限访问对应的API.

(请在spring-oauth-client项目中来测试不同grant_type时authorities的变化)

access_token_validity设定客户端的access_token的有效时间值(单位:秒),可选,若不设定值则使用默认的有效时间值(60*60*12,12小时).
在服务端获取的access_token JSON数据中的expires_in字段的值即为当前access_token的有效时间值.
在项目中,可具体参考DefaultTokenServices.java中属性accessTokenValiditySeconds.
在实际应用中,该值一般是由服务端处理的,不需要客户端自定义.
refresh_token_validity设定客户端的refresh_token的有效时间值(单位:秒),可选,若不设定值则使用默认的有效时间值(60*60*24*30,30天).
若客户端的grant_type不包括refresh_token,则不用关心该字段在项目中,可具体参考DefaultTokenServices.java中属性refreshTokenValiditySeconds.

在实际应用中,该值一般是由服务端处理的,不需要客户端自定义.
additional_information这是一个预留的字段,在Oauth的流程中没有实际的使用,可选,但若设置值,必须是JSON格式的数据,如:
{"country":"CN","country_code":"086"}
按照spring-security-oauth项目中对该字段的描述
Additional information for this client,not need by the vanilla OAuth protocol but might be useful,for example,for storing descriptive information.
(详见ClientDetails.javagetAdditionalInformation()方法的注释)在实际应用中,可以用该字段来存储关于客户端的一些其他信息,如客户端的国家,地区,注册时的IP地址等等.
create_time数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
archived用于标识客户端是否已存档(即实现逻辑删除),默认值为'0'(即未存档).
对该字段的具体使用请参考CustomJdbcClientDetailsService.java,在该类中,扩展了在查询client_details的SQL加上archived=0条件(扩展字段)
trusted设置客户端是否为受信任的,默认为'0'(即不受信任的,1为受信任的).
该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为0,则会跳转到让用户Approve的页面让用户同意授权,
若该字段为1,则在登录后不需要再让用户Approve同意授权(因为是受信任的).
对该字段的具体使用请参考OauthUserApprovalHandler.java.(扩展字段)
autoapprove设置用户是否自动Approval操作,默认值为'false',可选值包括'true','false','read','write'.
该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为'true'或支持的scope值,则会跳过用户Approve的页面,直接授权.
该字段与trusted有类似的功能,是spring-security-oauth2的2.0版本后添加的新属性.

在项目中,主要操作oauth_client_details表的类是JdbcClientDetailsService.java,更多的细节请参考该类.
也可以根据实际的需要,去扩展或修改该类的实现.

oauth_client_tokencreate_time数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
token_id从服务器端获取到的access_token的值.
token这是一个二进制的字段,存储的数据是OAuth2AccessToken.java对象序列化后的二进制数据.
authentication_id该字段具有唯一性,是根据当前的username(如果有),client_id与scope通过MD5加密生成的.
具体实现请参考DefaultClientKeyGenerator.java类.
user_name登录时的用户名
client_id

该表用于在客户端系统中存储从服务端获取的token数据,在spring-oauth-server项目中未使用到.
oauth_client_token表的主要操作在JdbcClientTokenServices.java类中,更多的细节请参考该类.

oauth_access_tokencreate_time数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
token_id该字段的值是将access_token的值通过MD5加密后存储的.
token存储将OAuth2AccessToken.java对象序列化后的二进制数据,是真实的AccessToken的数据值.
authentication_id该字段具有唯一性,其值是根据当前的username(如果有),client_id与scope通过MD5加密生成的.具体实现请参考DefaultAuthenticationKeyGenerator.java类.
user_name登录时的用户名,若客户端没有用户名(如grant_type="client_credentials"),则该值等于client_id
client_id
authentication存储将OAuth2Authentication.java对象序列化后的二进制数据.
refresh_token该字段的值是将refresh_token的值通过MD5加密后存储的.

在项目中,主要操作oauth_access_token表的对象是JdbcTokenStore.java.更多的细节请参考该类.

oauth_refresh_tokencreate_time数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
token_id该字段的值是将refresh_token的值通过MD5加密后存储的.
token存储将OAuth2RefreshToken.java对象序列化后的二进制数据.
authentication存储将OAuth2Authentication.java对象序列化后的二进制数据.

在项目中,主要操作oauth_refresh_token表的对象是JdbcTokenStore.java.(与操作oauth_access_token表的对象一样);更多的细节请参考该类.
如果客户端的grant_type不支持refresh_token,则不会使用该表.

oauth_codecreate_time数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)
code存储服务端系统生成的code的值(未加密).
authentication存储将AuthorizationRequestHolder.java对象序列化后的二进制数据.

在项目中,主要操作oauth_code表的对象是JdbcAuthorizationCodeServices.java.更多的细节请参考该类.
只有当grant_type为"authorization_code"时,该表中才会有数据产生;其他的grant_type没有使用该表.

3.2 redis中token存储的元数据详解

数据存储在redis中,并不像存储在mysql中那样可以做关联查询,并且根据redis中的数据结构。SpringOauth2.0 在redis中的存储结构如下:

  • auth_to_access
    OAuth2Authentication相关信息加密后的值,value为string结构
    这个主要是通过OAuth2Authentication来获取OAuth2AccessToken
  • auth:token
    value为string结构
    这个主要用来获取token的OAuth2Authentication,用来获取相应的权限信息
  • client_id_to_access:clientId
    value为list结构
    这个主要是存储了每个clientId申请的OAuth2AccessToken的集合
    方便用来审计和应急处理跟clientId相关的token
  • access:token
    value为string
    这个主要是通过token值来获取OAuth2AccessToken
  • uname_to_access:clientId:userId,
    value的结构是list
    存储OAuth2AccessToken的集合
    主要是为了通过clientId,userId来获取OAuth2AccessToken集合,方便用来获取及revoke approval

结语

通过上面的分析和元数据描述,基本了解了SpringOauth2.0分别在mysql和redis中的持久化数据结构。个人理解这里框架本身直接将java对象通过java本身的序列化的方式进行存储,这样需要更多的存储空间以及带宽。同时反序列化需要花费更多的cpu性能。可能觉得需要改进的地方

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值