{"id":14976331,"url":"https://github.com/selvin11/login","last_synced_at":"2025-04-09T20:05:19.129Z","repository":{"id":37686811,"uuid":"86223413","full_name":"Selvin11/login","owner":"Selvin11","description":"Vue + Vue-router + Vuex 实现前端页面及逻辑，Express 实现注册登录登出的RestFul API 。","archived":false,"fork":false,"pushed_at":"2017-04-06T12:14:03.000Z","size":81,"stargazers_count":317,"open_issues_count":3,"forks_count":91,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-09T20:05:12.680Z","etag":null,"topics":["axios","element-ui","express","gulp","login","vue-router2","vue2","webpack2"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Selvin11.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-26T10:08:40.000Z","updated_at":"2025-03-30T19:56:19.000Z","dependencies_parsed_at":"2022-08-08T21:15:54.378Z","dependency_job_id":null,"html_url":"https://github.com/Selvin11/login","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Selvin11%2Flogin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Selvin11%2Flogin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Selvin11%2Flogin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Selvin11%2Flogin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Selvin11","download_url":"https://codeload.github.com/Selvin11/login/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248103870,"owners_count":21048245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axios","element-ui","express","gulp","login","vue-router2","vue2","webpack2"],"created_at":"2024-09-24T13:53:44.040Z","updated_at":"2025-04-09T20:05:19.106Z","avatar_url":"https://github.com/Selvin11.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e 项目介绍\n\n* 采用`vue-cli` 构建初始项目目录\n* 使用`vue2`、`vue-router2`、`Element-ui`、`express`、`webpack2`、`gulp`构建项目开发环境\n* 前端采用Vue、Element-ui搭建页面以及Vuex进行数据处理，后端采用express完成注册、登录、登出、登录状态查询的restful API\n* 目前分为session登录和token登录\n* 页面为注册页，登录页，主页\n\n\u003e 目录及文件的改动，均含详细备注\n\n1. package.json中的'dependencies'包含的是增加的npm包\n2. config/index.js 中设置了请求端口代理，能够跨域访问服务器端口（8080 =\u003e 3000）\n3. server  主要是服务端的路由以，api接口，mongodb的配置操作\n4. src 为vue应用的主要文件，包含路由，component...\n5. gulpfile.js 增加服务器重启和浏览器刷新任务\n6. server.js  服务端启动文件\n\n\u003e 项目区分\n\n1. session登录的前后端对应src和server\n2. token登录的前后端对应src-token和server-token\n3. 主要是通过express设置当前目录为静态资源位置，通过根目录下的index.html进入不同的src下的index.html，从而实现项目区分\n\n\u003e 操作指令\n\n```shell\nnpm install 安装依赖\n\nnpm run dev 使用webpack开启前端资源的打包编译\n\nnpm run server  启动服务端并开启浏览器\n```\n\n这里需要双开两个命令行窗口，一个负责前端的编译，一个负责服务端的任务流\n\n\u003e 环境搭建的详细解决思路\n\n[前端热更新，后端服务重启，浏览器自动刷新]( http://selvinpro.com/2017/03/20/browser-reload/#more)\n\n- [x] 实现了基本登录 cookie \u0026\u0026 session\n- [x] 独立系统登录 token\n- [ ] 第三方登录 access_token\n\n\n\n\n---\n\n\n\n\u003e 详谈注册/登录/登出\n\n一. [登录功能实现的原理](#1)\n二. [登录功能实现的几种方式](#2)\n三. [基本登录 cookie \u0026\u0026 session 详解](#3)\n四. [token登录 详解](#4)\n\n\u003ch3 id=\"1\"\u003e一. 登录功能实现的原理\u003c/h3\u003e\n\n浏览器用户提交表单后与服务器产生会话，传递给服务器一个识别信息，同时服务器这边有相应的数据，因此能够对其进行比较，然后识别是否为正确的用户，而且一般需要设定此数据的存储时长，也就是用户认证的时长，过了这个时间段，则会失效，需要重新登录，因此这里需要有四个关键点，**浏览器的信息**，**服务器的信息**，**两个信息的对比**，**时效设定**。\n\n\u003ch3 id=\"1\"\u003e二. 登录功能实现的几种方式\u003c/h3\u003e\n\n1. 基本登录 cookie \u0026\u0026 session\n   浏览器提交用户表单后，服务器接受表单中的用户信息，将其存在session表中（session表存在内存，数据库，缓存等），然后以cookie的方式（cookie中存有sessionId及对应值）传回给浏览器，这样只要服务端设置的cookie没有失效，则在这段时间内，服务端便能正确识别用户信息，不用重复登录（sessionID是在服务端销毁的）\n\n   * 服务器端的产生Session ID\n   * 服务器端和客户端存储Session ID\n   * 从HTTP Header中提取Session ID\n   * 根据Session ID从服务器端的Hash中获取请求者身份信息\n\n2. `token`（JSON Web Token）\n\n   参考资料：\n\n   [JSON Web Token详情](http://blog.leapoahead.com/2015/09/06/understanding-jwt/)\n\n   [八幅漫画理解使用JSON Web Token设计单点登录系统](http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/)\n\n   用户提交其信息表单（比如包含username,password），服务端收到后，将username转为userId存储在JWT的payload（负荷）中，与头部进行Base64编码拼接后进行签名，于是形成了JWT，在cookie中保存返回给浏览器并设置时效，在失效之前浏览器每次请求时都会携带有JWT的cookie，因此服务端可以对JWT进行解密，与数据库中的user进行比较，确认无误后，便返回请求。\n\n   * cookie\u0026\u0026session 需要服务端提供独立机制来存储sessionId(内存，数据库等)\n   * cookie\u0026\u0026token 不需要服务端进行额外存储，但增加了加密，解密，编码等操作\n\n   \u003e 单点登录（一个站点登录，其余站点皆可登录）\n\n   * cookie\u0026\u0026session sessionId是需要存储在服务器上的，因此多个域名下的服务器都需要同步sessionId\n   * JWT是通过cookie传递的，并且不需要额外存储，只需要将含有JWT的cookie的域名设置为顶级域名，则旗下的域名皆可访问到此cookie以及其中包含的JWT\n\n\n3. 第三方：`access_token`\n\n\n\n\u003ch3 id=\"3\"\u003e三. 基本登录 cookie \u0026\u0026 session 详解 \u003c/h3\u003e\n\n1. 通过mongoose建立数据模型，以name为主索引并设置其唯一属性，此例中，我们服务端采用内存储存sessionId，也可尝试mongoDB或redis等，均有相应的中间件可以使用。\n\n2. 登录   POST：  `/api/login` \n  在前端vue中怎样识别登录状态，当输入用户表单后点击登录，我们向`/api/login `发起了`pos`t请求，`/api/login `对应的控制器中进行了这样几步流程\n\n  * request中包含的用户信息（用户名，密码），生成mogoose实例\n\n  * 进入mongoDB中查找此用户名的信息（User 的Skema模型中已经设定name为唯一的索引，不能重复的）\n\n  * 没有找到，返回无此用户名，找到则与数据库中的密码与此密码进行比较（在生成mongoose实例时，都将密码进行了sha1加密）\n\n  * 密码匹配正确，返回正确信息，并写入session\n\n        // 用户信息写入 session\n        delete user.password;\n        req.session.user = user;\n\n  * 密码匹配失败，否则返回密码错误的信息\n\n    在其它如/api/register、/api/user中通过判断req.session.user的存在与否，来实现登录登出状态的改变\n\n    ```javascript\n    checkLogin(req, res, next) {\n        if (!req.session.user) {\n          return res.json({\n             error: '未登录'\n          });\n        }\n        next();\n      },\n\n      checkNotLogin(req, res, next) {\n        if (req.session.user) {\n          return res.json({\n            error: '已登录'\n          });\n        }\n        next();\n      }\n    ```\n\n    至此，当流程跑通之后，我们能够得到返回登录成功的信息，接下来就可以在前端通过`vue-router`实现跳转，以及将获取的user信息填入`localstorage`，供其它页面使用其数据。\n\n3. 注册   POST： `/api/register`\n\n   注册功能的实现，同样当我们填写完注册表单后， `/api/register`对应的控制器中将接受我们提交之后的数据。\n\n   * 通过request获取用户名和密码，并将密码加密（此处采用sha1），生成mongoose实例，同时通过`objectid-to-timestamp `将实例中的objectId转化为时间格式，即为此实例的创建时间，具体可看**疑问详解**。\n   * 通过唯一的name属性，进入数据库查询，看该用户名是否已经被注册，返回相应的数据。\n\n4. 登出   GET：`/api/user`\n\n\n   此api的作用是实现登出功能的，当点击登出后，vue通过axios访问此api，api对应的控制器中将执行以下代码\n\n   ```javascript\n   // delete user session\n   req.session.user = null;\n   res.json({\n     message:'登出成功'\n   })\n   ```\n5. 登录状态判断\n\n    使用axios的拦截器，后端通过checkLogin函数判断session是否存在，返回sesssion的状态，在axios的响应拦截器中进行跳转设置，具体代码参见`./src/util/interceptor.js`\n\n\n\u003ch3 id=\"4\"\u003e四. token登录 详解 \u003c/h3\u003e\n\n* 注册登录的实现基本和session相同，除了数据库模型中添加了token属性，然后主要是登录状态的不同\n\n* 使用token判断登录状态，主要是后端的checkToken，将前端请求中携带的token进行解码，获取其设置的有效时长，从而返回token状态，同样使用axios的拦截器，在请求拦截器中添加头部Authorization的token，在响应拦截器中设置判断\n\n---\n\n\n\n\n\n\u003e 疑问详解\n\n1. objectid-to-timestamp 包的作用\n\n   首先了解MongoDB中的`ObjectId`，每一个document（即MySQL中的row每一行数据），均含有一个`_id`的属性，而其属性值即为`ObjectId`\n\n   `ObjectId` 是一个12字节 BSON 类型数据，有以下格式：\n\n   - 前4个字节表示时间戳（**数据创建的时间**）\n   - 接下来的3个字节是机器标识码\n   - 紧接的两个字节由进程id组成（PID）\n   - 最后三个字节是随机数。\n\n   因此它的作用即是将前四个字节转化为时间格式。\n\n2. 浏览器刷新之后，vuex维护的全局状态全部消失 ​\n\n  * 理解vuex的作用，以及使用本地存储一些状态数据的意义\n  * vuex状态管理是为了可维护性和可扩展性，和本地缓存没有任何关系。\n  * [详情可看github issue](https://github.com/vuejs/vuex/issues/47) \n\n3. vue-router中的路由拦截详细介绍\n\n  ```javascript\n    router.beforeEach((to, from, next) =\u003e {\n      next({\n          path: '/login',\n          query: {\n              redirect: to.fullPath\n          }\n      })\n    })\n    // vue-router 的导航钩子函数beforeEach\n    to : 将要进入的 路由对象\n    from : 当前导航正要离开的路由\n    next : 调用next()， resolve beforeEach这个钩子，即调用后，表示这个钩子函数结束了，同时里面可以设置一些跳转等\n\n    path: 表示将要跳转的路由\n    query: 配置路由url的参数\n    fullPath: 完成解析后的 URL，包含查询参数和 hash 的完整路径。\n\n    query: {\n        redirect: to.fullPath\n    }\n\n    这个表示，在当前路由中添加查询参数redirect，以及redirect的值to.fullPath，\n    to.fullPath表示跳转之前的路由url\n\n  ```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselvin11%2Flogin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fselvin11%2Flogin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fselvin11%2Flogin/lists"}