上篇文章说到前端路由,同时也简单说了一下 先后端合并启动项目,Spring Boot + Vue先后端分离(四)前端路由;本篇文章接着上篇内容继续为你们介绍 登陆拦截器。php
本文是Spring Boot + Vue先后端分离 系列的第五篇,了解前面的文章有助于更好的理解本文:html
1.Spring Boot + Vue先后端分离(一)前端Vue环境搭建
2.Spring Boot + Vue先后端分离(二)前端Vue启动流程
3.Spring Boot + Vue先后端分离(三)实现登陆功能
4.Spring Boot + Vue先后端分离(四)前端路由前端
目录vue
(一).后端拦截器java
(二).前端拦截器webpack
前言ios
上一篇你们都学习到了 前端路由web
(1)hash模式spring
(2)history模式。vue-router
和先后端合并启动,本文主要说一下 拦截器,主要限制在未登陆状态下 对核心功能的访问权限。
(一).后端拦截器
就像上面说的,添加拦截器主要是限制在未登陆状态的权限控制,好比:咱们通常系统会要求只有在登陆状态下才能进入系统,其余路径的访问都须要重定向到登陆页面,首先咱们先讲解后端拦截器的开发。(注意:后端拦截器须要把先后端项目整合起来,如没有合并(先后端分离部署)是没有办法使用这种方式)
拦截器的逻辑以下:
1.用户访问 URL,检测是否为登陆页面,若是是登陆页面则不拦截
2.若是用户访问的不是登陆页面,检测用户是否已登陆,若是未登陆则跳转到登陆页面,若是已登陆不作拦截。
后端拦截器从以下几个点作处理:
(1) LoginController 登陆控制器
package com.cxzc.mycxzc.demo.controller;
import com.cxzc.mycxzc.demo.bean.User;
import com.cxzc.mycxzc.demo.response.Result;
import com.cxzc.mycxzc.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.HtmlUtils;
import javax.servlet.http.HttpSession;
import java.util.Objects;
@Controller
public class LoginController {
@Autowired
UserService userService;
@CrossOrigin
@PostMapping(value = "req/login")
@ResponseBody
public Result login(@RequestBody User requestUser, HttpSession session) {
String username = requestUser.getUsername();
username = HtmlUtils.htmlEscape(username);
User user = userService.get(username, requestUser.getPassword());
if (null == user) {
return new Result(400);
} else {
//用户对象User添加到session中
session.setAttribute("userinfo", user);
return new Result(200);
}
}
}
package com.cxzc.mycxzc.demo.controller;import com.cxzc.mycxzc.demo.bean.User;import com.cxzc.mycxzc.demo.response.Result;import com.cxzc.mycxzc.demo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;import org.springframework.web.util.HtmlUtils;import javax.servlet.http.HttpSession;import java.util.Objects;@Controllerpublic class LoginController {@AutowiredUserService userService;@CrossOrigin@PostMapping(value = "req/login")@ResponseBodypublic Result login(@RequestBody User requestUser, HttpSession session) {String username = requestUser.getUsername();username = HtmlUtils.htmlEscape(username);User user = userService.get(username, requestUser.getPassword());if (null == user) {return new Result(400);} else {//用户对象User添加到session中session.setAttribute("userinfo", user);return new Result(200);}}}
解释:
不难发现,这个登陆控制器咱们比以前的多了一行代码:session.setAttribute("userinfo", user); 这行代码的做用:把用户信息存在 Session
对象中(当用户在应用程序的 Web 页之间跳转时,存储在 Session
对象中的变量不会丢失),这样在访问别的页面时,能够经过判断是否存在用户变量来判断用户是否登陆。这是一种比较简单的方式,但让还有其余方式,这里不作多说,能够自行查找。
(2) LoginInterceptor 登陆拦截器
新建 package 名为 interceptor
,新建类 LoginInterceptor
。
package com.cxzc.mycxzc.demo.interceptor;
import com.cxzc.mycxzc.demo.bean.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 判断 session 中是否存在 user 属性,若是存在就经过,若是不存在就跳转到 login 页面
*/
public class LoginInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
HttpSession session = httpServletRequest.getSession();
String contextPath=session.getServletContext().getContextPath();
String[] requireAuthPages = new String[]{
"index",
};
String uri = httpServletRequest.getRequestURI();
uri = StringUtils.remove(uri, contextPath+"/");
String page = uri;
if(begingWith(page, requireAuthPages)){
User user = (User) session.getAttribute("userinfo");
if(user==null) {
httpServletResponse.sendRedirect("login");
return false;
}
}
return true;
}
private boolean begingWith(String page, String[] requiredAuthPages) {
boolean result = false;
for (String requiredAuthPage : requiredAuthPages) {
if(StringUtils.startsWith(page, requiredAuthPage)) {
result = true;
break;
}
}
return result;
}
}
package com.cxzc.mycxzc.demo.interceptor;import com.cxzc.mycxzc.demo.bean.User;import org.springframework.web.servlet.HandlerInterceptor;import org.apache.commons.lang.StringUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;/*** 判断 session 中是否存在 user 属性,若是存在就经过,若是不存在就跳转到 login 页面*/public class LoginInterceptor implements HandlerInterceptor{@Overridepublic boolean preHandle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {HttpSession session = httpServletRequest.getSession();String contextPath=session.getServletContext().getContextPath();String[] requireAuthPages = new String[]{"index",};String uri = httpServletRequest.getRequestURI();uri = StringUtils.remove(uri, contextPath+"/");String page = uri;if(begingWith(page, requireAuthPages)){User user = (User) session.getAttribute("userinfo");if(user==null) {httpServletResponse.sendRedirect("login");return false;}}return true;}private boolean begingWith(String page, String[] requiredAuthPages) {boolean result = false;for (String requiredAuthPage : requiredAuthPages) {if(StringUtils.startsWith(page, requiredAuthPage)) {result = true;break;}}return result;}}
解释:
在 Springboot 中能够直接继承拦截器的接口,而后实现 preHandle
方法。preHandle
方法里的代码会在访问须要拦截的页面时执行。
1,判断 session
中是否存在 user
属性,若是存在就放行,若是不存在就跳转到 login
页面。
2,
String[] requireAuthPages = new String[]{
"index",
};
String[] requireAuthPages = new String[]{"index",};
,能够在里面写下须要拦截的路径,目前咱们只有一个页面 因此只拦截index,后面添加了新页面能够在这里新增拦截。
(3)WebConfigurer 配置拦截器
拦截器咱们添加好了,可是这样是不生效果的,怎么才能有效呢,须要咱们配置拦截器。
新建 package 名为 config
,新建类 WebConfigurer
package com.cxzc.mycxzc.demo.config;
import com.cxzc.mycxzc.demo.interceptor.LoginInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.*;
@SpringBootConfiguration
public class WebConfigurer implements WebMvcConfigurer {
@Bean
public LoginInterceptor getLoginIntercepter() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**").excludePathPatterns("/index.html");
}
}
package com.cxzc.mycxzc.demo.config;import com.cxzc.mycxzc.demo.interceptor.LoginInterceptor;import org.springframework.boot.SpringBootConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.web.servlet.config.annotation.*;@SpringBootConfigurationpublic class WebConfigurer implements WebMvcConfigurer {@Beanpublic LoginInterceptor getLoginIntercepter() {return new LoginInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**").excludePathPatterns("/index.html");}}
在这个配置类中,添加了写好的拦截器。
registry.addInterceptor(getLoginIntercepter()).addPathPatterns("/**")
.excludePathPatterns("/index.html");
做用是对全部路径应用拦截器,除了 /index.html
以前咱们在拦截器 LoginInterceptor 中配置的路径,即 index,触发的时机是在拦截器生效以后。也就是说,咱们访问一个 URL,会首先经过 Configurer 判断是否须要拦截,若是须要,才会触发拦截器 LoginInterceptor,根据咱们自定义的规则进行再次判断。
配置完拦截器 咱们就能够验证一下了:验证步骤
1,运行后端项目,
2,访问 http://localhost:8082/index ,发现页面自动跳转到了 http://localhost:8082/login ,输入用户名和密码登陆,跳转http://localhost:8082/index
3,把浏览器标签关掉,再在一个新标签页输入 http://localhost:8082/index ,这时候不会在进入登陆页面,直接进入步骤2的登陆成功后的页面,说明不会被拦截。
(二).前端拦截器
上面说了使用了后端拦截器作登陆的拦截操做,这种拦截器只有在将先后端项目整合在一块儿时才能生效,可是咱们作先后端分离确定是要分开的,因此咱们还须要熟知 前端拦截器的使用。
前端登陆拦截器,须要在前端判断用户的登陆状态。这里咱们须要引入一个很好的工具——Vuex,它是专门为 Vue 开发的状态管理,咱们能够把须要在各个组件中传递使用的变量、方法定义在这里。能够像以前那样在组件的 data 中设置一个状态标志,但登陆状态应该被视为一个全局属性,而不该该只写在某一组件中。
(1)引入Vuex
引入Vuex 须要两步操做:
1,执行命令行 npm install vuex --save 添加组件
2,在src目录下添加文件目录store,在里面新建一个index.js文件,改文件添加以下内容:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {
username: window.localStorage.getItem('userinfo' || '[]') == null ? '' : JSON.parse(window.localStorage.getItem('userinfo' || '[]')).username
}
},
mutations: {
login (state, user) {
state.user = user
window.localStorage.setItem('userinfo', JSON.stringify(user))
}
}
})
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {user: {username: window.localStorage.getItem('userinfo' || '[]') == null ? '' : JSON.parse(window.localStorage.getItem('userinfo' || '[]')).username}},mutations: {login (state, user) {state.user = userwindow.localStorage.setItem('userinfo', JSON.stringify(user))}}})
解释:
1,index.js 里设置须要的状态变量和方法。为了实现登陆拦截器,这里须要一个记录用户信息的变量。咱们使用一个用户对象而不是仅仅使用一个布尔变量。同时,设置一个方法,触发这个方法时能够为咱们的用户对象赋值。
2,咱们这里用到了这个函数 localStorage
,即本地存储,在项目打开的时候会判断本地存储中是否有 userinfo 这个对象存在,若是存在就取出来并得到 username
的值,不然则把 username
设置为空。
3,缓存这些数据,只要不清除缓存,登陆的状态就会一直保存。
(2)调整路由配置
这里咱们须要区分页面是否须要拦截,修改一下路由 src\router\index.js
,把须要拦截的路由中设置一个 requireAuth
字段
import Vue from 'vue'
import Router from 'vue-router'
// 导入刚才编写的组件
import HomePage from '@/components/home/HomePage'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
// 下面都是固定的写法
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/index',
name: 'HomePage',
component: HomePage,
meta: {
requireAuth: true
}
}
]
})
import Vue from 'vue'import Router from 'vue-router'// 导入刚才编写的组件import HomePage from '@/components/home/HomePage'import Login from '@/components/Login'Vue.use(Router)export default new Router({mode: 'history',routes: [// 下面都是固定的写法{path: '/login',name: 'Login',component: Login},{path: '/index',name: 'HomePage',component: HomePage,meta: {requireAuth: true}}]})
解释:
1,
meta: {
requireAuth: true
}
meta: {requireAuth: true}
添加是否拦截状态。
咱们目前只有一个页面 因此只添加了一个,登陆没有添加。
(3)拦截的判断(main.js)
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
// 设置反向代理,前端请求默认发送到 http://localhost:8082/
var axios = require('axios')
axios.defaults.baseURL = 'http://localhost:8082/req'
// 全局注册,以后可在其余组件中经过 this.$axios 发送数据
Vue.prototype.$axios = axios
Vue.config.productionTip = false
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()
}
}
)
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App),
router,
store,
components: { App },
template: '<App/>'
})
// The Vue build version to load with the `import` command// (runtime-only or standalone) has been set in webpack.base.conf with an alias.import Vue from 'vue'import App from './App'import router from './router'import store from './store'// 设置反向代理,前端请求默认发送到 http://localhost:8082/var axios = require('axios')axios.defaults.baseURL = 'http://localhost:8082/req'// 全局注册,以后可在其余组件中经过 this.$axios 发送数据Vue.prototype.$axios = axiosVue.config.productionTip = falserouter.beforeEach((to, from, next) => {if (to.meta.requireAuth) {if (store.state.user.username) {next()} else {next({path: 'login',query: {redirect: to.fullPath}})}} else {next()}})/* eslint-disable no-new */new Vue({el: '#app',render: h => h(App),router,store,components: { App },template: '<App/>'})
解释:
1,使用 router.beforeEach()函数
,意思是在访问每个路由前调用
2,import store from'./store' 引入
3,逻辑操做:判断访问的路径是否须要登陆,若是须要,判断 store
里有没有存储 userinfo
的信息,若是存在,则进入,不然跳转到登陆页面,并存储访问的页面路径(以便在登陆后跳转到访问页)
(4)登陆页面修改
<template>
<div>
帐号: <input type="text" v-model="loginForm.username" placeholder="请输入帐号"/>
<br><br>
密码: <input type="password" v-model="loginForm.password" placeholder="请输入密码"/>
<br><br>
<button v-on:click="login">登陆帐号</button>
</div>
</template>
<script>
export default {
name: 'Login',
data () {
return {
loginForm: {
username: '',
password: ''
},
responseResult: []
}
},
methods: {
login () {
var _this = this
this.$axios
.post('/login', {
username: this.loginForm.username,
password: this.loginForm.password
})
.then(succe***esponse => {
if (succe***esponse.data.code === 200) {
// var data = this.loginForm
_this.$store.commit('login', _this.loginForm)
var path = this.$route.query.redirect
this.$router.replace({path: path === '/' || path === undefined ? '/index' : path})
} else {
alert('登陆失败!')
}
})
.catch(failResponse => {
})
}
}
}
</script>
<template><div>帐号: <input type="text" v-model="loginForm.username" placeholder="请输入帐号"/><br><br>密码: <input type="password" v-model="loginForm.password" placeholder="请输入密码"/><br><br><button v-on:click="login">登陆帐号</button></div></template><script>export default {name: 'Login',data () {return {loginForm: {username: '',password: ''},responseResult: []}},methods: {login () {var _this = thisthis.$axios.post('/login', {username: this.loginForm.username,password: this.loginForm.password}).then(succe***esponse => {if (succe***esponse.data.code === 200) {// var data = this.loginForm_this.$store.commit('login', _this.loginForm)var path = this.$route.query.redirectthis.$router.replace({path: path === '/' || path === undefined ? '/index' : path})} else {alert('登陆失败!')}}).catch(failResponse => {})}}}</script>
解释:
修改了登陆成功后的重定向拦截。
1.点击登陆按钮,向后端发送数据
2.受到后端返回的成功代码时,触发 store 中的 login() 方法,把 loginForm 对象传递给 store 中的 userinfo 对象
3.获取登陆前页面的路径并跳转,若是该路径不存在,则跳转到首页
_this.$store.commit('login', _this.loginForm)
var path = this.$route.query.redirect
this.$router.replace({path: path === '/' || path === undefined ? '/index' : path})
_this.$store.commit('login', _this.loginForm)var path = this.$route.query.redirectthis.$router.replace({path: path === '/' || path === undefined ? '/index' : path})
功能添加完成,咱们运行试试吧。
步骤和后端拦截器同样:
1,同时运行先后端项目,访问 http://localhost:8080/index ,发现页面直接跳转到了 http://localhost:8080/login?redirect=%2Findex
2,输入帐号密码后登陆,成功跳转到 http://localhost:8080/index ,
3,未清理缓存 状况下 再次访问则无需登陆(除非清除缓存)。
。