IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SDU信息门户(2)图灵认证授权子系统:用户登录 -> 正文阅读

[Java知识库]SDU信息门户(2)图灵认证授权子系统:用户登录

2021SC@SDUSC

目录

一.引言

1.传统登录方式弊端

2.OAuth 系统设计简介

? ? ? OAuth 协议概述:

二.用户登录部分代码分析

1.proto

2.application

(1)commands

(2)图灵认证系统的环境配置

(3)queries

3.domain

? ?models

4.infrastructure

从mangoDB中查询用户信息

将用户信息存储在mangoDB

三.总结


一.引言

1.传统登录方式弊端

? ? ? ?在传统应用中,如果想要拿到用户信息,第三方应用往往通过用户名 username 与密码 password 直接向用户服务器获取,此种方式将导致用户数据不安全,山东大学官方系统自前年起也通过 CAS 系统解决了这一系列问题。

2.OAuth 系统设计简介

? ? ? ? ? OAuth 是一个开放协议标准,协议标准为 RFC 6749。

? ? ? OAuth 协议概述:

? ? ? ?OAuth 分为若干种授权方式,最主要的是授权码方式与刷新令牌方式。
? ? ? ?用户访问第三方客户端应用后,第三方客户端请求用户授权,跳转至 OAuth 系统授权端点,用户同意授权后将获得授权码,用户将 OAuth 系统提供的授权码返回给第三方客户端应用,第三方应用凭借授权码去令牌端点换取获取令牌 AccessToken 或刷新令牌 Refresh Token。之后,第三方客户端应用拿到令牌,想要获取用户信息时通过 Access Token 获取即可。因为 用户信息较为敏感,所以 Access Token 失效时间十分短暂,因此需要通过 Refresh Token在系统中刷新 Access Token 以减少系统被攻击的可能性。

二.用户登录部分代码分析

1.proto

proto部分确定客户端服务相关方法:CreateClient(创建用户),FindById(ClientById) (根据用户ID寻找用户),ValidateClient(用户授权)

syntax = "proto3";

package turing.connect.client.v1;

message CreateClientReq {
  string name = 1;
  string logoUri = 2;
  repeated string scopes = 3;
  repeated string redirectUris = 4;
}

message CreateClientRsp {
  string id = 1;
  string secret = 2;
}

message ClientById {
  string id = 1;
}

message ClientData {
  string id = 1;
  string name = 2;
  string logoUri = 3;
  repeated string scopes = 4;
  repeated string redirectUris = 5;
}

message ValidateClientReq {
  string id = 1;
  string secret = 2;
}

message ValidateClientRsp {
  bool valid = 1;
  optional string error = 2;
}

// 客户端服务
service ClientService {
  rpc CreateClient(CreateClientReq) returns (CreateClientRsp);
  rpc FindById(ClientById) returns (ClientData);
  rpc ValidateClient(ValidateClientReq) returns (ValidateClientRsp);
}

2.application

(1)commands

实现@nestjs/cqrs中的ICommand接口

创建用户结构体并export:结构体的属性是只读的,已经写死,防止被更改

import { ICommand } from '@nestjs/cqrs';
import { AuthScope } from 'src/domain/models/auth-scope';

export class CreateClientCommand implements ICommand {
  constructor(
    public readonly name: string,
    public readonly logoUri: string,
    public readonly scopes: AuthScope[],
    public readonly redirectUris: string[],
  ) {}
}

返回前台送来的用户ID和用户密码创建用户并且异步操作

@CommandHandler(CreateClientCommand)
export class CreateClientHandler
  implements ICommandHandler<CreateClientCommand>
{
  constructor(
    @ClientRepositoryImplement()
    private readonly repository: ClientRepository,
  ) {}

  async execute(command: CreateClientCommand): Promise<CreateClientResult> {
    const id = this.repository.nextId();
    const client = new Client({
      id,
      secret: '',
      name: command.name,
      logoUri: command.logoUri,
      scopes: command.scopes,
      redirectUris: command.redirectUris,
    });
    const secret = client.newSecret();
    client.create();
    await this.repository.save(client);
    client.commit();

    return {
      id,
      secret,
    };
  }
}

