前后端分离 SpringSecurity+Vue 登录功能实现

一.跨域问题&解决方式

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功能实现,花费了许多时间,好在最终解决了,也许这并非最好的解决方式,但也为遇到此类问题的朋友提供了一种思路。
       因为我还是一名小菜鸟,所以上面有些部分可能不正确,欢迎指正,谢谢!

  • 17
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring BootVue.js是两个非常流行的技术栈,可以非常好地实现前后端分离的开发模式。Security和JWT是两个很好的工具,可以帮助我们实现安全的登录和授权机制。 以下是实现Spring BootVue.js前后端分离的步骤: 1.创建Spring Boot工程 首先,我们需要创建一个Spring Boot工程,可以使用Spring Initializr来生成一个基本的Maven项目,添加所需的依赖项,包括Spring Security和JWT。 2.配置Spring SecuritySpring Security中,我们需要定义一个安全配置类,该类将定义我们的安全策略和JWT的配置。在这里,我们可以使用注解来定义我们的安全策略,如@PreAuthorize和@Secured。 3.实现JWT JWT是一种基于令牌的身份验证机制,它使用JSON Web Token来传递安全信息。在我们的应用程序中,我们需要实现JWT的生成和验证机制,以便我们可以安全地登录和授权。 4.配置Vue.jsVue.js中,我们需要创建一个Vue.js项目,并使用Vue CLI来安装和配置我们的项目。我们需要使用Vue Router来定义我们的路由,并使用Axios来发送HTTP请求。 5.实现登录和授权 最后,我们需要实现登录和授权机制,以便用户可以安全地登录和访问我们的应用程序。在Vue.js中,我们可以使用Vue Router和Axios来发送HTTP请求,并在Spring Boot中使用JWT来验证用户身份。 总结 以上是实现Spring BootVue.js前后端分离的步骤,我们可以使用Security和JWT来实现安全的登录和授权机制。这种开发模式可以让我们更好地实现前后端分离,提高我们的开发效率和应用程序的安全性。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值