编写登录页面,让自己的页面有权限管理功能。
登录验证--会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:客户端会话跟踪技术(Cookie),服务端会话跟踪技术(Session),令牌技术
一、使用session进行权限初步控制。
代码内容
login.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--引入组件库-->
<script src="./js/jquery.min.js"></script>
<script src="./js/vue.js"></script>
<script src="./js/element.js"></script>
<script src="./js/axios-0.18.0.js"></script>
<link rel="stylesheet" href="./js/element.css">
<title>登录页面</title>
</head>
<body>
<div id="app">
<div class="Login_container">
<div class="Login_box">
<div class="avatar_box">
<img src="./image/head.jpg" alt="">
</div>
<!--登录表单区-->
<el-form :model="form" :rules="rules" ref="loginForm" label-width="80px" class="login_form">
<el-form-item label="用户" prop="username">
<el-input v-model="form.username" ></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<div class="btns">
<el-button type="primary" :plain="true" @click="submitForm('loginForm')">登录</el-button>
<el-button type="primary" @click="resetForm('loginForm')">重置</el-button>
</div>
</el-form>
</div>
</div>
</div>
</body>
<script>
new Vue({
el:"#app",
data(){
return{
//登录表单的数据绑定对象
form:{
username:'',
password:''
},
// tableData:[],
//表单的验证规则对象
rules:{
//用户名合法
username: [
{ required: true, message: '请输入登录名称', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
//密码合法
password:[
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' }
]
}
};
},
methods:{
//登录
submitForm(formName) {
console.log(this.form.username);
console.log(this.form.password);
this.$refs[formName].validate((valid) => {
if (valid) {
axios.post('/login',{
'username':this.form.username,
'password':this.form.password
}).then(res=>{
if(res.data.code) {
// 假设服务器返回了正确的登录信息
// this.tableData = res.data;
// console.log(this.tableData);
location.href='order_show.html';
alert('登录成功')
}
else alert('登录失败');
}).catch(error => {console.error(error);})
}
else {
console.log('error submit!!');
return false;
}
});
},
//重置
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
})
</script>
<style>
#app{
height:100%;
}
.Login_container{
background-image: url("./image/login.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
height: 100%;
/* 如果容器是 body 或其他可能不完全占据视口的元素,你可能需要额外的样式来确保它占据整个视口 */
width: 100%;
}
.Login_box{
width:450px;
height: 300px;
background-color:rgba(255,255,255,0.3);
border-radius: 3px;
position: absolute;
left: 36%;
top:55%;
transform: translate(-50%,-50%);
}
.avatar_box{
height: 130px;
width:130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 8px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
top:4%;
transform: translate(-50%,-50%);
background-color:#fff;
}
img{
width:120px;
border-radius: 50%;
background-color: #eee;
}
.login_form{
position:absolute;
bottom:25px;
left:30px;
}
.btns{
display:flex;
justify-content:flex-end;
}
</style>
</html>
order_show.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--引入组件库-->
<script src="./js/jquery.min.js"></script>
<script src="./js/vue.js"></script>
<script src="./js/element.js"></script>
<script src="./js/axios-0.18.0.js"></script>
<link rel="stylesheet" href="./js/element.css">
<title>订单管理</title>
</head>
<body>
<div id="app">
<div v-if="resData.code==1">
<el-container style="height: 100vh; display: flex; flex-direction: row;">
<!--侧边栏-->
<el-aside width="200px" >
<el-menu background-color="#FEF5E4" text-color="#F09466" active-text-color="#ffd04b" style="position: absolute;top: 100px;">
<el-menu-item index="1">
<i class="el-icon-menu"></i>
<span slot="title">
用户管理
</span>
</el-menu-item>
<el-menu-item index="2">
<i class="el-icon-document"></i>
<span slot="title">
电影信息管理
</span>
</el-menu-item>
<el-menu-item index="3">
<i class="el-icon-document"></i>
<span slot="title">
场次管理
</span>
</el-menu-item>
<el-menu-item index="4">
<i class="el-icon-document"></i>
<span slot="title">
订单管理
</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<!--头部-->
<el-header style="height:80px; position: sticky; top: 0;z-index: 10" >
<div>
<img src="./image/head.jpg" alt="海绵宝宝">
影院管理系统
</div>
<el-button type="info" @click="back" style="height:40px; background-color: #D7A93A">退出</el-button>
</el-header>
<!--查询-->
<h1 style="text-align: center">用户信息</h1>
<div align="center">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="用户ID">
<el-input v-model="formInline.uid" placeholder="用户ID"></el-input>
</el-form-item>
<el-form-item label="电影ID">
<el-input v-model="formInline.mid" placeholder="电影ID"></el-input>
</el-form-item>
<el-form-item label="厅号">
<el-input v-model="formInline.hid" placeholder="厅号"></el-input>
</el-form-item>
<el-form-item>
<el-button type="warning" plain @click="onSubmit">查询</el-button>
</el-form-item>
<!--新增信息弹框-->
<el-dialog title="新增订单信息" :visible.sync="insertRule.insertDialog" width="50%" center>
<el-form :model="order" label-width="100px">
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="用户ID" prop="uid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.uid" placeholder="请输入用户ID"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="电影ID" prop="mid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.mid" placeholder="请输入电影ID"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="厅号" prop="hid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.hid" placeholder="请输入厅号"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="观影时间" prop="time">
<el-date-picker
v-model="order.time"
type="datetime"
placeholder="选择观影时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="座位号" prop="snum">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.snum" placeholder="请输入座位号"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="价格" prop="price">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.price" placeholder="请输入价格"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="checkRule_footer">
<el-button @click="cancelIn">取 消</el-button>
<el-button type="primary" @click="insert">确 定</el-button>
</span>
</el-dialog>
<el-form-item>
<el-button type="warning" plain @click="handleInsert">添加</el-button>
</el-form-item>
</el-form>
</div>
<!--主要内容区-->
<el-main style="flex: 1; overflow: auto;">
<el-card>
<!--用户信息表-->
<el-table :data="tableData.filter(data => !search || data.oid.toString().toLowerCase().includes(search.toLowerCase()))" style="width: 100%">
<el-table-column prop="oid" label="订单编号" width="180"></el-table-column>
<el-table-column prop="uid" label="用户ID" width="180"></el-table-column>
<el-table-column prop="mid" label="电影ID"></el-table-column>
<el-table-column prop="hid" label="厅号"></el-table-column>
<el-table-column prop="time" label="观影时间"></el-table-column>
<el-table-column prop="snum" label="座位号"></el-table-column>
<el-table-column prop="price" label="价格"></el-table-column>
<el-table-column prop="createtime" label="订单创建时间"></el-table-column>
<el-table-column align="right">
<template slot="header" slot-scope="scope">
<el-input v-model="search" size="mini" placeholder="输入订单编号搜索"></el-input>
</template>
<template slot-scope="scope">
<!--编辑信息弹框-->
<el-dialog title="更新订单信息" :visible.sync="editRule.editDialog" width="50%" center>
<el-form :model="editRule.ruleForm" label-width="100px">
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="用户ID" prop="uid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.uid" placeholder="请输入用户ID"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="电影ID" prop="mid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.mid" placeholder="请输入电影ID"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="厅号" prop="hid">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.hid" placeholder="请输入厅号"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="观影时间" prop="time">
<!-- <el-row :gutter="4">-->
<!-- <el-col :span="12" class="checkRule_list">-->
<!-- <el-input v-model="order.time" placeholder="请输入观影时间"></el-input>-->
<!-- </el-col>-->
<!-- </el-row>-->
<el-date-picker
v-model="order.time"
type="datetime"
placeholder="选择观影时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss">
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="座位号" prop="snum">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.snum" placeholder="请输入座位号"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="20">
<el-form-item label="价格" prop="price">
<el-row :gutter="4">
<el-col :span="12" class="checkRule_list">
<el-input v-model="order.price" placeholder="请输入价格"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="checkRule_footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="update">确 定</el-button>
</span>
</el-dialog>
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--分页-->
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage4"
:page-sizes="[20, 30, 40, 50]"
:page-size="pageSize"
:total="total">
</el-pagination>
</el-card>
</el-main>
</el-container>
</el-container>
</div>
<div v-else-if="resData.code==0" style="position: absolute;top: 30%;left: 20%;font-size: 100px">
<!-- {{resData.code}}没有权限访问!-->
没有权限访问!
</div>
</div>
</body>
<script>
new Vue({
el:"#app",
data(){
return{
tableData:[],
search: '',
currentPage4: 1,
pageSize: 20,
total:null,
//编辑规则
editRule: {
editDialog: false, //弹窗
},
insertRule: {
insertDialog: false, //弹窗
},
order:{
time:''
},
//查询
formInline:{
uid:'',
mid:'',
hid:''
},
//验证是否登录
resData:{
code:'',msg:'',data:''
},
}
},
methods:{
//返回
back(){
var URL='/logout';
axios.get(URL).then(res=>{
location.href='login.html'
})
},
//新增
handleInsert(){
this.insertRule.insertDialog=true;
},
cancelIn(){
this.insertRule.insertDialog=false;
},
insert(){
var url = '/insertOrder';
axios.put(url,this.order).then(res=>{
if(res.data.code){
this.insertRule.insertDialog=false;
location.href='order_show.html';
}else{alert(res.data.message);}
}).catch(error=>{})
},
//编辑
handleEdit(index, row) {
this.editRule.editDialog=true;
var URL=`getByOid/${row.oid}`
axios.get(URL).then(response=>{
if(response.data.code){
this.order=response.data.data;
}
}).catch(error=>{})
console.log(index, row);
},
//取消弹框
cancel(){
this.editRule.editDialog=false;
},
//弹框编辑信息并确认
update:function(){
var URL='updateOrder';
//put请求用于更新资源.url 是你想要更新的资源的地址;this.poet是包含更新后数据的对象,这个对象将被发送到服务器以更新资源。
axios.put(URL,this.order).then(res=>{
if(res.data.code){
this.editRule.editDialog=false;
location.href='order_show.html';
}else{alert(res.data.message);}
}).catch(error=>{})
},
//删除
handleDelete(index, row) {
if (window.confirm("确定要删除该记录吗?")) {
axios.post(`/deleteOrder?oid=${row.oid}`).then(ans => {
alert("删除成功");
this.findAll();
}).catch(function (error) {console.log(error);})
}
console.log(index, row);
},
//分页
findAll() {
var URL = `/listOrder/${this.currentPage4}/${this.pageSize}`
axios.get(URL).then(res => {
if (res.data.code) {
this.tableData = res.data.data.rows;
this.total = res.data.data.total;
}
}).catch(error => {
console.error(error);
})
},
handleSizeChange(val) {
this.pageSize = val;
this.findAll();
console.log(`每页 ${val} 条`);
},
handleCurrentChange(val) {
this.currentPage4 = val;
this.findAll();
console.log(`当前页: ${val}`);
},
//查询
onSubmit() {
var url = `/listOrderWithConditions/${this.currentPage4}/${this.pageSize}?uid=${encodeURIComponent(this.formInline.uid)}&mid=${encodeURIComponent(this.formInline.mid)}&hid=${encodeURIComponent(this.formInline.hid)}`
console.log(this.formInline.uid)
console.log(this.formInline.mid)
console.log(this.formInline.hid)
axios.get(url)
.then(res =>{
this.tableData = res.data.data.rows;
this.total=res.data.data.total;
console.log(this.tableData);
console.log(this.total);
})
.catch(error=>{
console.error(error);
})
},
},
// mounted(){
// axios.get('/listOrder').then(res=>{
// if(res.data.code){
// this.tableData = res.data.data;
// }
// });
// },
mounted() {
var url = `/index1`
axios.get(url)
.then(response => {
this.resData = response.data;
console.log(this.resData);
})
.catch(error=>{
console.error(error);
})
},
created() {
this.findAll();
}
})
</script>
<style>
a {
color: white;
text-decoration: none;
}
.el-header {
background-color: #FEF5E4;
display: flex;
justify-content: space-between;
align-items: center;
color: #F09466;
font-size: 40px;
}
> div {
display: flex;
align-items: center;
}
img {
width: 55px;
border-radius: 50%;
background-color: #eee;
}
.el-aside {
background-color:#FEF5E4;
height: 100%;
}
body>.el-container {
margin-bottom: 40px;
}
.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
line-height: 260px;
}
.el-container:nth-child(7) .el-aside {
line-height: 280px;
}
</style>
</html>
LoginController
package edu.wust.controller;
import edu.wust.pojo.Result;
import edu.wust.pojo.User;
import edu.wust.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/login")
public Result login(HttpServletRequest request, @RequestBody User users){
log.info("管理员登录:{}",users);
User u = loginService.login(users);
if (u!=null){
log.info("u:{}",u);
request.getSession().setAttribute("user",users.getUsername());
String user = (String) request.getSession().getAttribute("user");
System.out.println("查询不为空。"+user);
return Result.success();
}else{
String user = (String) request.getSession().getAttribute("user");
System.out.println("查询为空。"+user);
return Result.error("用户名或密码错误");
}
}
@RequestMapping("/logout")
public Result logout(HttpServletRequest request){
request.getSession().removeAttribute("user");
return Result.success("退出成功");
}
@RequestMapping ("/index1")
public Result index1(HttpServletRequest request) {
String user = (String) request.getSession().getAttribute("user");
if (user != null) {
return Result.success();
} else {
return Result.error("无权限");
}
}
}
LoginMapper
package edu.wust.mapper;
import edu.wust.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface LoginMapper {
@Select("select * from user where username=#{username} and password=#{password}")
User getByUP(User user);
}
pojo.User(管理员信息)
新建管理员信息的数据库表
package edu.wust.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
private String name;
private String gender;
private String position;
}
LoginService
package edu.wust.service;
import edu.wust.pojo.User;
import org.springframework.stereotype.Service;
@Service
public interface LoginService {
User login(User user);
// boolean login(User user);
}
LoginServiceImpl
package edu.wust.service.impl;
import edu.wust.mapper.LoginMapper;
import edu.wust.pojo.User;
import edu.wust.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private LoginMapper loginMapper;
@Override
public User login(User user){
return loginMapper.getByUP(user);
}
// @Override
// public boolean login(User user){
// Integer result = loginMapper.getByUP(user);
// return result == 1;
// }
}
页面展示
用户密码错误点击登录显示:
点击重置:
输入正确的用户密码后点击登录:
点击退出后返回登录页面:
此时直接访问order_show.html:
二、JWT令牌技术
令牌:用户身份的标识。
JWT:JSON Web Token 定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
pom.xml引入jwt依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
//补充依赖
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
utils.JwtUtils
package edu.wust.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "itheima";
private static Long expire = 43200000L;
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}
LoginController
package edu.wust.controller;
import edu.wust.pojo.Manager;
import edu.wust.pojo.Result;
import edu.wust.service.LoginService;
import edu.wust.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
// 基础登录功能
// @RequestMapping("/login")
// public Result login(@RequestBody Manager manager){
// log.info("管理员登录:{}",manager);
// Manager m = loginService.login(manager);
// return m!=null ?Result.success():Result.error("用户名或密码错误");
// }
//JWT令牌登录验证
@RequestMapping("/login")
public Result login(@RequestBody Manager manager){
log.info("管理员登录:{}",manager);
Manager m = loginService.login(manager);
//登录成功,生成令牌,下发令牌
if(m != null){
Map<String, Object> claims = new HashMap<>();
claims.put("id",m.getId()); //把管理员id信息存入claims中
claims.put("name",m.getName());
claims.put("username",m.getUsername());
String jwt = JwtUtils.generateJwt(claims); //jwt中包含了当前登录的员工信息
return Result.success(jwt);
}
//登录失败,返回错误信息
return m!=null ?Result.success():Result.error("用户名或密码错误");
}
}