graphql-server node版(五)

认证,注册和登陆功能

1、在data model中添加User 类型

在Link和User中建立关系,用户可以发表多个Link,每个Link都有对应的用户。这是SDL表达一对多关系的方式,如下:

type Link {
  id: ID! @unique
  description: String!
  url: String!
  postedBy: User
}

type User {
  id: ID! @unique
  name: String!
  email: String! @unique
  password: String!
  links: [Link!]!
}

  在更改数据模型后,我们需要重新部署以应用更改。

prisma deploy

  Prisma数据库schema src/generated/prisma.graphql以及Prisma服务的API已经更新。API现在还根据指定的关系公开User类型的CRUD操作以及连接和断开连接User元素的操作Link

2、扩展app应用程序层的schema

“schema驱动开发”,我们都是首先以增加客户端api为出发点的。

# import Link from "./generated/prisma.graphql"


  type Query{
    info:String!
    feed:[Link!]!
    # Fetch a single link by its `id`
    link(id: ID!): Link
  }

  type Mutation{
    #add a link
    post(url:String!,description:String!):Link!
    # Update a link
    updateLink(id: ID!, url: String, description: String): Link
    # Delete a link
    deleteLink(id: ID!): Link
    signup(name:String!,email:String!,password:String!):AuthPayload!
    login(email:String!,password:String!):AuthPayLoad
  }

  type AuthPayLoad{
    token:String!
    user:User
  }

  type User{
    id:ID!
    name:String!
    email:String!
    links:[Link!]!
  }

  这里我们没有从"./generated/prisma.graphql"引入User,因为我们想隐藏password信息。不想前端可以直接看到密码明文信息。所以我们这里重新定义了User.(当然了,数据库中的密码都应该是加密存储的,即使真的将password字段放在这里,请求到的也是一串加密后的密码。)

  这里我们还定义了一个AuthPayLoad,即授权用户。当用户注册登陆之后,将会得到授权用户的信息.

3、实现解析器功能

为了更加的模块化,我们可以将所有的解析器放在一个单独的文件下:resolvers

在src下创建一个resolvers文件夹,并新建三个文件Query.js,Mutation.js,和AuthPayLoad.js

在Query.js中

function feed(parent, args, context, info) {
  return context.db.query.links({}, info)
}
function link(root,args,context,info)=>{
  return context.db.query.link({id:args.id},info)
}

module.export={
  feed,
  link,
}

  在Mutation.js中

增加login解析器和signup解析器

const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const { APP_SECRET } = require('../utils')

function post(root,args,context,info)=>{
  return context.db.mutation.createLink({url:args.url,description:args.description},info)
}

function updateLink(root,args,context,info)=>{
  return context.db.mutation.updateLink({url:args.url,description:args.description,id:args.id},info)
}

function deleteLink(root,args)=>{
  return context.db.mutation.deleteLink({id:args.id},info)
}

async function signup(root,args,context,info)=>{
  const password = await bcrypt(args.password,10)
  const user = await context.db.mutation.createUser({
    data:{...args,password},
  },`{ id }`)
  const token = jwt.sign({userId:user.id},APP_SECRET)
  return {
    token,
    user
  }
}

async function login(root,args,context,info)=>{
  const user=await context.db.query.user({where:{email:args.email}},` { id password } `)
  if (!user) {
    throw new Error('No such user found')
  }
  const valid = bcrypt.compare(args.password,user.password)
  if(!valid){
    throw new Error('Invalid password')
  }
  const token=jwt.sign({userId:user.id},APP_SECRET)
  return{
    token,
    user
  }
}

module.export={
  post,
  updateLink,
  deleteLink,
  signup,
  login,

}

  新增signup和login方法

注册signup:

(1)我们使用bcrypt对密码进行加密,bcrpt是一种慢加密方法,使用穷举解密的时候需要很长的时间。在使用之前我们需要先安装bcrypt库。

(2)调用数据库层的api,将修改后的加密过后的密码同用户信息一起存到数据库中,使用createUser方法,并且在创建成功后返回一个用户ID字段、注意这里没有使用info而是采用了硬编码‘{id}’,,即用户注册成功后仅可以得到ID.

因为这里的info代表的是signup的info,即signup中的字段,而不是createUser中的字段。因此传入signup中的字段给creatUser肯定是错误的。

(3)这里我们使用jsonwebtoken包来生成token,使用了APP_SECRET签名加密。

(4)最后返回token和user

login解析器:

(1)根据email向database服务端api请求获取用户,获取成功后返回id和password字段,这里也是采用硬编码方式。没有引用info。也是同理,login的字段信息不能直接传给User.

