Skip to content

Commit

Permalink
feat: passport add presetProperty (#1358)
Browse files Browse the repository at this point in the history
  • Loading branch information
nawbc authored and czy88840616 committed Dec 6, 2021
1 parent f26af4f commit 4db8eda
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 91 deletions.
264 changes: 181 additions & 83 deletions packages/passport/README.md
@@ -1,42 +1,66 @@
# midway-passport 使用文档
身份验证是大多数Web应用程序的重要组成部分。因此Midway封装了目前Nodejs中最流行的Passport库。
Passport是通过称为策略的可扩展插件进行身份验证请求。Passport 不挂载路由或假设任何特定的数据库,这最大限度地提高了灵活性并允许开发人员做出应用程序级别的决策。

Passport 的目的是请求进行身份验证,其中它通过一组可扩展称为插件的*策略*`midway-passport` 对 Passport 进行了封装,目前支持`Express``Koa``Egg` 。此外推荐使用`Typescript`开发。

## 准备


1. 安装 `npm i @midwayjs/passport`



```bash
Express
@midwayjs/express
npm i passport
```


```bash
Koa, Egg
@midwayjs/koa, @midwayjs/web
npm i koa-passport
```


2. 开启相对应框架的 bodyparser

## 开始

首先请对[Passport](https://www.npmjs.com/package/passport)进行简单了解

##### 以本地, Jwt 为例
## 使用
这里我们以本地认证,和Jwt作为演示。

首先我们用`ExpressPassportStrategyAdapter` 创建一个 Strategy,其次再用`@BootStrategy`来启动此策路
首先
```bash
// configuration.ts

import { join } from 'path';
import * as jwt from '@midwayjs/jwt';
import { ILifeCycle,} from '@midwayjs/core';
import { Configuration } from '@midwayjs/decorator';
import * as passport from '@midwayjs/passport';

@Configuration({
imports: [
jwt,
passport,
],
importConfigs: [join(__dirname, './config')],
conflictCheck: true,
})
export class ContainerLifeCycle implements ILifeCycle {}

`local.strategy.ts`
```
### e.g. 本地
我们可以通过`@BootStrategy`和派生`ExpressPassportStrategyAdapter`来自启动一个策略。通过 verify 钩子来获取有效负载,并且此函数必须有返回值,其参数并不明确,可以参考对应的Strategy或者通过展开符打印查看。
PS. Koa,Egg请使用`WebPassportStrategyAdapter`
```typescript
// local-strategy.ts

```ts
// Egg, Koa 为WebPassportStrategyAdapter
import { BootStrategy, ExpressPassportStrategyAdapter } from '@deskbtm/midway-passport';
import { Strategy } from 'passport-local';
import { BootStrategy, ExpressPassportStrategyAdapter } from '@midwayjs/passport';
import { Repository } from 'typeorm';
import { ILogger } from '@midwayjs/logger';
import { InjectEntityModel } from '@midwayjs/orm';
import { Logger } from '@midwayjs/decorator';
import { UserEntity } from './user';
import * as bcrypt from 'bcrypt';

@BootStrategy({
async useParams() {
Expand All @@ -45,23 +69,14 @@ import { UserEntity } from './user';
};
},
})
// ExpressPassportStrategyAdapter 支持自定义name
export class LocalStrategy extends ExpressPassportStrategyAdapter(Strategy, 'local') {
@InjectEntityModel(UserEntity)
photoModel: Repository<UserEntity>;
userModel: Repository<UserEntity>;

@Logger('dash')
logger: ILogger;

// 通过 verify 钩子来获取有效负载 并且此函数必须有返回参数
// 详情见对应的Strategy
async verify(username, password) {
const user = await this.photoModel.findOne({ username });

this.logger.info('user from db', user);

if (!user) {
throw new Error('not found user ' + username);
const user = await this.userModel.findOne({ username });
if (await bcrypt.compare(password, user.password)) {
throw new Error('error password ' + username);
}

return {
Expand All @@ -70,48 +85,72 @@ export class LocalStrategy extends ExpressPassportStrategyAdapter(Strategy, 'loc
};
}
}
```

`local.middleware.ts`
```
之后派生`ExpressPassportMiddleware`出一个中间件。PS. Koa,Egg 使用`WebPassportMiddleware`
```typescript
// local-middleware.ts

```ts
import { Inject, Provide } from '@midwayjs/decorator';
//Egg, Koa 请使用WebPassportMiddleware
import { ExpressPassportMiddleware } from '@deskbtm/midway-passport';
import { UserEntity } from '@/rbac';
import { InjectEntityModel } from '@midwayjs/orm';
import { ExpressPassportMiddleware } from '@midwayjs/passport';
import { Context } from '@midwayjs/express';
import { Repository } from 'typeorm';

@Provide('local') // 同样可以在此处使用一个简短的identifier
@Provide('local') // 此处可以使用一个简短的identifier
export class LocalPassportMiddleware extends ExpressPassportMiddleware {
// required
public strategy: string = 'local';
strategy: string = 'local';

@InjectEntityModel(UserEntity)
photoModel: Repository<UserEntity>;

public async setOptions(ctx?: Context): AuthenticateOptions {
return {};
// 设置 AuthenticateOptions
async setOptions(ctx?: Context): AuthenticateOptions {
return {
failureRedirect: '/login',
presetProperty: 'user'
};
}

// required
// 首个参数为Context, 剩余参数请看 passport.authenticate
// 获取上下文实例可以使用 ctx.requestContext.get<xxx>('xxx');
// 推荐在此处鉴权
public async auth(_ctx, _err, data): Promise<Record<any, any>> {
const user = await this.photoModel.findOne({ username: data.username });
console.log(user);

// auth返回值默认会被挂到req.user上,当然你可以通过presetProperty来更改
// PS. 获取上下文实例可以使用 ctx.requestContext.get<xxx>('xxx');
async auth(_ctx, _err, data): Promise<Record<any, any>> {
return data;
}
}
```
```typescript
// controller.ts

import { Provide, Post, Inject, Controller } from '@midwayjs/decorator';

`jwt.strategy.ts`
@Provide()
@Controller('/')
export class LocalController {

@Post('/passport/local', { middleware: ['local'] })
async localPassport() {
console.log('local user: ', this.ctx.req.user);
return this.ctx.req.user;
}
}
```
使用curl 模拟一次请求。
```bash
curl -X POST http://localhost:7001/passport/local -d '{"username": "demo", "pwd": "1234"}' -H "Content-Type: application/json"

```ts
import { BootStrategy, ExpressPassportStrategyAdapter } from '@deskbtm/midway-passport';
结果 {"username": "demo", "pwd": "1234"}
```
### e.g. Jwt
首先你需要安装`npm i @midwayjs/jwt`,然后在 config.ts 中配置。PS. 默认未加密,请不要吧敏感信息存放在payload中。
```typescript
export const jwt = {
secret: 'xxxxxxxxxxxxxx', // fs.readFileSync('xxxxx.key')
expiresIn: '2d' // https://github.com/vercel/ms
}
```
```typescript
// jwt-strategy.ts

import { BootStrategy, ExpressPassportStrategyAdapter } from '@midwayjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';

@BootStrategy({
Expand All @@ -128,13 +167,13 @@ export class JwtStrategy extends ExpressPassportStrategyAdapter(Strategy, 'jwt')
return payload;
}
}
```

`jwt.middleware.ts`
```
```typescript
// jwt-middleware.ts

```ts
import { Provide } from '@midwayjs/decorator';
import { ExpressPassportMiddleware } from '@deskbtm/midway-passport';
import { ExpressPassportMiddleware } from '@midwayjs/passport';

@Provide()
export class JwtPassportMiddleware extends ExpressPassportMiddleware {
Expand All @@ -145,50 +184,109 @@ export class JwtPassportMiddleware extends ExpressPassportMiddleware {
}
}
```

`test.controller.ts`

```ts
import { ALL, Provide, Logger, Get, Inject } from '@midwayjs/decorator';
import { Body, Controller, Post } from '@midwayjs/decorator';
import { LocalPassportControl } from './local.control';
import { JwtPassportControl } from './jwt.control';
import { ILogger } from '@midwayjs/logger';
import { Jwt } from '@deskbtm/midway-jwt';
```typescript
import { Provide, Post, Inject } from '@midwayjs/decorator';
import { Controller, Post } from '@midwayjs/decorator';
import { Jwt } from '@midwayjs/jwt';

@Provide()
@Controller('/test')
export class TestPackagesController {
@Logger('dash')
logger: ILogger;
@Controller('/')
export class JwtController {

@Inject()
jwt: Jwt;

@Post('/local-passport', { middleware: ['local'] })
async localPassport(@Body(ALL) body) {
// auth返回的参数会被挂到req.user上
console.log('local user: ', this.ctx.req.user);
return body;
}
@Inject();
ctx: any;

@Post('/jwt-passport', { middleware: ['jwtPassportMiddleware'] })
async jwtPassport(@Body(ALL) body) {
@Post('/passport/jwt', { middleware: ['jwtPassportMiddleware'] })
async jwtPassport() {
console.log('jwt user: ', this.ctx.req.user);
return body;
return this.ctx.req.user;
}

@Post('/gen-jwt')
@Post('/jwt')
async genJwt() {
return {
t: await this.jwt.sign({ msg: 'Hello Midway' }),
};
}
}
```
使用curl模拟请求
```bash
curl -X POST http://127.0.0.1:7001/jwt

结果 {"t": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}

curl http://127.0.0.1:7001/passport/jwt -H "Authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

结果 {"msg": "Hello Midway","iat": 1635468727,"exp": 1635468827}

```
## 自定义其他策略
`@midwayjs/passport`支持自定义[其他策略](http://www.passportjs.org/packages/),这里以github oauth为例。
首先 `npm i passport-github`,之后编写如下代码:
```typescript
// github-strategy.ts

在 StrategyAdapter 中支持 1. 使用 BootStrategy 中的 useParams。2. 通过 StrategyAdapter 的第三个参数,两种方式传递 options。
import { BootStrategy, ExpressPassportStrategyAdapter } from '@midwayjs/passport';
import { Strategy, StrategyOptions } from 'passport-github';

## 相关
const GITHUB_CLIENT_ID = 'xxxxxx', GITHUB_CLIENT_SECRET = 'xxxxxxxx';

@BootStrategy({
async useParams({ configuration }): Promise<StrategyOptions> {
return {
clientID: GITHUB_CLIENT_ID,
clientSecret: GITHUB_CLIENT_SECRET,
callbackURL: 'https://127.0.0.1:7001/auth/github/cb'
};
},
})
export class GithubStrategy extends ExpressPassportStrategyAdapter(Strategy, 'github') {
async verify(...payload) {
return payload;
}
}

```
```typescript
// github-middleware.ts

import { ExpressPassportMiddleware } from '@midwayjs/passport';

@Provide()
export class GithubPassportMiddleware extends ExpressPassportMiddleware {
strategy: string = 'github';

async auth(_ctx, ...data) {
return data;
}
}
```
```typescript
// controller.ts

import { Provide, Get, Inject } from '@midwayjs/decorator';

@Provide()
@Controller('/oauth')
export class AuthController {
@Inject()
ctx: any;

@Get('/github', { middleware: ['githubPassportMiddleware'] })
async githubOAuth() {}

@Get('/github/cb', { middleware: ['githubPassportMiddleware'] })
async githubOAuthCallback() {
return this.ctx.req.user;
}
}

```
[midwayjs/jwt](../jwt/README.md)
10 changes: 6 additions & 4 deletions packages/passport/src/express.ts
@@ -1,5 +1,6 @@
import * as passport from 'passport';
import { Context, IWebMiddleware, Middleware } from '@midwayjs/express';
import { defaultOptions } from './options';

interface Class<T = any> {
new (...args: any[]): T;
Expand Down Expand Up @@ -101,13 +102,14 @@ export abstract class ExpressPassportMiddleware implements IWebMiddleware {
}
});

const options = (
this.setOptions ? await this.setOptions(req as any) : null
) as any;
const options = {
...defaultOptions,
...(this.setOptions ? await this.setOptions(req as any) : null),
};

passport.authenticate(this.strategy, options, async (...d) => {
const user = await this.auth(req as any, ...d);
req.user = user;
req[options.presetProperty] = user;
next();
})(req, res);
};
Expand Down
4 changes: 4 additions & 0 deletions packages/passport/src/options.ts
@@ -0,0 +1,4 @@
export const defaultOptions = {
session: false,
presetProperty: 'user',
};

0 comments on commit 4db8eda

Please sign in to comment.