http是一个无状态协议,比如无法识别用户,保持登录状态
服务器识别用户
使用session识别用户
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>首页</h1>
<form action="/logout" method="post">
<button>登出</button>
</form>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<button>登录</button>
</form>
</body>
</html>
home.js
'use strict';
const { Controller } = require('egg');
class HomeController extends Controller {
async index() {
const { ctx } = this;
if(this.ctx.session.user){
await ctx.render("index");
}else{
ctx.redirect("/login");
}
}
async login(){
await this.ctx.render("login");
}
async doLogin(){
let username = this.ctx.request.body.username;
let password = this.ctx.request.body.password;
if(username == "admin" && password == "123456"){
this.ctx.session.user = username;
this.ctx.redirect("/");
}else{
this.ctx.redirect("/login");
}
}
async logout(){
this.ctx.session.user = "";
this.ctx.redirect("/login");
}
}
module.exports = HomeController;
router.js
'use strict';//严格模式
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/login', controller.home.login);
router.post('/login', controller.home.doLogin);
router.post('/logout', controller.home.Logout);
};
post拦截记得要去掉
使用jwt(json web token)识别用户
config.default.js
生成token和校验token
async index(){
//这段用于教学
let user = {
username:"admin"
}//json对象
/*egg-jwt插件导入后能引用this.app里的jwt属性来获取jwt对象
它的sign方法能将里面的对象签名以实现加密*/
//用户登录服务器会生成一个token,然后把token发送给客户端
let token = this.app.jwt.sign(user,this.app.config.jwt.secret);//对象,密钥
/* this.ctx.body = token;
得到一串很长的字符串
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjc1Njg5MDUxfQ.
eC0L0YRrxTYKrpP3uNUJ0aaPIs8Sl4ZMcZhmDoKDKp4
*/
try{
//用户再次请求数据的时候都要带着生成的token给服务器校验
let decode = this.app.jwt.verify(token,this.app.config.jwt.secret);
/*解密token以校验,如果校验得出来就说明用户登录验证成功。
如果token是伪造的,则会跳转到报错页
如果不想跳转报错,想给反馈,就用捕获异常*/
/*this.ctx.body = decode;//{"username":"admin","iat":1675689453}
前面是token的信息,后面是时间戳
*/
}catch(e){
this.ctx.body = "token验证失败"
}
}
功能实现
以前后端分离的形式
后端egg
先行配置jwt、cors插件
jwt.js
const Controller = require("egg").Controller
class JwtController extends Controller {
async index(){
this.ctx.body = "hi,egg";
}
async doLogin(){//处理用户登录,验证成功就发个token
let user = this.ctx.request.body.user;//前端传过来的user,包含username和password属性
if(user.username === "admin" && user.password === "123456"){
let user_jwt = {userjwt:user.username};//用user.username来签名生成token
let token = this.app.jwt.sign(user_jwt,this.app.config.jwt.secret);
this.ctx.body = {
code:20000,
token:token
}//验证成功就给个响应,code和token是自己定义的,像msg那样
}else{//登录失败的响应
this.ctx.body = {
code:40000,
msg:"用户名或密码错误"
}
}
}
async getMessage(){//前端向后台获取信息时校验token
let token = this.ctx.request.header.token;//获取请求头里的token属性的值
try{
//用户再次请求数据的时候都要带着生成的token给服务器校验
let decode = this.app.jwt.verify(token,this.app.config.jwt.secret);
/*解密token以校验,如果校验得出来就说明用户登录验证成功。
如果token是伪造的,则会跳转到报错页
如果不想跳转报错,想给反馈,就用捕获异常*/
/*this.ctx.body = decode;//{"username":"admin","iat":1675689453}
前面是token的信息,后面是时间戳
*/
this.ctx.body = "hello jwt";
}catch(e){
this.ctx.body = "token验证失败"
}
}
}
module.exports = JwtController
router.js
'use strict';//严格模式
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/login', controller.home.login);
router.post('/login', controller.home.doLogin);
router.post('/logout', controller.home.logout);
router.get('/jwt', controller.jwt.index);
router.post('/jwtlogin', controller.jwt.doLogin);//处理前端/login提交的表单
router.get('/jwtmessage', controller.jwt.getMessage);//处理响应数据
};
前端vue
Login.vue
<template>
<h1>登录</h1>
<form @submit.prevent="doLogin">
<input type="text" name="username" v-model="user.username" placeholder="用户名">
<input type="password" name="password" v-model="user.password" placeholder="密码">
<button>登录</button>
</form>
</template>
<script>
import axios from "axios";
export default {
data(){
return {
user:{
username:"",
password:""
}
}
},
methods:{
doLogin(){
axios.post("http://127.0.0.1:7001/jwtlogin",{user:this.user})//跨域发送user对象给后台负责登录验证的地址/jwtlogin
.then((res)=>{
if(res.data.code === 20000){//后台响应的数据里有个code字段,值为20000则成功
localStorage.setItem("token",res.data.token);//把响应的token值存在本地储存的token字段中
console.log(res.data.token)
this.$router.push("/");
}
})
}
}
}
</script>
Home.vue
<template>
<h1>首页</h1>
<button @click="getMessage">获取数据</button>
<button @click="logout">登出</button>
</template>
<script>
import axios from 'axios';
export default {
methods:{
getMessage(){
let token = localStorage.getItem("token");//从本地存储中拿到token
axios.get("http://127.0.0.1:7001/jwtmessage",{headers:{token:token}}).then((res)=>{
/*axios的get请求,它的第二个参数能设置请求头响应头之类的信息。
headers代表的是请求头,headers:{token:token}的意思是
在请求头中设置一个字段为token(:左边)的属性,字段的值为token(:右边)
如果是post请求,第二个参数是数据,第三个才算请求头*/
})
},
logout(){
localStorage.setItem("token","");
location.reload();//js的方法,能自动刷新页面
}
}
}
</script>
App.vue
<template>
<router-view></router-view><!--用router-view占位,换页后显示的组件放在这里-->
</template>
<script>
export default {
name: 'App',
}
</script>
router.js
import { createRouter,createWebHashHistory } from "vue-router";//引入创建路由的方法和哈希模式
import Home from "./components/Home.vue"//要加上.vue不让显示不出来
import Login from "./components/Login.vue"
const router = createRouter({
history:createWebHashHistory(),//路由模式
routes:[//配置路由
{
path:"/",//首页
component:Home//跳转到Home组件
},
{
path:"/login",
component:Login
}
]
});//创建路由
router.beforeEach((to,from,next) => {
//只有存在token时才能跳转到内容页
//localStorage的getItem方法能根据数据名获取它的值
let token = localStorage.getItem("token");
if(token || to.path === "/login"){//有token或者访问的是登录页
next();
}else {
next("/login");//否则让路由跳转到登录页
}
})
export default router;
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router.js'//引入router
const app = createApp(App)
app.use(router);
app.mount('#app')
用户携带token的方式,把token放到请求头requestHeader中。后台也由此校验
结果
点击登录
点击获取数据
点击登出