2021SC@SDUSC
目录
一.引言
1.OAUTH认证授权三个步骤
1. 获取未授权的Request Token
2. 获取用户授权的Request Token
3. 用授权的Request Token换取Access Token
2.具体执行方式
A. 使用者(第三方软件)向OAUTH服务提供商请求未授权的Request Token。向Request Token URL发起请求。
B. OAUTH服务提供商同意使用者的请求,并向其颁发未经用户授权的oauth_token与对应的oauth_token_secret,并返回给使用者。
C. 使用者向OAUTH服务提供商请求用户授权的Request Token。向User Authorization URL发起请求,请求带上上步拿到的未授权的token与其密钥。
D. OAUTH服务提供商将引导用户授权。该过程可能会提示用户,你想将哪些受保护的资源授权给该应用。此步可能会返回授权的Request Token也可能不返回。如Yahoo OAUTH就不会返回任何信息给使用者。
E. Request Token 授权后,使用者将向Access Token URL发起请求,将上步授权的Request Token换取成Access Token。
F. OAUTH服务提供商同意使用者的请求,并向其颁发Access Token与对应的密钥,并返回给使用者。
G. 使用者以后就可以使用上步返回的Access Token访问用户授权的资源。
二.Oauth模块部分代码分析
1.proto
oauth:包括AuthorizationPayload 消息(包括userId和clientId),Authorization 消息中包括code授权码和AuthorizationPayload,SignTokenRes 包括 accessToken(访问令牌)refreshToken(刷新令牌)。
开放授权端服务OAuthService ,授权会话CreateAuthorization()输入令牌载体AuthorizationPayload,获取授权码AuthorizationCode;通过授权码获取会话信息,FindAuthorizationByCode (),返回的是Authorization;签发 accessToken 和 refreshToken
通过给出AuthorizationCode授权码, 返回授权令牌和刷新令牌;刷新令牌 RefreshAccessToken 给出RefreshAccessTokenReq返回新的RefreshAccessTokenRes;获取令牌载体数据GetAccessTokenPayload () 给出AccessToken 返回 AuthorizationPayload。
syntax = "proto3";
package turing.connect.oauth.v1;
message AuthorizationPayload {
string userId = 1;
string clientId = 2;
repeated string scopes = 3;
}
message Authorization {
string code = 1; // 授权码
AuthorizationPayload payload = 2;
}
message AuthorizationCode {
string code = 1;
}
message SignTokenRes {
// 一天就过期呢
string accessToken = 1;
// 过期时间有点长喔
string refreshToken = 2;
}
message RefreshAccessTokenReq {
string refreshToken = 1;
}
message RefreshAccessTokenRes {
string accessToken = 1;
// 原样传回
optional string refreshToken = 2;
}
message AccessToken {
string accessToken = 1;
}
// 开放授权端服务
service OAuthService {
// 创建授权会话,默认三分钟过期
rpc CreateAuthorization(AuthorizationPayload) returns (AuthorizationCode);
// 通过授权码获取会话信息
// 授权码过期将抛出错误
rpc FindAuthorizationByCode (AuthorizationCode) returns (Authorization);
// 签发 accessToken 和 refreshToken
rpc SignToken (AuthorizationCode) returns (SignTokenRes);
// 刷新 accessToken
rpc RefreshAccessToken (RefreshAccessTokenReq) returns (RefreshAccessTokenRes);
// 获取令牌载体数据
rpc GetAccessTokenPayload (AccessToken) returns (AuthorizationPayload);
}
user:包括UserData,SingInReq,SignInRes等message,SignIn方法传入参数类型SignInReq返回SignInRes,SignOut传入参数SignOutReq返回SignOutRes;FindById传入UserById返回UserData。
syntax = "proto3";
package turing.connect.user.v1;
message UserData {
string casId = 1;
string name = 3;
enum UserRole {
Student = 0;
Teacher = 1;
}
UserRole role = 4;
int32 grade = 5;
string workplace = 6;
string campus = 7;
string major = 8;
optional string type = 110;
}
message SignInReq {
string casId = 1;
string password = 2;
}
message SignInRes {}
message SignOutReq {
string casId = 1;
}
message SignOutRes {}
message UserById {
string casId = 1;
}
// 用户服务
service UserService {
rpc SignIn(SignInReq) returns (SignInRes);
rpc SignOut(SignOutReq) returns (SignOutRes);
rpc FindById(UserById) returns (UserData);
}
2.domain\models
AuthorizationCode类,首先构造器传入_code,_createdAt,_expiresIn(有效时间)三个参数并配置了三个参数的get方法,还有一个getValid方法,首先将传入的createdAt参数调用它的getTime()方法得到授权码的创建时间,然后将创建时间加上传入的_expiresIn参数得到有效截止时间,最后先用Date.now()方法得到现在时间,将有效截止时间减去现在时间,大于0说明还未过期则返回true,小于零则说明过期则返回false。
export class AuthorizationCode {
private _code: string;
private _createdAt: Date;
private _expiresIn: number;
constructor(code: string, expiresIn: number, createdAt = new Date()) {
this._code = code;
this._expiresIn = expiresIn;
this._createdAt = createdAt;
}
get code() {
return this._code;
}
get createdAt() {
return this._createdAt;
}
get expiresIn() {
return this._expiresIn;
}
get valid() {
const now = Date.now();
const expiresAt = this._createdAt.getTime() + this._expiresIn;
// 过期时间在现在之后则有效嘿嘿
return expiresAt - now > 0;
}
toString() {
return this._code;
}
}
authorization-id类:构造类接受String类型code或者上面编写的AuthorizationCode类的对象,先进行类型判断,如果是String,直接进行 this._code = code,如果是AuthorizationCode,则提取code。
import { AuthorizationCode } from './authorization-code';
export class AuthorizationId {
private _code: string;
constructor(code: string | AuthorizationCode) {
if (typeof code === 'string') {
this._code = code;
} else {
this._code = code.code;
}
}
toString() {
return this._code;
}
}
authorization-payload类:首先构造器得到userId,clientId, scopes: AuthorizationScope[]三个参数并赋值给私有变量,配置三个私有变量的get方法,其中AuthorizationScope类代码为enum AuthorizationScope {OpenId = 'openid',UserInfo = 'user-info'}。
import { AuthorizationScope } from './authorization-scope';
export class AuthorizationPayload {
private _userId: string;
private _clientId: string;
private _scopes: AuthorizationScope[];
constructor(userId: string, clientId: string, scopes: AuthorizationScope[]) {
this._userId = userId;
this._clientId = clientId;
this._scopes = scopes;
}
get userId() {
return this._userId;
}
get clientId() {
return this._clientId;
}
get scopes() {
return this._scopes;
}
}
先定义authorizatrepository接口,接口包括nextCode()方法得到授权码, idFromCode
findById()得到授权Id,findById()根据Id得到授权,save()将传入的授权保存,然后用Symbol函数将Authorization Repository生成新的类型 AuthorizationRepositoryToken。
解释一下Symbol:原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
最后用AuthorizationRepositoryImplement 类向Inject装饰器注入来实现AuthorizationRepositoryToken接口。
import { Inject } from '@nestjs/common';
import { Authorization } from './authorization';
import { AuthorizationCode } from './authorization-code';
import { AuthorizationId } from './authorization-id';
export interface AuthorizationRepository {
nextCode(): AuthorizationCode;
idFromCode(code: AuthorizationCode): AuthorizationId;
findById(id: AuthorizationId): Promise<Authorization | undefined>;
save(authorization: Authorization): Promise<void>;
}
export const AuthorizationRepositoryToken = Symbol('Authorization Repository');
export const AuthorizationRepositoryImplement = () => Inject(AuthorizationRepositoryToken);
authorization类:首先构造器得到三个参数并赋值给私有变量_id, _code,_payload,配置三个私有变量的get方法。这个类的主要目的是为了之后的授权。关于导入的AggregateRoot :在存储库模式的上下文中,聚合根是客户机代码从存储库中加载的唯一对象,存储库封装对子对象的访问,从调用者的角度来看,它会自动加载子对象,要么在加载根的同时,要么在实际需要它们的时候(如延迟加载)。可能有一个 order 对象,它封装了多个 lineitem 对象上的操作。客户端代码不会直接加载 lineitem 对象,只会加载包含这些对象的顺序,这些顺序将是域的这一部分的聚合根。
import { AggregateRoot } from '@nestjs/cqrs';
import { AuthorizationCode } from './authorization-code';
import { AuthorizationId } from './authorization-id';
import { AuthorizationPayload } from './authorization-payload';
export class Authorization extends AggregateRoot {
private _id: AuthorizationId;
private _code: AuthorizationCode;
private _payload: AuthorizationPayload;
constructor(code: AuthorizationCode, payload: AuthorizationPayload) {
super();
this._id = new AuthorizationId(code.code);
this._code = code;
this._payload = payload;
}
get id() {
return this._id;
}
get code() {
return this._code;
}
get payload() {
return this._payload;
}
}
三.总结
本次学习 我了解在SDU信息门户教务系统的Oauth认证授权获取授权码,授权Id还有对应的对授权的有效时间的验证以便后续进行相应的三步授权。目前实现了输入令牌载体AuthorizationPayload,获取授权码AuthorizationCode;通过给出AuthorizationCode授权码, 返回授权令牌和刷新令牌;刷新令牌 RefreshAccessToken, 给出RefreshAccessTokenReq返回新的RefreshAccessTokenRes;获取令牌载体数据GetAccessTokenPayload () 给出AccessToken 返回 AuthorizationPayload。而三步授权的实现则通过下一节application类中的具体方法来实现。Oauth是图灵认证授权中最重要的模块。本模块使用的框架是nestjs框架,编程语言是TypeScript。