进行用户授权:

(1)先创建用户授权结构体再export

export class ValidateClientCommand implements ICommand {
  constructor(public readonly id: ClientId, public readonly secret: string) {}
}

?(2)进行用户授权确认:

@CommandHandler(ValidateClientCommand)
export class ValidateClientHandler
  implements ICommandHandler<ValidateClientCommand>
{
  constructor(
    @ClientRepositoryImplement()
    private readonly repository: ClientRepository,
  ) {}

  async execute(command: ValidateClientCommand): Promise<void> {
    const client = await this.repository.findById(command.id);
    if (!client) {
      throw new ClientNotFoundException();
    }
    if (!client.validate(command.secret)) {
      throw new GrpcException(status.INVALID_ARGUMENT, '客户端密钥错误');
    }
    return;
  }
}

如果用户在库中找不到,抛出异常ClientNotFoundException

import { GrpcException } from '@sdu-turing/microservices';
import { status } from 'grpc';

export class ClientNotFoundException extends GrpcException {
  constructor() {
    super(status.NOT_FOUND, '客户端不存在');
  }
}

如果用户密钥错误,抛出异常

import { GrpcException } from '@sdu-turing/microservices';
import { status } from 'grpc';

export class InvalidArgumentException extends GrpcException {
  constructor() {
    super(status.INVALID_ARGUMENT, '参数非法');
  }
}

(2)图灵认证系统的环境配置

import { IsEnum, IsString, IsUrl } from 'class-validator';
import { Env } from '@sdu-turing/config';

export enum NodeEnvironment {
  Development = 'development',
  Production = 'production',
  Test = 'test',
  Provision = 'provision',
}

export class AppConfigSchema {
  @IsEnum(NodeEnvironment)
  NODE_ENV: NodeEnvironment;

  @IsString()
  MONGO_URI: string;
}

export class AppConfig {
  mongoUri: string;

  nodeEnv: NodeEnvironment;

  constructor(@Env() env: AppConfigSchema) {
    this.nodeEnv = env.NODE_ENV;
    this.mongoUri = env.MONGO_URI;
  }
}

(3)queries

用户数据接口

import { AuthScope } from 'src/domain/models/auth-scope';

export interface ClientData {
  id: string;
  name: string;
  logoUri: string;
  scopes: AuthScope[];
  redirectUris: string[];
}

通过IDquery找到用户

import { IQuery } from '@nestjs/cqrs';

export class FindClientByIdQuery implements IQuery {
  constructor(public readonly clientId: string) {}
}

?将{?IQueryHandler,?QueryHandler?} 通过?'@nestjs/cqrs'导出

通过this.clientQuery.findById返回clientData

nestjs/cqrs简介:CQRS的核心除了Command与Query的分离,还有Controller层与Handler层的解耦。以往的MVC架构中,Controller层会实例化Service,比如UserService,CommentService。Service实例提供了数据库操作逻辑。 这就是Controller与Service的紧耦合。 NestJS的CQRS框架通过QueryBus/CommandBus(.net的CQRS框架中称为Mediator)实现了Controller与事件处理服务的解耦。

@QueryHandler(FindClientByIdQuery)
export class FindClientByIdHandler
  implements IQueryHandler<FindClientByIdQuery>
{
  constructor(
    @ClientQueryImplement()
    private readonly clientQuery: ClientQuery,
  ) {}

  async execute(query: FindClientByIdQuery): Promise<ClientData | undefined> {
    const clientData = await this.clientQuery.findById(query.clientId);
    return clientData;
  }
}

3.domain

? ?models

用户属性的model

import { AuthScope } from './auth-scope';
import { ClientId } from './client-id';

export interface ClientProperties {
  id: ClientId;
  name: string;
  secret: string;
  logoUri: string;
  scopes: AuthScope[];
  redirectUris: string[];
}

整个用户的model,包括各种用户方法。

import { AuthScope } from './auth-scope';
import { ClientId } from './client-id';
import { ClientProperties } from './client-properties';
import * as SHA256 from 'sha256';
import { nanoid } from 'nanoid';
import { AggregateRoot } from '@nestjs/cqrs';
import { ClientCreatedEvent } from '../events/client-created.event';

