天天看點

前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&解決方式二.SpringSecurity後端三.Vue前端

前後端分離 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的協定、域名、端口三者之間任意一個與目前頁面不同即為跨域

跨域并不是請求發不出去,請求能發出去,服務端能收到請求并正常傳回結果,隻是結果被浏覽器攔截了。

前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&解決方式二.SpringSecurity後端三.Vue前端

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方法進行了簡單的封。
前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&解決方式二.SpringSecurity後端三.Vue前端
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+Vue 登入功能實作一.跨域問題&amp;解決方式二.SpringSecurity後端三.Vue前端

需要注意的是登入驗證并不難,難點在于啟用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>

           
前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&amp;解決方式二.SpringSecurity後端三.Vue前端

2.Vuex 存儲登入資訊

前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&amp;解決方式二.SpringSecurity後端三.Vue前端
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頁面。

前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&amp;解決方式二.SpringSecurity後端三.Vue前端
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;//導出

           
前後端分離 SpringSecurity+Vue 登入功能實作一.跨域問題&amp;解決方式二.SpringSecurity後端三.Vue前端
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功能實作,花費了許多時間,好在最終解決了,也許這并非最好的解決方式,但也為遇到此類問題的朋友提供了一種思路。

       因為我還是一名小菜鳥,是以上面有些部分可能不正确,歡迎指正,謝謝!