起初接到项目的时候自己还不了解什么是Oauth2.0协议,项目大概的意思是要做一个对外开放的数据查询服务。由于我们是做医疗行业的公司,使用我们的系统的用户大都是固定的医院和机构,所以用户扩展速度非常慢,可以不做额外的注册功能,使用现有的登陆功能即可完成身份验证。
简单来说。就是用户正在使用第三方的平台,这时需要一些我们平台的数据,这个用户在第三方平台触发登陆授权,我们平台就允许第三方平台主动发起对应的数据查询请求,并返回结果。
原理网上真的是非常多,随便查一下就有了。我大致说下我的理解吧。
我找了个介绍的文章,可以自行查看 https://www.cnblogs.com/Wddpct/p/8976480.html
QQ开放平台文档:http://wiki.open.qq.com/wiki/website/开发攻略_Server-side
微博开放平台文档:https://open.weibo.com/authentication
调研过程中,主要选择了QQ和微博的授权登陆来看。
首先 QQ的登陆授权感觉要比别的复杂一点,复杂来源于QQ可以自动识别当前已经登陆的用户。作为前端小白,不明白其中的道理,所以感觉处理逻辑应该不简单。
用户在第三方网站点击QQ登陆授权按钮的时候,会跳转到一个授权网页。如果登陆了QQ的话就可以点击用户头像直接登陆,如果没有的话就要输入用户名密码来登陆。登陆后点击授权即可。
我感觉,这个授权就是页面来做登陆,登陆后拿到用户信息,然后给授权服务器传用户id之类的参数,授权服务器直接根据用户ID来绑定授权码。
微博的登陆页面个人感觉相对简单,可能是因为微博客户端没有qq那么普遍吧,它的登陆页面没有识别是否微博登陆,直接出现用户名密码的输入框,然后点击授权。根据用户名密码获取用户信息的操作都放在点击授权之后做了。
应该是授权服务器根据用户名密码去获取了一次用户信息,如果没问题就返回授权码。
知道了大概的原理,后边实现起来就没什么难度了。
使用 mysql 数据库,java 的 oauth 官方工具包
用户表 : 在授权方注册的第三方平台开发者或者公司
DROP TABLE IF EXISTS viewchaindb.open_user_account;
CREATE TABLE viewchaindb.open_user_account
(
id bigint(11) NOT NULL AUTO_INCREMENT,
user_name varchar(255) NOT NULL COMMENT 'app 账户名称',
user_type tinyint(1) NOT NULL comment '1-供应商 2-医院',
app_key varchar(255) NOT NULL COMMENT 'app 账户唯一id',
app_secret varchar(255) NOT NULL COMMENT 'app 密码',
gmt_create datetime NOT NULL COMMENT '创建时间',
gmt_modify datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
status tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态 0:关闭 1:开启,2:禁用',
ver int(255) NOT NULL DEFAULT 1,
description varchar(255) DEFAULT '' COMMENT '账户描述',
PRIMARY KEY (id),
KEY app_key (app_key),
KEY user_type (user_type)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='开放平台账户';
授权表 :在第三方平台操作的用户来授权服务后记录的绑定关系
DROP TABLE IF EXISTS viewchaindb.open_user_token;
CREATE TABLE viewchaindb.open_user_token
(
id bigint(11) NOT NULL AUTO_INCREMENT,
open_user_account_id bigint(11) NOT NULL COMMENT '开放平台用户账户id',
vh_user_id bigint(11) NOT NULL COMMENT '内部用户id',
auth_code varchar(255) DEFAULT NULL COMMENT 'app 授权码',
access_token varchar(255) DEFAULT NULL COMMENT '授权 token',
refresh_token varchar(255) DEFAULT NULL COMMENT '刷新 token',
token_expire_time datetime DEFAULT NULL COMMENT 'token 过期时间',
refresh_token_expire_time datetime DEFAULT NULL COMMENT 'refresh_token 过期时间',
ver int(255) NOT NULL DEFAULT 1,
PRIMARY KEY (id),
KEY vh_user_id (vh_user_id)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='开放平台账户授权';
package com.***.***.open.utils;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import java.util.UUID;
public class Oauth2Utils {
public static String generateCode() throws OAuthSystemException {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
return oauthIssuerImpl.authorizationCode();
}
public static String generateAccessToken() throws OAuthSystemException {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
return oauthIssuerImpl.accessToken();
}
public static String generateRefreshToken() throws OAuthSystemException {
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
return oauthIssuerImpl.refreshToken();
}
public static String generateAppKey() {
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
public static String generateAppSecret() throws OAuthSystemException {
UUID uuid = UUID.randomUUID();
MD5Generator md5Generator = new MD5Generator();
return md5Generator.generateValue(uuid.toString());
}
}
基础mybatis代码忽略
代码仅展示逻辑,声明、引包等操作都没有贴出,请根据命名自行推测字段含义。如有不明白的可以回复问我
1.授权码获取
/**
* 用户根据分配的账号获取授权码(使用一次,未设置过期时间,如设置可用redis)
* 授权码申请第一次保存到数据库 ,并返回给客户端
*
* @param request app_Key 账户key
* request app_secret 账户密码
* userId VIEWHIGH 用户id
* userName VIEWHIGH 用户名
* password VIEWHIGH 用户密码
* 用户id和用户名密码不能同时为空
* @return code 授权码
*/
@Transactional
GetAuthCodeResp getAuthorizationCode(GetAuthCodeReq request) throws OAuthSystemException {
String appKey = request.getAppKey();
String appSecret = request.getAppSecret();
Long userId = request.getUserId();
String userName = request.getUserName();
String password = request.getPassword();
log.info("请求授权,params:{}", request.toString());
if ((StringUtils.isBlank(userName) || StringUtils.isBlank(password)) && userId <= 0L) {
//todo 参数为空校验异常
throw new CheckedException("user info cannot be null");
}
OpenUserAccount openUserAccount = openUserAccountMapper.queryByAppKey(appKey, appSecret);
GetAuthCodeResp.Builder builder = GetAuthCodeResp.newBuilder();
if (openUserAccount == null) {
throw new CheckedException("No user info! Please check params( appKey and appSecret)");
}
String authCode = Oauth2Utils.generateCode();
if (StringUtils.isNotBlank(authCode)) {
OpenUserToken openUserToken = new OpenUserToken();
openUserToken.setAuthCode(authCode);
openUserToken.setOpenUserAccountId(openUserAccount.getId());
openUserToken.setUserId(getUserId(userName, password, userId));
openUserTokenMapper.addOpenUserToken(openUserToken);
builder.setAuthCode(authCode);
}
//todo 失败编码
return builder.build();
}
2.授权码换token
/**
* 根据授权码获取请求令牌 和 刷新令牌 并删除授权码
* 后边刷新token根据刷新令牌来获取和延长时间
* 刷新令牌失效需重新授权
*
* @param request appKey
* appSecret
* authCode 授权码
* @return accessToken 请求令牌
* refreshToken 刷新令牌
*/
@Transactional
GetAccessTokenResp getAccessToken(GetAccessTokenReq request) throws OAuthSystemException {
String appKey = request.getAppKey();
String authCode = request.getAuthCode();
log.info("Get accessToken with code, params : {}", request.toString());
OpenUserToken openUserToken = openUserTokenMapper.queryByCode(appKey, authCode, null);
if (openUserToken == null) {
throw new CheckedException("Code is Invalid");
}
GetAccessTokenResp.Builder builder = GetAccessTokenResp.newBuilder();
String accessToken = Oauth2Utils.generateAccessToken();
String refreshToken = Oauth2Utils.generateRefreshToken();
if (StringUtils.isBlank(accessToken) || StringUtils.isBlank(refreshToken)) {
throw new CheckedException("System exception! Cannot generate token! Please try again later.");
}
Date accessTokenExpiredTime = DateUtils.addSecond(new Date(), TEN_HOURS_MILLS);
Date refreshTokenExpiredTime = DateUtils.addDay(new Date(), HALF_YEAR_DAYS);
builder.setAccessToken(accessToken)
.setRefreshToken(refreshToken)
.setAccessTokenExpiredTime(
DateUtils.format(
accessTokenExpiredTime, FMT_DATETIME_WHOLE))
.setRefreshTokenExpiredTime(
DateUtils.format(
refreshTokenExpiredTime, FMT_DATETIME_WHOLE));
openUserToken.setAccessToken(accessToken);
openUserToken.setRefreshToken(refreshToken);
openUserToken.setTokenExpireTime(accessTokenExpiredTime);
openUserToken.setRefreshTokenExpireTime(refreshTokenExpiredTime);
openUserToken.setAuthCode(null);
openUserTokenMapper.updateOpenUserToken(openUserToken);
return builder.build();
}
3.刷新token
/**
* 刷新令牌,如果请求令牌未过期则重置失效时间,
* 如果请求令牌过期,刷新令牌未过期则重新生成新令牌
* 刷新令牌过期需要重新授权
*
* @param request
* @return
*/
@Transactional
RefreshTokenResp refreshToken(RefreshTokenReq request) throws OAuthSystemException {
String appKey = request.getAppKey();
String refreshToken = request.getRefreshToken();
log.info("Refresh access token ,params : {}", request.toString());
if (StringUtils.isBlank(appKey)) {
throw new CheckedException("AppKey cannot be null");
}
if (StringUtils.isBlank(refreshToken)) {
throw new CheckedException("RefreshToken cannot be null");
}
RefreshTokenResp.Builder builder = RefreshTokenResp.newBuilder();
OpenUserToken openUserToken = openUserTokenMapper.queryByCode(appKey, null, refreshToken);
if (openUserToken == null) {
throw new CheckedException("RefreshToken do not exist or appKey did not authorized");
}
if (openUserToken.getRefreshTokenExpireTime() == null
|| !openUserToken.getRefreshTokenExpireTime().after(new Date())) {
throw new CheckedException("RefreshToken is expired! Please retry authorize");
}
if (StringUtils.isBlank(openUserToken.getAccessToken()) || openUserToken.getTokenExpireTime() == null) {
throw new CheckedException("AccessToken has not been generated! Please retry authorized token");
}
Date accessTokenExpiredTime = DateUtils.addSecond(new Date(), TEN_HOURS_MILLS);
String accessToken = openUserToken.getAccessToken();
Date date = new Date();
if (openUserToken.getTokenExpireTime().before(date)) {
accessToken = Oauth2Utils.generateAccessToken();
}
openUserToken.setAccessToken(accessToken);
openUserToken.setTokenExpireTime(accessTokenExpiredTime);
openUserTokenMapper.updateOpenUserToken(openUserToken);
builder.setAccessToken(accessToken)
.setAccessTokenExpiredTime(DateUtils.format(
accessTokenExpiredTime, FMT_DATETIME_WHOLE))
.setRefreshToken(refreshToken)
.setRefreshTokenExpiredTime(DateUtils.format(openUserToken.getRefreshTokenExpireTime(), FMT_DATETIME_WHOLE));
return builder.build();
}