export class Client extends AggregateRoot {
  private _id: ClientId;

  private _name: string;

  private _secret: string;

  private _logoUri: string;

  private _scopes: AuthScope[];

  private _redirectUris: string[];

  constructor(props: ClientProperties) {
    super();
    this.assignProps(props);
  }

  get asProps(): ClientProperties {
    return {
      id: this._id,
      name: this._name,
      secret: this._secret,
      logoUri: this._logoUri,
      scopes: this._scopes,
      redirectUris: this._redirectUris,
    };
  }

  private assignProps(props: ClientProperties): this {
    this._id = props.id;
    this._name = props.name;
    this._secret = props.secret;
    this._logoUri = props.logoUri;
    this._redirectUris = props.redirectUris;
    this._scopes = props.scopes;
    return this;
  }

  create() {
    this.apply(new ClientCreatedEvent(this.asProps));
  }

  newSecret() {
    const secret = nanoid(32);
    this._secret = this.hashSecret(secret);
    return secret;
  }

  validate(secret: string) {
    return this._secret === this.hashSecret(secret);
  }

  private hashSecret(plain: string) {
    return SHA256(plain);
  }
}

4.infrastructure

从mangoDB中查询用户信息

import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { ClientData } from 'src/application/queries/client-data.interface';
import { ClientQuery } from 'src/application/queries/client.query';
import { ClientCollection, ClientDocument } from './client.schema';

export class MongoClientQuery implements ClientQuery {
  constructor(
    @InjectModel(ClientCollection)
    private readonly clientModel: Model<ClientDocument>,
  ) {}

  async findById(id: string): Promise<ClientData | undefined> {
    const client = await this.clientModel.findById(id);
    if (!client) {
      return undefined;
    }
    return {
      id: client._id,
      name: client.name,
      logoUri: client.logoUri,
      scopes: client.scopes,
      redirectUris: client.redirectUris,
    };
  }
}

将用户信息存储在mangoDB

import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { nanoid } from 'nanoid';
import { Client } from 'src/domain/models/client';
import { ClientId } from 'src/domain/models/client-id';
import { ClientRepository } from 'src/domain/models/client-repository';
import { ClientCollection, ClientDocument } from './client.schema';

export class MongoClientRepository implements ClientRepository {
  constructor(
    @InjectModel(ClientCollection)
    private readonly clientModel: Model<ClientDocument>,
  ) {}

  nextId(): ClientId {
    return new ClientId(nanoid(24));
  }

  async findById(id: ClientId): Promise<Client | undefined> {
    const clientDoc = await this.clientModel.findById(id.toString());
    if (!clientDoc) {
      return undefined;
    }
    return this.documentToModel(clientDoc);
  }

  async save(client: Client): Promise<void> {
    const clientDoc = this.modelToDocument(client);
    await this.clientModel.updateOne(
      {
        _id: clientDoc._id,
      },
      clientDoc,
      {
        upsert: true,
      },
    );
  }

  private documentToModel(document: ClientDocument) {
    const props = document.toObject();
    return new Client({
      ...props,
      id: new ClientId(props._id),
    });
  }

  private modelToDocument(model: Client) {
    const props = model.asProps;
    const document = new this.clientModel({
      ...props,
      _id: props.id.toString(),
    });
    return document;
  }
}

三.总结

? ? ? ?本周主要通过学习SDU信息门户代码的图灵式的Oauth登录,学习掌握了Typescript语言,我之前从未接触typescript语言,现在已经略微学习到了基本的语法,通过分析了项目代码,更加深入地掌握理解了typescript,并且我也学习了一部分地nestJS,掌握了docker的使用方式以及如何使用docker来部署项目或者pull官方软件,还学会了写自己的docker-compose.yaml文件,对了,go语言的基本用法我也基本掌握了。虽然学习了很多新的知识,但感觉还有很多东西需要学习,学得越多,越感觉自己知识地浅陋。希望以后通过和队友的交流和自己的学习能学更多的知识。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 11:39:38  更:2021-10-08 11:40:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:42:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码