Aqueduct入门四步走(四)简单配置OAuth 2.0

原创
2020/10/15 15:19
阅读数 353

Dart全栈系列 Aqueduct(四)简单配置OAuth 2.0

曾经有位大佬说过,java之所以能纵横江湖几十年,完全是因为Spring太牛逼了。而Aqueduct就是Dart界的SpringBoot。

目录

  1. 认识水渠
  2. 数据库连接和ORM
  3. 配置和自测
  4. 简单配置OAuth 2.0

什么是OAuth 2.0

请看阮一峰老师的博客

阮一峰老师的解释非常通俗了,这里我更加通俗的解释一下:OAuth是江湖上非常流行的,用来验证用户权限的协议,我们常用这玩意做注册和登陆。为什么要用它?因为它已经是事实上的标准化了,支持自己和第三方登陆、单点登陆,而且安全性很高!

那不用行不行?当然可以,愿意当非主流,谁也拦不住你!

OAuth 2.0的四种验证方式

具体请参考阮老师的博客,这里不再赘述。下面是整理出来供我自己用的:

方式 用途
授权码 常用于第三方登陆认证
隐藏式 纯前段应用
密码式 高度信任某应用,或自家应用
客户端凭证 适用于没有缓存的应用,如控制台命令行应用

Aqueduct引入Oauth2.0

创建用户实体、数据库表

import 'package:aqueduct/managed_auth.dart';
import 'package:heroes/heroes.dart';
import 'package:heroes/model/hero.dart';

class User extends ManagedObject<_User> implements _User, ManagedAuthResourceOwner<_User> {}

class _User extends ResourceOwnerTableDefinition {}

这里创建了一个model——User,必须继承ManagedAuthResourceOwner。这是个Aqueduct内置的托管类,里面实现了OAuth2.0的一系列方法。

同时数据库接口_User也要继承ResourceOwnerTableDefinition,里面封装着对认证信息的操作。

然后就可以更新数据库了:

aqueduct db generate
aqueduct db upgrade --connect postgres://heroes_user:password@localhost:5432/heroes

这时migrations的版本号就变成了2.

这里我试了好几次也不成功,后来删除了00000001_initial.migration.dart,再重新生成它,然后手动把文件名里的1改成了2,再去同步数据库,没想到竟然成功了。

这里有个问题,我们登陆时会提交用户名和密码,但是User继承自_User继承于ResourceOwnerTableDefinition,ResourceOwnerTableDefinition中没有存用户密码,只存了一个hashedPassword。这是因为真正的用户密码我们是不应该存的,而应该存hash加密后的密码。所以我们应该在User中添加个password用来接收用户提交的密码,但却不直接存入数据库:

class User extends ManagedObject<_User> implements _User, ManagedAuthResourceOwner<_User> {
  @Serialize(input: true, output: false)
  String password;
}

创建用户注册Controller

import 'dart:async';

import 'package:aqueduct/aqueduct.dart';
import 'package:heroes/model/user.dart';

class RegisterController extends ResourceController {
  RegisterController(this.context, this.authServer);

  final ManagedContext context;
  final AuthServer authServer;

  @Operation.post()
  Future<Response> createUser(@Bind.body() User user) async {
    // Check for required parameters before we spend time hashing
    if (user.username == null || user.password == null) {
      return Response.badRequest(
        body: {"error": "username and password required."});
    }

    user
      ..salt = AuthUtility.generateRandomSalt()
      ..hashedPassword = authServer.hashPassword(user.password, user.salt);

    return Response.ok(await Query(context, values: user).insert());
  }
}

这个controller主要是通过构造方法注入了一个AuthServer,然后用这玩意去哈希加密用户密码,再存入数据库。

这里有个salt是盐的意思,它是个32位Base64编码的随机序列。但是为什么叫做「盐」呢,我觉得叫「润色」更为合适。

接下来在channel中挂载router:

import 'package:heroes/controller/register_controller.dart';

...

  @override
  Controller get entryPoint {
    final router = Router();

    router
      .route('/heroes/[:id]')
      .link(() => HeroesController(context));

    router
      .route('/register')
      .link(() => RegisterController(context, authServer));

    return router;
  }
}    

注册用户

curl -X POST http://localhost:8888/register -H 'Content-Type: application/json' -d '{"username":"bob", "password":"password"}'

执行完上面的命令后,就可以在数据库的_user表中查询到一条用户信息。

用OAuth2.0去认证用户

当用户注册成功后,就可以登陆了,在用户登陆时,我们需要用OAuth2.0去认证他。

进入channel中,在prepary中初始化一个authServer:

class HeroesChannel extends ApplicationChannel {
  ManagedContext context;

  // Add this field
  AuthServer authServer;

  Future prepare() async {
    logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));

    final config = HeroConfig(options.configurationFilePath);
    final dataModel = ManagedDataModel.fromCurrentMirrorSystem();
    final persistentStore = PostgreSQLPersistentStore.fromConnectionInfo(
      config.database.username,
      config.database.password,
      config.database.host,
      config.database.port,
      config.database.databaseName);

    context = ManagedContext(dataModel, persistentStore);

    // 添加这两行
    final authStorage = ManagedAuthDelegate<User>(context);
    authServer = AuthServer(authStorage);
  }
  ...

那么是不是像注册时那样,我们需要自定义一个controller来实现登陆逻辑呢?答案是不需要的,Aqueduct里已经内置好了一个专业用于认证的controller,我们直接拿来用就好了,这就是标准化的好处。

@override
Controller get entryPoint {
  final router = Router();

  // 直接添加这个路由即可
  router
    .route('/auth/token')
    .link(() => AuthController(authServer));

  router
    .route('/heroes/[:id]')
    .link(() => HeroesController(context));

  router
    .route('/register')
    .link(() => RegisterController(context, authServer));

  return router;
}

然后向数据库中添加一个clientID:

aqueduct auth add-client --id com.heroes.tutorial --connect postgres://heroes_user:password@localhost:5432/heroes

接着来看一下OAuth2.0的请求协议有哪些内容:

  1. 请求头中包含clientID 和 [客户端密码];
  2. 请求体中包含用户名和密码;
  3. 请求体中包含验证类型,由于是自己的服务,所以我这里采用password类型;
  4. 必须用POST请求提交application/x-www-form-urlencoded格式。

请求token

执行:

curl -X POST http://localhost:8888/auth/token -H 'Authorization: Basic Y29tLmhlcm9lcy50dXRvcmlhbDo=' -H 'Content-Type: application/x-www-form-urlencoded' -d 'username=bob&password=password&grant_type=password'

如果返回如下信息,说明token请求成功:

{"access_token":"687PWKFHRTQ9MveQ2dKvP95D4cWie1gh","token_type":"bearer","expires_in":86399}

验证token

router
  .route('/heroes/[:id]')
  //加上验证路由
  .link(() => Authorizer.bearer(authServer))
  .link(() => HeroesController(context));

然后测试:

curl -X GET --verbose http://localhost:8888/heroes

直接访问会返回401 Unauthorized错误。那么我们加上token后再访问:

curl -X GET http://localhost:8888/heroes -H 'Authorization: Bearer 687PWKFHRTQ9MveQ2dKvP95D4cWie1gh'

没问题的话就能获取到英雄列表了。

Postman中加入OAuth2.0验证

展开阅读全文
打赏
0
0 收藏
分享
加载中
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部