在上一篇文章中我们简单实现了用户的登录,在文章最后我们提出了一个问题,那就是当我们直接在地址栏中对首页直接进行访问的时候会发现我们直接跳过了登录页面,这样是很不安全的行为,那么我们这篇文章就这个问题来进行解决。同时我们也会将用户信息存在数据库中,进行登录的验证(当然我们后续还会使用更高级的方式去实现,但是在这篇文章中先这样进行实现)。
一、登录认证
简单来说,登录认证就是在你访问一个网站的时候,你必须经过登录以后才可以访问到一些内容,当你没有进行登录的时候要么就是不可以进入人家的系统,要么就是以游客模式访问,这样肯定有很多的内容是访问不到的。
一般来说,我所了解到的登录认证分为前端和后端的。后端的认证主要是通过拦截器进行拦截,拦截到请求以后,判断用户是否登录,如果登录则放行,如果未登录,那么就拦截请求,但是这里有一个问题,这种方法一般用在前后端项目整合在一起的时候,那么前后端分离的情况下,这种方法就失效了。如果感兴趣的同学可以自行去了解一下。本篇文章主要讲的是前后端分离的项目中,前端如何简单的实现登录认证。
使用vuex和前端登录拦截
具体实现思路如下:
在前端判断用户登录状态,我们可以在data中设置一个表示登录状态的变量,用以保存用户登录状态,但是这里有一个问题,那就是data中的变量都只能在本组件中进行访问,其他组件想要访问还需要组件中的通讯,可以理解为局部变量,那么我们需要的是什么,我们需要的是一个在所有组件中都可以使用的一个变量,即全局属性,所以在这里我们需要引用一个新的工具叫做vuex。那么具体如何使用,接下来我们具体介绍一下。
引入vuex
使用以下语句:
npm install vuex --save #如果该语句报错,那就执行下面语句
npm install vuex --force
之后观察src目录下有没有stroe目录,如果没有新建一个,并添加index.js文件并添加以下代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
之后我们需要在此文件中添加我们刚开始说的“全局变量”(在state中添加)同时添加一个方法(在成功登录后执行的方法,以改变登录状态)(在mutations中添加)
state: {
user: {
username: window.sessionStorage.getItem('user' || '[]') == null ? '' : JSON.parse(window.sessionStorage.getItem('user' || '[]')).username
}
},
mutations: {
login (state, user) {
state.user = user
window.sessionStorage.setItem('user', JSON.stringify(user))
}
}
这里我们用到了 window.sessionStorage,在网页打开的时候,会判断session中是否有uer这个对象。这样我们只要不关闭浏览器,登录状态就会一直存在,当我们关闭浏览器再次打开的时候,就会要求我们重新登录。
修改路由配置
在需要拦截的路由后面添加以下代码
meta: {
requireAuth: true
}
完整代码如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
import login from '@/components/demo/Login.vue'
import AppIndex from '@/components/demo/home/AppIndex.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'login',
component: login
},
{
path: '/login',
name: 'login',
component: login
},
{
path: '/index',
name: 'AppIndex',
component: AppIndex,
meta: {
requireAuth: true
}
}
]
// 解决跳转到当前路由报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch((err) => err)
}
const router = new VueRouter({
mode: 'history',
routes
})
export default router
添加好以后在main.js文件中添加拦截和放行的代码
在main.js文件中编写route.beforeEach(){}方法
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
if (store.state.user.username) {
next()
} else {
next({
path: 'login'
// query: { redirect: to.fullPath }
})
}
} else {
next()
}
})
这段代码的大概意思就是:判断待跳转组件中的登录标识符,判断是否需要拦截,如果不需要,则直接放行,如果需要拦截,则判断store中是否存有用户信息,如果存在用户信息,则放行,如果不存在用户信息,则进行拦截,并跳转回登录页面。
再次运行项目我们就会发现,确实被拦截到了,但是无论输入什么用户信息都会被拦截,这是当然的了,因为我们在登录的时候并没有将信息存入到store中。
在之前我们写了登录时的方法,接下来我们只需要在登录时调用这个方法就可以了。
调用方法为:
_this.$store.commit('login', _this.loginForm)
完整代码为:
methods: {
login () {
const _this = this
this.$axios.post('/login', {
username: this.loginForm.username,
password: this.loginForm.password
}).then(successResponse => {
if (successResponse.data.code === 200) {
_this.$store.commit('login', _this.loginForm)
const path = this.$route.query.redirect
this.$router.push({ path: path === '/' || path === undefined ? '/home' : path })
}
}).catch(failResponse => {
})
}
}
login组件的全部代码为:
<template>
<div id="poster">
<el-form class="login-container" label-position="left" label-width="0px">
<h3 class="login_title">登录</h3>
<el-form-item>
<el-input type="text" v-model="loginForm.username" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<el-form-item>
<el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码"></el-input>
</el-form-item>
<el-form-item style="width: 100%">
<el-button type="primary" style="width: 100%;background: #505458;border: none" v-on:click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<style>
</style>
<script>
export default {
name: 'Login',
data () {
return {
loginForm: {
username: '',
password: ''
},
responseResult: []
}
},
methods: {
login () {
const _this = this
this.$axios.post('/login', {
username: this.loginForm.username,
password: this.loginForm.password
}).then(successResponse => {
if (successResponse.data.code === 200) {
_this.$store.commit('login', _this.loginForm)
const path = this.$route.query.redirect
this.$router.push({ path: path === '/' || path === undefined ? '/home' : path })
}
}).catch(failResponse => {
})
}
}
}
</script>
<style>
.login-container {
border-radius: 15px;
background-clip: padding-box;
margin: 90px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.login_title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
#poster{
background: url('@/assets/eva.jpg');
background-position: center;
height: 100%;
width: 100%;
background-size: cover;
position: fixed;
}
body{
margin: 0px;
}
</style>
到这登录功能就可以正常使用了。
二、连接数据库
首先在后端springBoot项目中添加数据库相关依赖,这里我们使用的是mysql8.0版本的数据库,并且使用mybatis框架作为操作数据库的框架。
首先添加相应依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.40</version>
</dependency>
<!--引入mybatis,这是MyBatis官方提供的适配spring Boot的,而不是spring Boot自己的-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
添加完成后在application.yaml文件中配置数据库相关配置
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/vue_book?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
还有mybatis相关配置:
mybatis:
type-aliases-package: com.element.pojo
mapper-locations: classpath:mapper/*.xml
配置好后就开始编写代码:
在项目目录下新建mapper文件夹
编写UerMapper:
package com.element.mapper;
import com.element.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper {
User queryUserByUsername(String username);
}
在resources文件夹下编写mapper文件夹,里面编写UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace = 绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.element.mapper.UserMapper">
<select id="queryUserByUsername" resultType="user">
select *
from user
where username = #{username};
</select>
</mapper>
同理编写service和controller层
service接口:
package com.element.service;
import com.element.pojo.User;
public interface phoneLoginService {
User queryUserByUsername(String username);
}
service实现类:
package com.element.service.impl;
import com.element.mapper.UserMapper;
import com.element.pojo.User;
import com.element.service.phoneLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class phoneLoginServiceImpl implements phoneLoginService{
@Autowired
private UserMapper userMapper;
@Override
public User queryUserByUsername(String username) {
return userMapper.queryUserByUsername(username);
}
}
controller层:
package com.element.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.element.pojo.User;
import com.element.utils.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.element.service.phoneLoginService;
import org.springframework.web.util.HtmlUtils;
import java.util.Objects;
@RestController
public class phoneLoginController {
@Autowired
private phoneLoginService phoneLoginService;
@CrossOrigin
@PostMapping("/login")
public Result login(@RequestBody User user){
//此处后续需要连接数据库使用
String phone = user.getUsername();
String password = user.getPassword();
System.out.println(phone+password);
User u = phoneLoginService.queryUserByUsername(phone);
System.out.println("查询到的"+u);
if (u!=null){
if (!Objects.equals(u.getUsername(),phone) || !Objects.equals(u.getPassword(),password)){
String message = "账号密码错误";
System.out.println(message);
return new Result(400);
}else {
System.out.println("手机号:"+phone+"密码:"+password);
return new Result(200);
}
}else {
String message = "账号不存在";
System.out.println(message);
return new Result(400);
}
}
}
这样就连接到数据库了。
三、总结
在这篇文章中主要完成的功能如下:
- 实现前端的登录认证
- 引入了数据库
- 用户账号密码简单实现了数据库的认证(后期应该会使用其他方法进行验证)
大家有什么问题的话欢迎积极评论,我看到后也会积极回复大家的!