前後端分離 SpringSecurity+Vue 登入功能實作
- 一.跨域問題&解決方式
-
- 1.什麼是跨域
- 2.跨域問題解決方式
- 二.SpringSecurity後端
-
- 1.配置MySpringSecurityConfig
- 2.替換UsernamePasswordAuthenticationFilter
- 3.定義Handler
- 4.異常類&異常枚舉類
- 5.重寫AbstractRememberMeServices的rememberMeRequested(),實作rememberMe功能
- 三.Vue前端
-
- 1.Login.vue
- 2.Vuex 存儲登入資訊
- 3.路由守衛
一.跨域問題&解決方式
1.什麼是跨域
浏覽器的同源政策限制。同源政策(Sameoriginpolicy)是一種約定,它是浏覽器最核心也最基本的安全功能,如果缺少了同源政策,浏覽器很容易受到 XSS、CSFR 等攻擊。浏覽器的正常功能會受到影響。可以說Web是建構在同源政策基礎之上的,浏覽器隻是針對同源政策的一種實作。同源政策會阻止一個域的javascript腳本和另外一個域的内容進行互動。
所謂同源(即指在同一個域)就是兩個頁面具有相同的協定(protocol),主機(host)和端口号(port)。
- 當一個請求url的協定、域名、端口三者之間任意一個與目前頁面不同即為跨域
跨域并不是請求發不出去,請求能發出去,服務端能收到請求并正常傳回結果,隻是結果被浏覽器攔截了。
2.跨域問題解決方式
因為網上提供的跨域解決方式有很多,這裡我提供一下我使用的一種方式:
由于我使用axios來發送ajax請求,但是axios中隻能使用get和post方法來進行請求資料,沒有提供jsonp等方法進行跨域通路資料。是以使用axios直接進行跨域是不可行的,上面我們說過跨域問題的産生原因是浏覽器的同源政策的限制,是以用戶端通過浏覽器通路服務端(B/S)就會産生跨域問題,但是我們配置一個代理的伺服器請求另一個伺服器中的資料,然後把請求出來的資料傳回到我們的代理伺服器中,代理伺服器再傳回資料給我們的用戶端,這樣就可以實作跨域通路資料。
代碼實作
1.配置代理
在config檔案夾下的index.js檔案中的proxyTable字段
dev: {
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:8082', // 你請求的第三方接口
changeOrigin: true,// 在本地會建立一個虛拟服務端,然後發送請求的資料,并同時接收請求的資料,這樣服務端和服務端進行資料的互動就不會有跨域問題
pathRewrite: { // 路徑重寫,
'^/api': '/'// 替換target中的請求位址
}
}
},
2.設定axios執行個體
這裡為了不用每次發送axios請求的時候寫"/api",這裡對axios的get,post方法進行了簡單的封。
import axios from "axios";
//輸出通用axios執行個體
const instance =axios.create({
timeout:5000,//設定axios請求逾時時間
headers:{
"Content-Type":"application/json;charset=utf-8"
}
})
export default {//封裝get post 方法
toPost(url,data){
return instance.post("/api"+url,data);
},
toGet(url,config){
return instance.get("/api"+url,config);
}
}
main.js 中
import MyAxiosInstance from "./myConfig/api";
Vue.prototype.axiosInstance=MyAxiosInstance;//axios 執行個體
以上就是Vue+axios解決跨域問題!
二.SpringSecurity後端
1.配置MySpringSecurityConfig
package com.dzk.web.common.config;
import com.dzk.web.common.security.Handler.MyAuthenticationFailureHandler;
import com.dzk.web.common.security.Handler.MyAuthenticationSuccessHandler;
import com.dzk.web.common.security.filter.LoginFilter;
import com.dzk.web.common.security.service.MyPersistentTokenBasedRememberMeServices;
import com.dzk.web.common.security.service.SystemUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
import java.util.UUID;
/**
* @author dzk
* @version 1.0
* @date 2020/11/17 10:36
*/
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final String DEFAULT_REMEMBER_ME_KEY=UUID.randomUUID().toString();
@Value("${myConfig.security.tokenValiditySeconds}")
private int tokenValiditySeconds;
@Autowired
private SystemUserDetailsService systemUserDetailsService;
@Autowired
private DataSource dataSource;//datasource 用的是springboot預設的application.yml中的配置
/**
* 密碼加密(strength=5 設定加密強度4-31)
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(5);
}
/**
* 過濾,授權
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/test/isLogin").authenticated()//受限資源
.anyRequest().permitAll()
.and()
.rememberMe()//配置rememberMe
.tokenRepository(persistentTokenRepository())
.userDetailsService(systemUserDetailsService)
.tokenValiditySeconds(tokenValiditySeconds) //設定rememberMe失效時間
.rememberMeServices(myPersistentTokenBasedRememberMeServices())
.and()
.cors()
.and()
.csrf().disable()//關閉csrf 功能,登入失敗存在的原因
.formLogin().and()
.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);//繼承UsernamePasswordAuthenticationFilter,替換原來的UsernamePasswordAuthenticationFilter
}
private RememberMeServices myPersistentTokenBasedRememberMeServices() {
//自定義RememberMeServices
return new MyPersistentTokenBasedRememberMeServices(DEFAULT_REMEMBER_ME_KEY, userDetailsService(), persistentTokenRepository());
}
/**
* 身份驗證(BCryptPasswordEncoder加密)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(systemUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
LoginFilter loginFilter() throws Exception{
LoginFilter loginFilter=new LoginFilter();
//設定認證成功傳回
loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
//設定認證失敗傳回
loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
//這句很關鍵,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己組裝AuthenticationManager
loginFilter.setAuthenticationManager(authenticationManagerBean());
loginFilter.setRememberMeServices(myPersistentTokenBasedRememberMeServices());
loginFilter.setFilterProcessesUrl("/login");
return loginFilter;
}
/**
* 持久化token
* Security中,預設是使用PersistentTokenRepository的子類InMemoryTokenRepositoryImpl,将token放在記憶體中
* 如果使用JdbcTokenRepositoryImpl,會建立表persistent_logins,将token持久化到資料庫
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自動建立相關的token表(首次運作時需要打開,二次運作時需要注解掉)
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
2.替換UsernamePasswordAuthenticationFilter
package com.dzk.web.common.security.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dzk.web.common.exception.LoginMethodException;
import com.dzk.web.common.exception.VerifyCodeException;
import com.dzk.web.common.exception.VerifyCodeTimeOutException;
import com.dzk.web.common.utils.RedisUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/17 13:50
*/
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
Logger logger=LoggerFactory.getLogger(LoginFilter.class);
@Autowired
private RedisUtil redisUtil;//redis工具類
/**
*
* @param request
* @param response
* @return
* @throws AuthenticationException
* @describe 重寫attemptAuthentication方法,應為前端使用axios發送ajax請求,通過fastJson 讀取request流中的登入資訊,
* 後續步驟同其父類類似,因為實作了RememberMe功能,而在後面的AbstractRememberMeServices中是通過request.getParameter(parameter)擷取記住我參數,
* 而我們在後端不好對request的parameter進行手動添加參數,是以我們使用request.setAttribute("remember-me",rememberMe);進行傳參,後面通過重寫AbstractRememberMeServices
* 的rememberMeRequested(HttpServletRequest request, String parameter)方法來接收參數
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if(!request.getMethod().equals("POST")){//請求不為Post方式
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
if(request.getContentType()==null){//使用PostMan發送表單登入時會出現空指針異常
throw new LoginMethodException();
}
logger.error("request.getContentType()--->"+request.getContentType());
logger.error("類型比較:"+request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE));
if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
//如果是Json資料格式請求登入
String jsonData=getJsonParam(request);
JSONObject jsonObject= JSON.parseObject(jsonData);
String verifyCodeKey = jsonObject.getString("uuid");//擷取前端傳來的uuid
if (verifyCodeKey == null) {
verifyCodeKey = "";
}
String verifyCodeValue = (String) redisUtil.get(verifyCodeKey);//得到存儲在redis中的驗證碼
String verifyCodeOfUser = jsonObject.getString("captcha");//使用者傳來的驗證碼
if (verifyCodeOfUser == null) {
verifyCodeOfUser = "";
}
checkVerifyCode(verifyCodeOfUser, verifyCodeValue);//校驗驗證是否正确
String username = jsonObject.getString("username");//擷取使用者名
String password = jsonObject.getString("password");//密碼
String rememberMe = jsonObject.getString("rememberMe");//記住我
username = username.trim();
request.setAttribute("remember-me",rememberMe);//設定ememberMe
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}else{
throw new LoginMethodException();//禁止使用非application/json格式的登入方式
}
}
public void checkVerifyCode(String verifyCodeOfUser, String verifyCodeOfRedis){
if(StringUtils.isEmpty(verifyCodeOfRedis)){
throw new VerifyCodeTimeOutException();
}else if(!verifyCodeOfRedis.equalsIgnoreCase(verifyCodeOfUser)){
throw new VerifyCodeException();
}
}
/**
* 擷取HttpServletRequest中的Json資料
*
* @param request
* @return
*/
private String getJsonParam(HttpServletRequest request) {
String jsonParam = "";
ServletInputStream inputStream = null;
try {
int contentLength = request.getContentLength();
if (!(contentLength < 0)) {
byte[] buffer = new byte[contentLength];
inputStream = request.getInputStream();
for (int i = 0; i < contentLength; ) {
int len = inputStream.read(buffer, i, contentLength);
if (len == -1) {
break;
}
i += len;
}
jsonParam = new String(buffer, "utf-8");
}
} catch (IOException e) {
logger.error("參數轉換成json異常g{}", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
logger.error("參數轉換成json異常s{}", e);
}
}
}
return jsonParam;
}
}
3.定義Handler
驗證失敗Handler
package com.dzk.web.common.security.Handler;
import com.dzk.web.common.exception.LoginMethodException;
import com.dzk.web.common.exception.UserNotExistException;
import com.dzk.web.common.exception.VerifyCodeException;
import com.dzk.web.common.exception.VerifyCodeTimeOutException;
import com.dzk.web.common.myEnum.ExceptionCodeEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/18 11:30
*/
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException{
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
Map<String,Object> message=new HashMap<>();
if (exception instanceof LockedException) {
message.put("msg", ExceptionCodeEnum.LOCKEDEXCEPTION.getExceptionCode());//賬戶被鎖定,請聯系管理者!
} else if (exception instanceof CredentialsExpiredException) {
message.put("msg",ExceptionCodeEnum.CREDENTIALSEXPIREDEXCEPTION.getExceptionCode());//密碼過期,請聯系管理者!
} else if (exception instanceof AccountExpiredException) {
message.put("msg",ExceptionCodeEnum.ACCOUNTEXPIREDEXCEPTION.getExceptionCode());//賬戶過期,請聯系管理者!
} else if (exception instanceof DisabledException) {
message.put("msg",ExceptionCodeEnum.DISABLEDEXCEPTION);//賬戶被禁用,請聯系管理者!
}
else if(exception.getCause() instanceof UserNotExistException) {
message.put("msg",ExceptionCodeEnum.USERNOTEXCEPTION.getExceptionCode());//賬戶不存在!
}
else if (exception instanceof BadCredentialsException) {
message.put("msg",ExceptionCodeEnum.BADCREDENTIALSEXCEPTION.getExceptionCode());//密碼輸入錯誤,請重新輸入!
}else if(exception instanceof VerifyCodeException){
message.put("msg",ExceptionCodeEnum.VERIFYCODEEXCEPTION.getExceptionCode());//驗證碼錯誤!
}
else if(exception instanceof VerifyCodeTimeOutException){
message.put("msg",ExceptionCodeEnum.VERIFYCODETIMEOUTEXCEPTION.getExceptionCode());//驗證碼過期!
}
else if(exception instanceof LoginMethodException){
message.put("msg",ExceptionCodeEnum.LOGINMETHODEXCEPTION.getExceptionCode());//驗證碼過期!
}
else{
message.put("msg",ExceptionCodeEnum.EXCEPTION.getExceptionCode());//其他異常exception.getMessage()
}
out.write(new ObjectMapper().writeValueAsString(message));
out.flush();
out.close();
}
}
驗證成功Handler
package com.dzk.web.common.security.Handler;
import com.dzk.web.pojo.SystemUserEntity;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.val;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/18 11:32
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
SystemUserEntity systemUserEntity = (SystemUserEntity) authentication.getPrincipal();
systemUserEntity.setPassword(null);//密碼清空
Map<String,Object> message=new HashMap<>();
HttpSession session=request.getSession();
String sessionId=session.getId();//作為登入辨別
systemUserEntity.setSessionId(sessionId);
message.put("msg",1000);//登入成功
message.put("data",systemUserEntity);
String s = new ObjectMapper().writeValueAsString(message);
out.write(s);
out.flush();
out.close();
}
}
4.異常類&異常枚舉類
驗證碼過期
package com.dzk.web.common.exception;
import org.springframework.security.core.AuthenticationException;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/19 9:51
*/
public class VerifyCodeTimeOutException extends AuthenticationException {
public VerifyCodeTimeOutException() {
super("驗證碼過期");
}
public VerifyCodeTimeOutException(String msg) {
super(msg);
}
}
驗證碼錯誤
package com.dzk.web.common.exception;
import org.springframework.security.core.AuthenticationException;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/18 10:04
*/
public class VerifyCodeException extends AuthenticationException {
public VerifyCodeException() {
super("驗證碼錯誤");
}
public VerifyCodeException(String message) {
super(message);
}
}
使用者名不存
package com.dzk.web.common.exception;
import org.springframework.security.core.AuthenticationException;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/17 13:28
*/
/*使用者不存在異常*/
public class UserNotExistException extends AuthenticationException {
public UserNotExistException() {
super("該使用者名不存在!");
}
public UserNotExistException(String message) {
super(message);
}
}
登入方式異常
package com.dzk.web.common.exception;
import org.springframework.security.core.AuthenticationException;
public class LoginMethodException extends AuthenticationException {
public LoginMethodException(String msg) {
super(msg);
}
public LoginMethodException() {
super("登入方式不正确");
}
}
異常枚舉類
package com.dzk.web.common.myEnum;
import com.dzk.web.common.exception.LoginMethodException;
import com.dzk.web.common.exception.UserNotExistException;
import com.dzk.web.common.exception.VerifyCodeException;
import com.dzk.web.common.exception.VerifyCodeTimeOutException;
import org.springframework.security.authentication.*;
/**
* @author 獨照
* @version 1.0
* @date 2020/11/23 16:07
*/
public enum ExceptionCodeEnum {
LOCKEDEXCEPTION(new LockedException("賬戶被鎖定"),1102),
ACCOUNTEXPIREDEXCEPTION(new AccountExpiredException("賬戶過期"),1101),
DISABLEDEXCEPTION(new DisabledException("賬戶被禁用"),1103),
USERNOTEXCEPTION(new UserNotExistException("賬戶不存在"),1104),
CREDENTIALSEXPIREDEXCEPTION(new CredentialsExpiredException("密碼過期"),1201),
BADCREDENTIALSEXCEPTION(new BadCredentialsException("密碼錯誤"),1202),
VERIFYCODEEXCEPTION(new VerifyCodeException("驗證碼錯誤"),1300),
VERIFYCODETIMEOUTEXCEPTION(new VerifyCodeTimeOutException("驗證碼過期"),1301),
LOGINMETHODEXCEPTION(new LoginMethodException("登入方式錯誤"),1401),
EXCEPTION(new Exception("未知異常"),1500);
private Exception exceptionClass;
private int exceptionCode;
private ExceptionCodeEnum(Exception exceptionClass, int exceptionCode){
this.exceptionClass=exceptionClass;
this.exceptionCode=exceptionCode;
}
public Exception getExceptionClass() {
return exceptionClass;
}
public int getExceptionCode() {
return exceptionCode;
}
}
5.重寫AbstractRememberMeServices的rememberMeRequested(),實作rememberMe功能
記住我原理
需要注意的是登入驗證并不難,難點在于啟用SpringSecurity的RememberMe功能,因為後面AbstractRememberMeServices類中擷取request中的remember-me參數使用的是getParameter方法,因為我們前端是通過ajax傳輸的登入資訊,是以getParameter方法擷取不到remember-me參數,我之前想在LoginFilter中向request中添加remember-me參數的方式來使AbstractRememberMeServices中擷取到remember-me參數,但是效果并不好,于是我通過request.setAttribute(“remember-me”,rememberMe);方法傳輸remember-me參數,繼承AbstractRememberMeServices的子類PersistentTokenBasedRememberMeServices并重寫了rememberMeRequested方法。
package com.dzk.web.common.security.service;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.servlet.http.HttpServletRequest;
public class MyPersistentTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices {
public MyPersistentTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
super(key, userDetailsService, tokenRepository);
}
@Override
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
boolean rememberMeFlag= super.rememberMeRequested(request, parameter);
String RememberMeParameter=super.getParameter();
String paramValue =(String) request.getAttribute(RememberMeParameter);
if (paramValue != null) {
if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
return true;
}
}
return rememberMeFlag;
}
}
三.Vue前端
1.Login.vue
<template>
<div id="loginBody_div" >
<transition name="el-zoom-in-center">
<div id="form_body" v-show="show" class="transition-box">
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="賬号" prop="username" label-width="60px">
<el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密碼" prop="password" label-width="60px">
<el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
</el-form-item>
<el-row>
<el-col :span="15">
<el-form-item label="驗證碼" prop="captcha" class="captcha" label-width="60px">
<el-input v-model="ruleForm.captcha" ></el-input>
</el-form-item>
</el-col>
<el-col :span="8" :offset="1">
<div id="div_captcha"><img :src="captchaPath" class="captchaImg" @click="getCaptcha()"></div>
</el-col>
</el-row>
<el-row>
<el-col :span="8" :offset="4">
<el-checkbox v-model="ruleForm.rememberME"><small>Remember Me</small></el-checkbox>
</el-col>
</el-row>
<el-form-item label-width="60px">
<div id="submit_div">
<el-button size="medium" type="primary" @click="submitForm('ruleForm')" round>送出</el-button>
</div>
</el-form-item>
<el-form-item label-width="45px">
<div class="btn_group">
<el-button type="text" size="mini" @click="jumpPage(1)">忘記密碼</el-button>
<el-button type="text" size="mini" @click="jumpPage(2)">忘記賬号</el-button>
<el-button type="text" size="mini" @click="jumpPage(3)">注冊</el-button>
</div>
</el-form-item>
</el-form>
</div>
</transition>
</div>
</template>
<script>
import {getUUID} from '../../utils';
import GLOBAL from '../../myConfig/globalVariable'
export default {
name: "Login.vue",
data() {
var validateCaptcha =(rule, value, callback) => {
console.log(this.loginStatusCode+" "+this.loginStatusMsg)
if (!value) {
return callback(new Error('驗證碼不能為空'));
}
if (value.toString().length!=4) {
callback(new Error('驗證碼長度為4個字元'));
}else if(1300<=parseInt(this.loginStatusCode) && parseInt(this.loginStatusCode)<=1500){
callback(new Error(this.loginStatusMsg));
this.clearLoginStatus();
}
else{
callback();
}
};
//賬号前端校驗
var validateUsername = (rule, value, callback) => {
if (value === '') {
callback(new Error('請輸入使用者名'));
}
else if(1100<=parseInt(this.loginStatusCode) && parseInt(this.loginStatusCode)<1200){
callback(new Error(this.loginStatusMsg));
this.clearLoginStatus();
}
callback();
};
//密碼前端校驗
var validatePassword = (rule, value, callback) => {
if (value === '') {
callback(new Error('請輸入密碼'));
}
else if(1200<=parseInt(this.loginStatusCode) && parseInt(this.loginStatusCode)<1300){
callback(new Error(this.loginStatusMsg));
this.clearLoginStatus();
}
callback();
};
return {
ruleForm: {
username: '',
password: '',
captcha: '',
captchaUUID:'',
rememberME:false,
},
rules: {
username: [
{ validator: validateUsername, trigger: 'blur' }
],
password: [
{ validator: validatePassword, trigger: 'blur' }
],
captcha: [
{ validator: validateCaptcha, trigger: 'blur' }
]
},
captchaPath:'/static/testImg/captcha.png',//此處去後端擷取
show:false,
loginStatusCode:999,//狀态碼,判斷登入是否成功
loginStatusMsg:""//登入狀态資訊
};
},
methods: {
submitForm(formName) {
this.loginStatusMsg="";//每次送出時,清空登入狀态資訊欄
let that=this;
this.$refs[formName].validate(async (valid) => {
if (valid) {
//拿到資料去背景驗證
await this.axiosInstance.toPost(GLOBAL.BACKPATH+"/login",{
"captcha":this.ruleForm.captcha,
"uuid":this.ruleForm.captchaUUID,
"username":this.ruleForm.username,
"password":this.ruleForm.password,
"rememberMe":this.ruleForm.rememberME
}).then(async function (response) {
let loginStatusCode=response.data.msg.toString();
//console.log(JSON.stringify(response.data.data.username));
//登入成功後将sessionId 作為登入成功的辨別
let userDetails=JSON.stringify(response.data.data);
that.$store.commit("setUserDetails",userDetails);//将userDetails存入vuex 中
that.dealOfLoginStatusCode(loginStatusCode);
}).catch(function (error) {
console.log("伺服器異常"+error);
})
} else {
console.log('error submit!!');
return false;
}
});
},
showForm: function () {
this.show = true;
},
dealOfLoginStatusCode(statusCode){
this.loginStatusCode=parseInt(statusCode);//得到狀态碼
if(this.loginStatusCode==1000){//登入成功後跳轉
this.$router.push("/homePage");
}else{//失敗
this.loginStatusMsg=GLOBAL.STATUSCODE.get(this.loginStatusCode);
this.$refs["ruleForm"].validate(()=>{});//表單驗證
}
},
clearLoginStatus(){//清除登入狀态
this.loginStatusMsg="";
this.loginStatusCode=999;
},
getCaptcha() {
this.ruleForm.captcha="";//請求驗證碼時清除驗證碼框中的數值
this.$refs["ruleForm"].clearValidate("captcha",valid => {});
this.ruleForm.captchaUUID=getUUID();//生成uuid 傳輸到背景作為redis的key
this.axiosInstance.toGet(GLOBAL.BACKPATH+"/publicResources/getVerifyCodeImg",{
params: {
uuid:this.ruleForm.captchaUUID,
},
responseType: 'arraybuffer'//切記 切記 要寫在params 的外面!!!!
}).then((response)=>{
const bufferUrl ='data:image/png;base64,'+ btoa(new Uint8Array(response.data).reduce((data, byte) => data + String.fromCharCode(byte), ''));
return bufferUrl;
}).then(data=>{
this.captchaPath=data;
}).catch(error=>{
console.log("驗證碼圖檔請求失敗:"+error);
})
},
jumpPage(choice){
if(choice==1){//forget password
this.$router.push({
path:"/forgetPassword"
})
}else if(choice==2){ //forget Username
this.$router.push({
path:"/forgetUsername"
})
}else if(choice==3){ //register
this.$router.push({
path:"/register"
})
}
}
},
mounted() {
this.getCaptcha();//頁面加載時擷取驗證碼
this.showForm();
}
}
</script>
<style>
.captchaImg{
width: 100%;
height: 100%;
background-size: cover;
}
#form_body{
height: 400px;
width: 400px;
margin: 160px auto;
}
#div_captcha{
width: 100%;
height: 40px;
float: right;
}
.demo-ruleForm{
font-weight: 500;
}
#loginBody_div #form_body .el-form-item__label{
color: rgba(30, 36, 35, 0.99) !important;
}
.btn_group{
margin: 0 auto;
margin-top: -25px;
text-align: center;
}
#submit_div button{
margin: 10px auto;
width: 330px;
height: 40px;
}
#loginBody_div{
height:100%;
width: 100%;
position: fixed;
background-size: cover;
background-image: url("/static/Img/BackgroundImg/loginBackgroundImg.jpg");
}
</style>
2.Vuex 存儲登入資訊
import Vuex from "vuex";
Vue.use(Vuex);
import Vue from 'vue';
export default new Vuex.Store({
state:{
userDetails:null//初始化token
},
mutations: {
//存儲token方法
//設定token等于外部傳遞進來的值
setUserDetails(state, userDetails) {
state.userDetails = userDetails;
sessionStorage.setItem("userDetails",userDetails)//同步存儲token至sessionstorage
/*vuex存儲在記憶體,localstorage(本地存儲)則以檔案的方式存儲在本地,永久儲存;sessionstorage( 會話存儲 ) ,臨時儲存。localStorage和sessionStorage隻能存儲字元串類型*/
// 頁面重新整理失效
}
},
getters : {
//擷取token方法
//判斷是否有token,如果沒有重新指派,傳回給state的token
getUserDetails:(state)=>{
if (!state.userDetails) {
state.userDetails = sessionStorage.getItem("userDetails");//若vuex中無token就去sessionStorage中查找
}
return state.userDetails;
}
},
})
3.路由守衛
在每次進入首頁時先通路後端,查詢是否登入,如果登入将導航至首頁并将登入資訊存儲到sessionStorage中,反之進入Login頁面。
import Vue from "vue";
import VueRouter from "vue-router";
import Login from "../views/commons/Login"
import Register from "../views/commons/Register";
import ForgetPassword from "../views/commons/ForgetPassword";
import ForgetUsername from "../views/commons/ForgetUsername";
import HomePage from "../views/Users/HomePage";
import routerFunction from "./routerFunction";
import myVuex from "../myConfig/MyVuex"
import MyAxiosInstance from "../myConfig/api";
Vue.use(VueRouter);
this.axiosInstance=MyAxiosInstance;//axios 執行個體
const AllRouters=[
{
path:"/login",
name:"login",
component:Login
},{
path:"/register",
name:"register",
component:Register
},{
path:"/forgetPassword",
name:"forgetPassword",
component:ForgetPassword
},{
path:"/forgetUsername",
name:"forgetUsername",
component:ForgetUsername
},
{
path:"/homePage",
name:"homePage",
component:HomePage
}
];
const router=new VueRouter({
mode: 'history',//去掉url的#
routes:AllRouters,
});
router.beforeEach((to,from,next)=>{//路由守衛
routerFunction.isLogin(to,from,next,myVuex,this);
})
export default router;//導出
import GLOBAL from "../myConfig/globalVariable";
async function isLogin(to,form,next,myVuex,that) {
await isLoginBackstage(that,myVuex);
console.log(" !!!"+JSON.stringify(myVuex.getters.getUserDetails))
const jsonData=JSON.parse(myVuex.getters.getUserDetails);
let sessionId=null;
if(jsonData!=null){
sessionId=jsonData.sessionId.toString();
}
//擷取sessionId
if(sessionId){//如果已登入
//判斷是否是進入登入頁面
if(to.name=="login"){
next("homePage");
}else{
next(true);
}
}else{
next(true)
}
}
async function isLoginBackstage(that,myVuex) {
await that.axiosInstance.toPost(GLOBAL.BACKPATH+"/user/isLogin",{}).then(
function (response) {
if(response.data!=""){
console.log(JSON.stringify(response.data.username));
//登入成功後将sessionId 作為登入成功的辨別
let userDetails=JSON.stringify(response.data);
myVuex.commit("setUserDetails",userDetails);//将userDetails存入vuex 中
console.log(" ++"+JSON.parse(myVuex.getters.getUserDetails).sessionId);
}
}).catch(function (error) {
console.log("伺服器異常"+error);
}
)
}
export default {
isLogin
}
以上内容是我在課程設計中涉及到的問題,特别是在RememberMe功能實作,花費了許多時間,好在最終解決了,也許這并非最好的解決方式,但也為遇到此類問題的朋友提供了一種思路。
因為我還是一名小菜鳥,是以上面有些部分可能不正确,歡迎指正,謝謝!