1,vue-cli进行项目搭建
2,使用ELement-UI
3,使用vue组件路由
4,点击登录,向后端进行账号密码比对
三种情况:
密码有误
服务器忙
密码正确。
具体步骤:
首先写好前端一个大体框架,然后点击登录按钮时触发的函数,还有使用v-model对数据进行双向绑定。
- <template>
-
- <div class="login_container">
-
- <div class="login_box">
-
- <div class="img_box">
- <img src="./assets/logo.png" />
- div>
- <div style="padding: 100px 30px 30px 0px;">
- <el-form label-width="80px">
- <el-form-item label="账号">
- <el-input v-model="from.account">el-input>
- el-form-item>
- <el-form-item label="密码">
- <el-input type="password" v-model="from.password">el-input>
- el-form-item>
- <el-form-item>
- <el-button type="primary" round @click="login()">登录el-button>
- <el-button>取消el-button>
- el-form-item>
- el-form>
- div>
- div>
- div>
- template>
其次就是js代码:这是vue框架所以格式是按照vue框架给定的来的。这段代码主要作用就是,前端传给后端账号密码后,后端给前端的响应,前端对响应做出判断,通过状态码做出判断 。
- export default {
- data() {
- return {
- from: {
- account: "adele",
- password: "12345678"
- }
-
- }
- },
- methods: {
- login() {
- this.$http.post("LoginServlet", jsonToString(this.from)).then(resp => {
- if (resp.data.code == 201) {
- this.$message({
- message: resp.data.message,
- type: 'warning'
- });
- } else if (resp.data.code == 200) {
- this.$message({
- message: resp.data.message,
- type: 'success'
- });
- sessionStorage.setItem("account", resp.data.data.account);
- sessionStorage.setItem("userToken", resp.data.data.token);
- this.$router.push("/main"); //在js中进行路由导航
- }
-
- })
-
- }
- },
- }
- //将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);
- }
由于后端
5,接下来就是后端:
在写后端之前需要在数据库中创建一个管理员的表,代表登录人信息,因为后端需要和数据库进行比对,如果数据库中存在当前登录的信息才可以进入主页面。
这是登录的servlet,读入流,调用dao的函数进行账号密码比对,通过比对结果向前端传入数据。
由于json跨平台兼容,数据体积小等优点,所以我们通过json格式化java对象传给前端。
- package com.ffyc.webserver.servlet;
-
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.ffyc.webserver.Models.CommonData;
- import com.ffyc.webserver.dao.LoginDao;
- import com.ffyc.webserver.Models.User;
- import com.ffyc.webserver.util.JWTUtil;
-
- 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;
- import java.sql.SQLException;
-
- public class LoginServlet extends HttpServlet {
- private LoginDao dao=new LoginDao();
-
- // @Override
- // protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- //
- // }
- // @Override
- // protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // String account=req.getParameter("account");//接收请求中我们自己提交的数据
- // User user=new User("adele","123456789");
- // ObjectMapper objectMapper=new ObjectMapper();
- // String stu=objectMapper.writeValueAsString(user);
- // resp.getWriter().print(stu);
- // System.out.println(objectMapper.writeValueAsString(user));
- // if(account.equals("admin"))
- // {
- // resp.getWriter().print("账号已注册");
- // }
- // else
- // {
- // resp.getWriter().print("账号未注册");
- // }
- // }
-
- @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);
- System.out.println(password);
- PrintWriter printWriter=resp.getWriter();
- User user= null;
- CommonData commonData=null;
- try {
- user = dao.logindao(account,password);
- if(user==null) {
- commonData=new CommonData(201,"账号或密码输入有误,请重新输入!");
- }
- else {
- user.setToken(JWTUtil.getToken(user));
- commonData=new CommonData(200,user,"登录成功");
- System.out.println(user.getToken());
- }
- } catch (Exception throwables) {
-
- commonData=new CommonData(500,"服务器忙...请稍后重试!");
- }
- ObjectMapper objectMapper=new ObjectMapper();
- String json=objectMapper.writeValueAsString(commonData);
- printWriter.print(json);
- }
- }
然后就是dao层的调用了,因为只要是对数据库进行增删改查操作时,我们都需要调用一次数据库,所以,将数据库连接打包成一个类,每当进行操作,调用该类即可。
DBUtil.java
- package com.ffyc.webserver.util;
-
- import java.sql.*;
-
- public class DButils {
- static {
- try {
- Class.forName("com.mysql.cj.jdbc.Driver");
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
-
- public static Connection getConnection() throws SQLException {
-
- String url = "jdbc:mysql://127.0.0.1:3306/userdb?serverTimezone=Asia/Shanghai";
- Connection connection = DriverManager.getConnection(url, "root", "123456");
- return connection;
- }
-
- public static void close(ResultSet resultSet, Statement statement, Connection connection) throws SQLException {
-
- if (resultSet != null) {
- resultSet.close();
- }
- if (statement != null) {
- statement.close();
- }
- if (connection != null) {
- connection.close();
- }
-
- }
- }
然后再写一个登录对账号密码验证的dao:使用sql语言调用数据库进行查询,最后将查询到数据以对象形式返回。如果对象为空代表未在数据库查到当前账号,说明账号或密码有误,不为空则说明登录成功。
- package com.ffyc.webserver.dao;
-
- import com.ffyc.webserver.Models.User;
- import com.ffyc.webserver.util.DButils;
-
- import java.sql.*;
-
- public class LoginDao {
- public User logindao(String account, String password) throws SQLException {
- String sql="select * from user where account=? and password=?";
- Connection connection=DButils.getConnection();
- PreparedStatement preparedStatement= connection.prepareStatement(sql);
- preparedStatement.setObject(1,account);
- preparedStatement.setObject(2,password);
- ResultSet resultSet=preparedStatement.executeQuery();
- User user=null;
-
- if(resultSet.next())
- {
- user=new User();
- String psw=resultSet.getString("password");
- user.setAccount(account);
- user.setPassword(psw);
- }
- DButils.close(resultSet,preparedStatement,connection);
- return user;
- }
- }
因为要以对象形式返回数据,所以需要创建登录者信息类:
- package com.ffyc.webserver.Models;
-
- public class User {
- String id;
- String account;
- String password;
- String token;
-
- public String getToken() {
- return this.token;
- }
-
- public void setToken(String token) {
- this.token = token;
- }
-
- public void setAccount(String account) {
- this.account = account;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getAccount() {
- return account;
- }
-
- public String getPassword() {
- return password;
- }
- }
最后,不要忘记配置当前servlet:
- <servlet>
- <servlet-name>Loginservlet-name>
- <servlet-class>com.ffyc.webserver.servlet.LoginServletservlet-class>
- servlet>
- <servlet-mapping>
- <servlet-name>Loginservlet-name>
- <url-pattern>/LoginServleturl-pattern>
- servlet-mapping>
这样,一个简单的登录就完成了。
6,我们发现,类似于状态码为500的公共拦截码如果每次都写一遍很费时间,所以在前端为axios配置响应拦截器,将500这种公共拦截码进行拦截。
- import axios from 'axios';
- //设置访问后台服务器地址
- axios.defaults.baseURL = "http://localhost:8088/webServer/";
- //将 axios 挂载到 vue 全局对象中,使用 this 可以直接访问
-
- //axios 请求拦截 每次向后端发送
- axios.interceptors.request.use(config => {
- //为请求头对象,添加 Token 验证的 token 字段
- config.headers.userToken = window.sessionStorage.getItem('userToken');
- return config;
- })
- axios.interceptors.response.use((resp) => { //正常响应拦截
- if (resp.data.code == 500) {
- ElementUI.Message({
- message: resp.data.message,
- type: "error"
- })
- }
- if (resp.data.code == 402) {
- ElementUI.Message({
- message: resp.data.message,
- type: "error"
- })
- router.replace("/login");
- }
- return resp;
- });
-
- Vue.prototype.$http = axios;
为axios框架配置响应拦截器,一旦后端做出响应,响应器拦截。
7,存在的问题:在登录页面的地址栏输入主页面地址可以直接访问,无需验证账号密码。
解决办法:在登录前判断验证用户是否登录,未登录就不能访问/main。
路由导航守卫:每当发生路由,自动调用此方法。
路由导航守卫是一种在路由导航过程中进行拦截和控制的机制。它允许开发者在用户导航到某个路由之前、之后或在路由发生变化时执行相应的操作。
路由导航守卫可以用于实现诸如身份验证、权限检查、日志记录等功能。通过注册守卫函数,开发者可以在路由导航事件发生时介入,并根据需要执行特定的逻辑。
to:访问组件地址
from:从哪个组件发起的路由
next():放行函数。
写入index.js中:导出路由器对象,并将代码包装在模块中并导出对象
- import Vue from 'vue';
- import router from 'vue-router'; /* 导入路由 */
- /* 导入需要路由的组件 */
- import Login from "../Login.vue";
- import Main from "../Main.vue";
- import student from '../student/student.vue';
- import marjor from '../marjor/marjor.vue';
- Vue.use(router)
- /* 定义组件路由 */
- var rout = new router({
- routes: [{
- path: '/',
- component: Login
- },
- {
- path: '/login',
- /* 地址 */
- name: 'login',
- /* 地址名 */
- component: Login /* 调用地址 */
- },
- {
- path: '/main',
- component: Main,
- children: [{
- path: "/student",
- component: student,
- }, {
- path: "/marjor",
- component: marjor
- }],
- }
- ]
- });
- //导出路由对象
- rout.beforeEach((to, from, next) => {
- if (to.path == '/login') {
- //如果用户访问的登录页, 直接放行
- return next();
- } else {
- var account = window.sessionStorage.getItem("account");
- if (account == null) {
- return next("/login");
- } else {
- next();
- }
- }
- })
- export default rout;
8, 前端显示用户信息,并实现安全退出。
- export default {
- name: "main",
- data() {
- return {
- account: ""
- }
- },
- methods: {
- open() {
- this.$confirm('此操作将退出当前账号, 是否继续?', '操作提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- this.$message({
- type: 'success',
- message: '账号已退出!',
- });
- sessionStorage.clear();
- this.$router.push("/login"); //在js中进行路由导航
- });
- },
- test() {
- this.$http.get("admin/LoginServlet");
- }
-
- },
- mounted() {
- this.account = sessionStorage.getItem("account");
- }
- }
9,登录成功后,进入管理后台,需要会话跟踪。
由于http请求是无状态的,默认不携带用户信息,所以在一次会话中,需要多次向后端发送请求,我们需要让后端程序知道是谁发送的请求。这个过程称为会话跟踪。
①传统的session会话:登录成功后,在后端生成HttpSession对象。里面可以存储用户信息和id,存储在服务器端,id号响应给前端,前端提交id到后端,查询session对象。(不适合分布式架构)
在分布式架构中,各个子系统通常相互独立且自治,它们可以并行地处理任务,从而提高系统的处理能力。每个子系统可以运行在自己的硬件设备上,也可以共享资源,例如数据库、存储等。通过将负载均衡和故障容错机制引入到系统中,分布式架构可以实现系统的高可用性和容错性。
②token会话:
jwt搭建:
①配置安装jar包;
②创建生成token方法;
③之后每次请求都要发送token(在前端axios中配置拦截器,每次向后端发送)
- //axios 请求拦截 每次向后端发送
- axios.interceptors.request.use(config => {
- //为请求头对象,添加 Token 验证的 token 字段
- config.headers.userToken = window.sessionStorage.getItem('userToken');
- return config;
- })
④每次做出响应之前进行token验证
- package com.ffyc.webserver.util;
-
- import com.auth0.jwt.JWT;
- import com.auth0.jwt.JWTVerifier;
- import com.auth0.jwt.algorithms.Algorithm;
- import com.auth0.jwt.interfaces.DecodedJWT;
- import com.ffyc.webserver.Models.User;
-
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * JWT工具类
- */
- public class JWTUtil {
-
- /**
- * 根据用户id,账号生成token
- * @param u
- * @return
- */
- public static String getToken(User u) {
- String token = "";
- try {
- //过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
- Date expireDate = new Date(new Date().getTime() + 3600*1000);
- //秘钥及加密算法
- Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
- //设置头部信息
- Map
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);
- }
-
- }
- package com.ffyc.webserver.filter;
-
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.ffyc.webserver.Models.CommonData;
- import com.ffyc.webserver.util.JWTUtil;
-
- import javax.servlet.*;
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.io.PrintWriter;
-
- public class TokenFilter implements Filter {
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest httpServletRequest=(HttpServletRequest) servletRequest;
- String userToken = httpServletRequest.getHeader("userToken");
- if(JWTUtil.verify(userToken))
- {
- filterChain.doFilter(servletRequest, servletResponse);
- }else{
- PrintWriter printWriter=servletResponse.getWriter();
- CommonData commonData=new CommonData(402,"Token认证失败!");
- ObjectMapper objectMapper=new ObjectMapper();
- String json=objectMapper.writeValueAsString(commonData);
- printWriter.print(json);
- }
- }
- }
会话跟踪实现:
第一次登录成功时,在后端根据管理员信息以及密钥生成的token(字符串),响应给前端,之后每次请求都携带token到后端,后端对token进行验证。