目录
一、前端登录发起请求
二、后端请求接收
三、连接数据库
四、后端响应
五、前端处理
六、在前端验证用户是否登录
七、web会话跟踪
八、请求拦截器和响应拦截器
本文Vue-cli前端项目基于文章:
Vue-cli搭建项目(包含Node.js安装和ElementUI安装)_小俱的一步步的博客-CSDN博客
后端javaEE项目基于文章:
创建web后端程序(servlet程序搭建)_小俱的一步步的博客-CSDN博客
一、前端登录发起请求
1.安装axios,使用框架
axios 是一个 HTTP 的网络请求库,安装 npm install axios;
在 main.js 中配置 axios,导入网络请求库:import axios from 'axios';
设置访问后台服务器地址:axios.defaults.baseURL="http://127.0.0.1:8080/webBack/";
将 axios 挂载到 vue 全局对象中,使用 this 可以直接访问:Vue.prototype.$http=axios;
//网络请求
//导入 axios
import axios from 'axios';
//设置访问后台服务器地址
axios.defaults.baseURL = "http://127.0.0.1:8080/webBack/";
//将 axios 挂载到 vue 全局对象中,使用 this 可以直接访问
Vue.prototype.$http = axios;
2.组装请求数据:
在Login(登录)组件中获取前端数据:
data() {
return {
form: {
account: '',
password: ''
}
}
},
由于向前端提供的数据为json格式,后端不识别,因此将json对象序列化为键=值&键=值:
//将json对象序列化为键=值&键=值
function jsonToString(jsonobj) {
console.log(jsonobj)
var str = "";
for (var s in jsonobj) {
str += s + "=" + jsonobj[s] + "&";
}
return str.substring(0, str.length - 1);
}
二、后端请求接收
创建LoginServlet 类继承 HttpServlet 类,req.getParameter("")接收前端数据
三、连接数据库
1.创建数据库,创建管理员表
CREATE DATABASE web_db CHARSET utf8
CREATE TABLE admin(
id INT PRIMARY KEY AUTO_INCREMENT,
account VARCHAR(20) UNIQUE,
PASSWORD VARCHAR(20)
)
封装用户信息:创建Admin类 属性有int id,String account,String password;生成Getter ,Setter方法。
2.jdbc连接数据库:
import java.sql.*;
public class LoginDao {
public Admin login(String account, String password) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement ps = null;
Admin admain = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");//初始化驱动程序
String url ="jdbc:mysql://127.0.0.1:3306/web_db?serverTimezone=Asia/Shanghai";
String uname="root";
String pwd ="123456";
connection = DriverManager.getConnection(url,uname,pwd);
ps = connection.prepareStatement("SELECT id,account FROM admin WHERE account = ?AND password = ?");
ps.setObject(1,account);
ps.setObject(2,password);
ResultSet rs = ps.executeQuery();
while (rs.next()){
admain = new Admin();
admain.setId(rs.getInt("id"));
admain.setAccount(rs.getString("account"));
}
}finally {
if (connection!=null){
connection.close();
}
if (ps!=null){
ps.close();
}
}
return admain;
}
}
本次后端项目目录:
其中LoginDao放在dao包中 ,Admin放在model包中。
四、后端响应
1.接收到dao返回的查询数据
2.进行数据封装,创建一个CommonResult类,封装后端数据,必须有get、set方法 ,否则转json时会报错,三个属性:
private int code;//自定义状态码
private Object data;//数据
private String message;//消息
3.进行逻辑判断: 根据前端传入的admin数据进行判断,并响应自定义的状态码,都是用封装的CommonResult类转为json格式传递。
后端接收和响应代码:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String account = req.getParameter("account");
String password = req.getParameter("password");
System.out.println(account+":"+password);
//设置响应代码
resp.setContentType("text/html;charset=utf-8");
PrintWriter printWriter = resp.getWriter();
CommonResult commonResult = null;
try{
LoginDao loginDao = new LoginDao();
Admin admin = loginDao.login(account,password);
if (admin!=null){
String token = JWTUtil.getToken(admin);
admin.setToken(token);
commonResult = new CommonResult(200,admin,"登录成功");
}else {
commonResult = new CommonResult(201,"账号密码错误");
}
}catch (Exception e){
e.printStackTrace();
commonResult = new CommonResult(500,"系统忙");
}
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
printWriter.print(json);
}
}
五、前端处理
1.接收后端响应的数据:resp.data接收
this.$http.get("back/test").then((resp) => {
if (resp.data.code == 200) {
}
}
2.前端路由跳转:登录账号密码与数据库中的匹配,页面跳转到/main,也就是路由跳转,在index.js中配置。
//路由导航守卫,每次进行路由跳转时,就会自动的执行此段逻辑
rout.beforeEach((to, from, next) => {
if (to.path == '/') {//由于自己定义的login组件的地址为“/”
// 如果用户访问的登录页,nest直接放行
return next();
} else {
var account = sessionStorage.getItem("account");
if (account == null) {
return next("/");
} else {
next();
}
}
});
3.前端存储用户信息:在生命周期函数中储存账号account
mounted() {
this.account = sessionStorage.getItem("account");
}
4.在main组件中显示用户账号:在Main组件中的mounted()中存储账号信息并在页面显示管理员账号(双向绑定)。
六、在前端验证用户是否登录
如果没有登录就不能访问/main页面,仍停留到login登录页面;在前端router中index.js添加路由导航守卫,在每次路由跳转时进行判断。
rout.beforeEach((to, from, next) => {
if (to.path == '/') {//由于自己定义的login组件的地址为“/”
// 如果用户访问的登录页,nest直接放行
return next();
} else {
var account = sessionStorage.getItem("account");
if (account == null) {
return next("/");
} else {
next();
}
}
});
七、web会话跟踪
http是无状态的,登录完成后客户端与服务器断开了连接,之后再向后端发送请求,后端就不知道是哪个客户端发送的。
使用会话跟踪:
1.登录 向后端发送账号和密码
2.后端与数据库连接验证账号密码
3.如果账号正确,在后端生成一个token(唯一的一种字符串),把token响应给前端,用JWT生成token。
Base64:将八个bit位一组改为六个bit位一组。
JWT组成的三部分:
第一部分头部(header),第二部分载荷(payload, 用户的信息),第三部分签证(signature)。
步骤:将JWT两个jar包加载到后端里
将生成的token存到admin中(即添加token属性),传到前端去。
生成token的工具类:
/**
* JWT工具类
*/
public class JWTUtil {
/**
* 根据用户id,账号生成token
* @param u
* @return
*/
public static String getToken(Admin u) {
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 20*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",u.getId())
.withClaim("account",u.getAccount())
.withExpiresAt(expireDate)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
/**
* 验证token是否有效
* @param token
* @return
*/
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {//当传过来的token如果有问题,抛出异常
return false;
}
}
/**
* 获得token 中playload部分数据,按需使用
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
}
}
4.前端存储token:sessionStorage.setItem("adminToken", resp.data.data.token);
5.之后每次请求,都将token携带者向后端发送。
6.后端的java对请求中的token进行解析验证。
写一个过滤器将每次拿到的token进行验证。
public class AdminTokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("token验证过滤器");
HttpServletRequest request = (HttpServletRequest)servletRequest;
String adminToken = request.getHeader("adminToken");
boolean verify = JWTUtil.verify(adminToken);
if (verify){//token验证没有问题
filterChain.doFilter(servletRequest,servletResponse);//继续向后执行
}else {//token验证失败,向前端响应
servletResponse.setContentType("text/html;charset=utf-8");
PrintWriter printWriter = servletResponse.getWriter();
CommonResult commonResult = new CommonResult(401,"token验证失败,请重新登录");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(commonResult);
printWriter.print(json);
}
}
}
八、请求拦截器和响应拦截器
在main.js中配置两个拦截器
请求拦截器:每发送一次http请求,都会执行此拦截器
axios.interceptors.request.use(config => {
//为请求头对象,添加 Token 验证的 token 字段
config.headers.adminToken = window.sessionStorage.getItem('adminToken');
return config;
})
响应拦截器:
axios.interceptors.response.use((resp) => {
if(resp.data.code == 500){
ElementUI.Message({
message:resp.data.message,
type:"error"
})
}
if(resp.data.code == 401){
ElementUI.Message({
message:"token验证失败",
type:"error"
})
router.replace("/");
}
return resp;
});