上篇文章說到前端路由,同時也簡單說了一下 先後端合併啓動項目,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,未清理緩存 狀況下 再次訪問則無需登陸(除非清除緩存)。
。