diff --git a/package.json b/package.json index e6a6aa0..7228b16 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,18 @@ "dependencies": { "@nestjs/common": "^6.0.0", "@nestjs/core": "^6.0.0", + "@nestjs/jwt": "^6.1.1", + "@nestjs/passport": "^6.1.0", "@nestjs/platform-express": "^6.0.0", "@nestjs/typeorm": "^6.1.2", "@types/bcrypt": "^3.0.0", + "@types/passport": "^1.0.0", + "@types/passport-jwt": "^3.0.1", "bcrypt": "^3.0.6", "class-transformer": "^0.2.3", "mysql": "^2.17.1", + "passport": "^0.4.0", + "passport-jwt": "^4.0.0", "reflect-metadata": "^0.1.12", "rimraf": "^2.6.2", "rxjs": "^6.3.3", diff --git a/src/app.module.ts b/src/app.module.ts index 0e2c717..8b2252b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { AppService } from './app.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PostModule } from './modules/post/post.module'; import { UserModule } from './modules/user/user.module'; +import { AuthModule } from './modules/auth/auth.module'; @Module({ imports: [ @@ -15,10 +16,12 @@ import { UserModule } from './modules/user/user.module'; password: 'jeremy', database: 'nest', synchronize: true, + logging: true, entities: [__dirname + '/**/*.entity{.ts,.js}'], }), PostModule, UserModule, + AuthModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/core/decorators/user.decorator.ts b/src/core/decorators/user.decorator.ts new file mode 100644 index 0000000..2dd2ced --- /dev/null +++ b/src/core/decorators/user.decorator.ts @@ -0,0 +1,3 @@ +import { createParamDecorator } from "@nestjs/common"; + +export const User = createParamDecorator((data, req) => req.user) \ No newline at end of file diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts new file mode 100644 index 0000000..5695209 --- /dev/null +++ b/src/modules/auth/auth.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthController } from './auth.controller'; + +describe('Auth Controller', () => { + let controller: AuthController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuthController], + }).compile(); + + controller = module.get(AuthController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..0bf3b42 --- /dev/null +++ b/src/modules/auth/auth.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Post, Body, Get, UseGuards } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { LoginDto } from './login.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { User } from '../../core/decorators/user.decorator'; + +@Controller('auth') +export class AuthController { + constructor( + private readonly authService: AuthService, + ) { } + + @Post('login') + async login(@Body() data: LoginDto) { + return await this.authService.login(data) + } + + @Get('test') + @UseGuards(AuthGuard()) + async authTest(@User() user) { + console.log('user: ', user) + return { + message: 'ok', + } + } + +} diff --git a/src/modules/auth/auth.interface.ts b/src/modules/auth/auth.interface.ts new file mode 100644 index 0000000..615fc7f --- /dev/null +++ b/src/modules/auth/auth.interface.ts @@ -0,0 +1,4 @@ +export interface JwtPayload { + id: number; + username: string +} \ No newline at end of file diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..389ad35 --- /dev/null +++ b/src/modules/auth/auth.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { UserModule } from '../user/user.module'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy } from './strategies/jwt.strategy'; + +@Module({ + imports: [ + UserModule, + JwtModule.register({ + secret: 'nDSAR3+K4pLD+HIl9xWFY/n7maMxLwPTj5gscZvd9Vo=', + signOptions: { + expiresIn: '12h', + } + }), + PassportModule.register({ + defaultStrategy: 'jwt', + }), + ], + controllers: [AuthController], + providers: [AuthService, JwtStrategy] +}) +export class AuthModule {} diff --git a/src/modules/auth/auth.service.spec.ts b/src/modules/auth/auth.service.spec.ts new file mode 100644 index 0000000..800ab66 --- /dev/null +++ b/src/modules/auth/auth.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuthService], + }).compile(); + + service = module.get(AuthService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts new file mode 100644 index 0000000..142ebf6 --- /dev/null +++ b/src/modules/auth/auth.service.ts @@ -0,0 +1,42 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { UserService } from '../user/user.service'; +import { LoginDto } from './login.dto'; +import { JwtPayload } from './auth.interface'; +import { JwtService } from '@nestjs/jwt'; + +@Injectable() +export class AuthService { + constructor( + private readonly userService: UserService, + private readonly jwtService: JwtService, + ) {} + + async login(data: LoginDto) { + const { username, password } = data + const entity = await this.userService.findByUserName(username) + console.log(entity) + + if (!entity) { + throw new UnauthorizedException('用户不存在') + } + const match = await entity.comparePassword(password) + console.log(match) + + if (!match) { + throw new UnauthorizedException('密码错误') + } + + const { id } = entity + const payload = {username, id} + const token = this.signToken(payload) + + return { + ...payload, + token + } + } + + signToken(data: JwtPayload) { + return this.jwtService.sign(data) + } +} diff --git a/src/modules/auth/login.dto.ts b/src/modules/auth/login.dto.ts new file mode 100644 index 0000000..b98fc45 --- /dev/null +++ b/src/modules/auth/login.dto.ts @@ -0,0 +1,4 @@ +export class LoginDto { + readonly username: string; + readonly password: string; +} \ No newline at end of file diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..a4a05c0 --- /dev/null +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,27 @@ +import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { PassportStrategy } from "@nestjs/passport"; +import { Strategy, ExtractJwt } from "passport-jwt"; +import { JwtPayload } from "../auth.interface"; +import { UserService } from "../../user/user.service"; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor( + private readonly userService: UserService + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: 'nDSAR3+K4pLD+HIl9xWFY/n7maMxLwPTj5gscZvd9Vo=', + }) + } + + async validate(payload: JwtPayload) { + console.log(payload) + const { username } = payload + const entity = await this.userService.findByUserName(username) + if (!entity) { + throw new UnauthorizedException('用户不存在') + } + return entity + } +} \ No newline at end of file diff --git a/src/modules/user/user.entity.ts b/src/modules/user/user.entity.ts index 04953c5..18a9399 100644 --- a/src/modules/user/user.entity.ts +++ b/src/modules/user/user.entity.ts @@ -27,6 +27,7 @@ export class User { } async comparePassword(password: string) { + console.log(password, this.password) return await bcrypt.compare(password, this.password); } } \ No newline at end of file diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts index 3979957..95839c6 100644 --- a/src/modules/user/user.module.ts +++ b/src/modules/user/user.module.ts @@ -9,6 +9,7 @@ import { User } from './user.entity'; TypeOrmModule.forFeature([User]), ], controllers: [UserController], - providers: [UserService] + providers: [UserService], + exports: [UserService] }) export class UserModule {} diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index e2e7fc1..c30271f 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -43,4 +43,8 @@ export class UserService { entity.password = newPassword return await this.userRepository.save(entity) } + + async findByUserName(username: string) { + return await this.userRepository.findOne({ username }) + } } diff --git a/yarn.lock b/yarn.lock index c146f31..8f28ef5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,6 +39,19 @@ optional "0.1.4" uuid "3.3.2" +"@nestjs/jwt@^6.1.1": + version "6.1.1" + resolved "https://registry.npm.taobao.org/@nestjs/jwt/download/@nestjs/jwt-6.1.1.tgz#78883321fc8663a7cf32aa725a70adc8454bbf5d" + integrity sha1-eIgzIfyGY6fPMqpyWnCtyEVLv10= + dependencies: + "@types/jsonwebtoken" "7.2.8" + jsonwebtoken "8.4.0" + +"@nestjs/passport@^6.1.0": + version "6.1.0" + resolved "https://registry.npm.taobao.org/@nestjs/passport/download/@nestjs/passport-6.1.0.tgz#80da326cc976a82530648d8025b04c8e2d41c10e" + integrity sha1-gNoybMl2qCUwZI2AJbBMji1BwQ4= + "@nestjs/platform-express@^6.0.0": version "6.3.1" resolved "https://registry.npm.taobao.org/@nestjs/platform-express/download/@nestjs/platform-express-6.3.1.tgz#8adb602a5bb9571b9d58646bd52141a26ea8ba3e" @@ -105,7 +118,7 @@ "@types/node" "*" "@types/range-parser" "*" -"@types/express@^4.16.0": +"@types/express@*", "@types/express@^4.16.0": version "4.17.0" resolved "https://registry.npm.taobao.org/@types/express/download/@types/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287" integrity sha1-SertsglYKobxLtm3JRYPEtBO8oc= @@ -124,6 +137,20 @@ resolved "https://registry.npm.taobao.org/@types/json5/download/@types/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/jsonwebtoken@*": + version "8.3.2" + resolved "https://registry.npm.taobao.org/@types/jsonwebtoken/download/@types/jsonwebtoken-8.3.2.tgz#e3d5245197152346fae7ee87d5541aa5a92d0362" + integrity sha1-49UkUZcVI0b65+6H1VQapaktA2I= + dependencies: + "@types/node" "*" + +"@types/jsonwebtoken@7.2.8": + version "7.2.8" + resolved "https://registry.npm.taobao.org/@types/jsonwebtoken/download/@types/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + integrity sha1-jRmdq03bW7oyNPgxG4BNICevKzo= + dependencies: + "@types/node" "*" + "@types/mime@*": version "2.0.1" resolved "https://registry.npm.taobao.org/@types/mime/download/@types/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -139,6 +166,30 @@ resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-10.14.9.tgz#2e8d678039d27943ce53a1913386133227fd9066" integrity sha1-Lo1ngDnSeUPOU6GRM4YTMif9kGY= +"@types/passport-jwt@^3.0.1": + version "3.0.1" + resolved "https://registry.npm.taobao.org/@types/passport-jwt/download/@types/passport-jwt-3.0.1.tgz#bc4c2610815565de977ea1a580c047d71c646084" + integrity sha1-vEwmEIFVZd6XfqGlgMBH1xxkYIQ= + dependencies: + "@types/express" "*" + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + +"@types/passport-strategy@*": + version "0.2.35" + resolved "https://registry.npm.taobao.org/@types/passport-strategy/download/@types/passport-strategy-0.2.35.tgz#e52f5212279ea73f02d9b06af67efe9cefce2d0c" + integrity sha1-5S9SEieepz8C2bBq9n7+nO/OLQw= + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*", "@types/passport@^1.0.0": + version "1.0.0" + resolved "https://registry.npm.taobao.org/@types/passport/download/@types/passport-1.0.0.tgz#747fa127a747a145ff279f3df3e07c425e5ff297" + integrity sha1-dH+hJ6dHoUX/J5898+B8Ql5f8pc= + dependencies: + "@types/express" "*" + "@types/range-parser@*": version "1.2.3" resolved "https://registry.npm.taobao.org/@types/range-parser/download/@types/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" @@ -723,6 +774,11 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.npm.taobao.org/buffer-equal-constant-time/download/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1311,6 +1367,13 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.npm.taobao.org/ecdsa-sig-formatter/download/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha1-rg8PothQRe8UqBfao86azQSJ5b8= + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2982,6 +3045,37 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +jsonwebtoken@8.4.0: + version "8.4.0" + resolved "https://registry.npm.taobao.org/jsonwebtoken/download/jsonwebtoken-8.4.0.tgz#8757f7b4cb7440d86d5e2f3becefa70536c8e46a" + integrity sha1-h1f3tMt0QNhtXi877O+nBTbI5Go= + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + +jsonwebtoken@^8.2.0: + version "8.5.1" + resolved "https://registry.npm.taobao.org/jsonwebtoken/download/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + integrity sha1-AOceC431TCEhofJhN98igGc7zA0= + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^5.6.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2992,6 +3086,23 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.npm.taobao.org/jwa/download/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha1-dDwymFy56YZVUw1TZBtmyGRbA5o= + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.1.5, jws@^3.2.2: + version "3.2.2" + resolved "https://registry.npm.taobao.org/jws/download/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha1-ABCZ82OUaMlBQADpmZX6UvtHgwQ= + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3087,6 +3198,41 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.npm.taobao.org/lodash.includes/download/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.npm.taobao.org/lodash.isboolean/download/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.npm.taobao.org/lodash.isinteger/download/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.npm.taobao.org/lodash.isnumber/download/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.npm.taobao.org/lodash.isplainobject/download/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.npm.taobao.org/lodash.isstring/download/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.npm.taobao.org/lodash.once/download/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.npm.taobao.org/lodash.sortby/download/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -3822,6 +3968,27 @@ pascalcase@^0.1.1: resolved "https://registry.npm.taobao.org/pascalcase/download/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +passport-jwt@^4.0.0: + version "4.0.0" + resolved "https://registry.npm.taobao.org/passport-jwt/download/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065" + integrity sha1-fwvnupQuKLn10iwuu7jOlu988GU= + dependencies: + jsonwebtoken "^8.2.0" + passport-strategy "^1.0.0" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.npm.taobao.org/passport-strategy/download/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= + +passport@^0.4.0: + version "0.4.0" + resolved "https://registry.npm.taobao.org/passport/download/passport-0.4.0.tgz#c5095691347bd5ad3b5e180238c3914d16f05811" + integrity sha1-xQlWkTR71a07XhgCOMORTRbwWBE= + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.npm.taobao.org/path-dirname/download/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -3873,6 +4040,11 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +pause@0.0.1: + version "0.0.1" + resolved "https://registry.npm.taobao.org/pause/download/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -4335,7 +4507,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5, semver@^5.5.0, semver@^5.6.0: version "5.7.0" resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz?cache=0&sync_timestamp=1559063729249&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha1-eQp89v6lRZuslhELKbYEEtyP+Ws=