(2)如果没有找到用户,则提示用户名不存在

(3)如果用户名存在的话验证密码是否正确,使用bcrypt.compare(args.password,user.password),比较用户传入的密码和服务器存储的密码是否一致。

(4)如果不一致,提示密码无效

(5)如果登陆成功,则获取token

(6)返回登陆成功的token和user

我们需要在根目录添加相应的包bcrypt和jsonwebtoken

yarn add jsonwebtoken bcryptjs

  

继续在mutation.js中增加AuthPayload解析器

function user(root, args, context, info) {
  return context.db.query.user({ where: { id: root.user.id } }, info)
}

module.exports = { user }

  在AuthPayload解析器中解析user,他接受的是login或signup传递来的user.id,因此我们使用root.user.id来查找user,并且返回user.这样在对于login和signup中返回的AuthPayload中的user可以正常的返回user对象了。因为info的现值,在login和signup中的user都是使用了硬编码的方式来获取字段id,在AuthPayload中在login,signup中获取的id字段再次转换为了user对象。

现在创建一个utils.js,

在utils.js中创建一些会在其他地方重用的信息。

const jwt = require('jsonwebtoken')
const APP_SECRET = 'GraphQL-is-aw3some'

function getUserId(context) {
  const Authorization = context.request.get('Authorization')
  if (Authorization) {
    const token = Authorization.replace('Bearer ', '')
    const { userId } = jwt.verify(token, APP_SECRET)
    return userId
  }

  throw new Error('Not authenticated')
}

module.exports = {
  APP_SECRET,
  getUserId,
}

  APP_SECRET是用来签署发布给用户的JWT token的加密签名信息。它和prisma.yml中的secret没有任何关系。即如果你要更换数据库层的实现,那么APP_SECRET将继续以完全相同的方式工作,没有影响。prisma.yml中的secret是用来限制访问prisma GraphQLAPI的;而APP_SECRET是发给用于的身份信息,用户每次发送请求携带token便可以验证用户的身份。用户身份验证,及访问都是在App层,不会涉及到prisma database层。

getUserId函数是一个辅助函数,您将在需要验证用户的解析器中调用。它首先从传入的HTTP请求中检索Authorization标头(包含User's JWT)。然后它验证JWT并从中检索用户的ID。请注意,如果该进程因任何原因未成功,该函数将抛出异常因此,您可以使用它来“保护”需要身份验证的解析程序。

最后,您需要将所有内容导入Mutation.js

const { APP_SECRET } = require('../utils')

 然后修改post,必须要验证的用户才能增加link,并且在增加的link中写入postBy的相关信息

function post(root,args,context,info)=>{
  const userId = getUserId(context)
  return context.db.mutation.createLink({
    url:args.url,
    description:args.description,
    postBy:{
      connect:{id:userId}
    }
  },info)
}

  首先:使用getUserId从context中解析出userID.如果没有成功解析出id,就会爆出异常。

  其次:我们使用connect userId来连接一个已经存在的User对象。

查看connect mutation

https://www.prisma.io/docs/reference/prisma-api/mutations-ol0yuoz6go/#overview

4、在index.js中使用新的解析器

const Query = require('./resolvers/Query')
const Mutation = require('./resolvers/Mutation')
const AuthPayload = require('./resolvers/AuthPayload')


const resolvers={
  Query,
  Mutation,
  AuthPayLoad
}

  

重启服务器:

node src/index.js

  

重新打开playground

graphql playground

  

5、在playground中测试一下

新增一个用户

mutation {
  signup(
    name: "Alice"
    email: "alice@graph.cool"
    password: "graphql"
  ) {
    token
    user {
      id
    }
  }
}

  从服务器的响应中,复制身份验证token并在Playground中打开另一个选项卡。在新选项卡中,打开左下角HTTP HEADERS窗格并指定Authorization标题 - 类似于之前对Prisma Playground所做的操作。再次,用实际令牌替换__TOKEN__。每个客户端请求都必须带上token.

mutation {
  post(
    url: "www.graphql-europe.org"
    description: "Europe's biggest GraphQL conference"
  ) {
    id
  }
}

  模拟客户端登陆后可以发表新的Link了。

要验证一切正常,可以使用login验证:

mutation {
  login(
    email: "alice@graph.cool"
    password: "graphql"
  ) {
    token
    user {
      email
      links {
        url
        description
      }
    }
  }
}

  好了,现在登陆和验证功能就全部实现了。

posted @ 2018-09-10 18:08  tutu_python  阅读(151)  评论(0)    收藏  举报