若依实现第三方登录通用方法——手机号登录测试

1. 新增LoginAuthenticationToken类,继承AbstractAuthenticationToken

这个直接复制就好了,更换包名,其他不用进行修改

package com.ruoyi.framework.handler;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;

import java.util.Collection;

public class LoginAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 550L;
    private final Object principal;
    private Object credentials;

    public LoginAuthenticationToken(Object principal, Object credentials) {
        super((Collection) null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

    public LoginAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    public Object getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        super.setAuthenticated(false);
    }

    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}

2. 创建LoginAuthenticationProvider类,继承AuthenticationProvider

这个直接复制就好了,更换包名,其他不用进行修改

package com.ruoyi.framework.handler;

import lombok.extern.log4j.Log4j2;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

@Log4j2
public class LoginAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    public LoginAuthenticationProvider(UserDetailsService userDetailsService) {
        setUserDetailsService(userDetailsService);
    }

    /**
     * 重写authentication方法,实现身份验证逻辑
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LoginAuthenticationToken authenticationToken = (LoginAuthenticationToken) authentication;
        String phone = (String) authenticationToken.getPrincipal();
        String loginType = (String) authenticationToken.getCredentials();
        //委托 UserDetailsService 查找系统用户
        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);
        //鉴权成功,返回一个拥有鉴权的AbstractAuthenticationToken
        LoginAuthenticationToken authenticationTokenRes = new LoginAuthenticationToken(userDetails, userDetails.getAuthorities());
        authenticationTokenRes.setDetails(authenticationToken.getDetails());
        return authenticationTokenRes;
    }

    /**
     * 重写supports方法,指定此AuthenticationProvider 仅支持短信验证码身份验证
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return LoginAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

3. 创建UserDetailsByOtherLoginServiceImpl实现类,直接复制不用改

位置:com.ruoyi.framework.web.service

重点:@Service(“userDetailsByOtherLogin”) 注解别忘了

package com.ruoyi.framework.web.service;

import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * 用户验证处理
 *
 * @author yizhi
 */
@Service("userDetailsByOtherLogin")
public class UserDetailsByOtherLoginServiceImpl implements UserDetailsService {
    private static final Logger log = LoggerFactory.getLogger(UserDetailsByOtherLoginServiceImpl.class);

    @Autowired
    private ISysUserService userService;
    @Autowired
    private SysPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user)) {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户:{} 已被删除.", user.getUserName());
            throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已被删除");
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户:{} 已被停用.", user.getUserName());
            throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已停用");
        }
        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user) {
        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
    }
}

4. 修改UserDetailsServiceImpl实现类,添加@Service(“userDetailsByPassword”)注解

位置:package com.ruoyi.framework.web.service

5. 修改SecurityConfig文件,修改后3和4才会生效

位置:com.ruoyi.framework.config

原有的userDetailsService上面添加个注解 @Qualifier(“userDetailsByPassword”) :这个是用来对应4的@Service(“userDetailsByPassword”) 注解的

添加userDetailsByOtherLoginService并在上面添加注解@Qualifier(“userDetailsByOtherLogin”):这个是用来对应3的@Service(“userDetailsByOtherLogin”)

身份认证接口上添加其他登录方式的验证,具体代码见下方

package com.ruoyi.framework.config;

/**
 * spring security配置
 *
 * @author ruoyi
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    @Qualifier("userDetailsByPassword")
    private UserDetailsService userDetailsService;
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    @Qualifier("userDetailsByOtherLogin")
    private UserDetailsService userDetailsByOtherLoginService;

    ==================================
        没有修改的地方省略了
    ==================================

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //其他登录方式的验证
        auth.authenticationProvider(new LoginAuthenticationProvider(userDetailsByOtherLoginService));
        //账号密码的验证
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

到此第三方登录的配置就完成了,接下来我们创建策略模式+工厂模式,简化后续接入其他第三方代码的成本

1. 创建工厂类

package com.ruoyi.framework.handler;

/**
 * 定义上下文
 *
 * @author yizhi
 */
@Component
public class LoginContext implements InitializingBean, ApplicationContextAware {

    private ApplicationContext appContext;
    private final Map<String, LoginStrategy> payStrategyHandlerMap = new HashMap<>();


    public LoginStrategy getHandler(String type) {
        return payStrategyHandlerMap.get(type);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Collection<LoginStrategy> values = appContext.getBeansOfType(LoginStrategy.class).values();
        values.forEach(loginStrategy -> payStrategyHandlerMap.put(loginStrategy.getLoginType(), loginStrategy));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        appContext = applicationContext;
    }
}

2. 创建策略接口

getLoginType:用来根据类型进行不同的策略

login:在上个类型的基础上执行登录获取token

package com.ruoyi.framework.handler;

/**
 * 登录策略接口
 *
 * @author yizhi
 */
public interface LoginStrategy {


    /**
     * 类型
     */

    String getLoginType();

    /**
     * 登录
     *
     * @param username
     * @param password
     * @param code
     * @return
     */
    String login(String username, String password, String code, String uuid);

}

3. 实现策略

a. 抽出密码登录中不需要修改的部分为公共类

package com.ruoyi.framework.handler;

import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Log4j2
@Component
public class LoginHanlder {


    @Autowired
    private ISysUserService userService;
    @Resource
    private AuthenticationManager authenticationManager;
    @Autowired
    private TokenService tokenService;


    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    public SysUser selectUserByPhone(String phone) {
        return userService.selectUserByPhone(phone);
    }

    /**
     * 获取token
     *
     * @return
     */
    public String getToken(LoginUser loginUser) {
        return tokenService.createToken(loginUser);
    }

    public Authentication authenticate(Authentication authentication) {
        return authenticationManager.authenticate(authentication);
    }
}

b. 抽出原有密码登录为密码策略, 并继承抽出的公共类

密码这块跟原有的密码登录对比没有修改

package com.ruoyi.framework.handler;

import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysConfigService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Log4j2
@Component
public class PasswordLoginHanlder extends LoginHanlder implements LoginStrategy {


    @Autowired
    private ISysConfigService configService;
    @Autowired
    private RedisCache redisCache;

    @Override
    public String getLoginType() {
        return "password";
    }

    @Override
    public String login(String username, String password, String code, String uuid) {
        log.info("这是密码策略");
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return getToken(loginUser);
    }


    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    private void validateCaptcha(String username, String code, String uuid) {
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }
}

c. 创建短信登录策略

重点是这里,checkCode方法进行自定义校验,然后根据当前是手机号登录还是什么登录查出用户信息,之后的代码就是跟密码登录一样了,不用改什么了

checkCode(phone, code);
log.info("这是短信登录");
//查询用户
SysUser sysUser = selectUserByPhone(phone);

附上完整代码

package com.ruoyi.framework.handler;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.List;

@Log4j2
@Component
public class SmsLoginHanlder extends LoginHanlder implements LoginStrategy {

    @Autowired
    private RedisCache redisCache;

    @Override
    public String getLoginType() {
        return "sms";
    }

    @Override
    public String login(String phone, String password, String code, String uuid) {
        checkCode(phone, code);
        log.info("这是短信登录");
        //查询用户
        SysUser sysUser = selectUserByPhone(phone);
        if (ObjectUtil.isEmpty(sysUser)) {
            throw new UserPasswordNotMatchException();
        }
        // 用户验证
        Authentication authentication = null;
        try {
            authentication = authenticate(new LoginAuthenticationToken(sysUser.getUserName(), "sms"));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(phone, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(phone, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(phone, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return getToken(loginUser);
    }

    @SneakyThrows
    private void checkCode(String phone, String code) {

        if (StrUtil.isBlank(code)) {
            throw new RuntimeException("验证码不能为空");
        }
        if (isSuperAccount(phone)) {
            return;
        }
        String key = "SMS_CODE_KEY:" + phone;
        redisCache.redisTemplate.setKeySerializer(new StringRedisSerializer());

        if (!redisCache.redisTemplate.hasKey(key)) {
            throw new RuntimeException("验证码不正确");
        }
        Object codeObj = redisCache.redisTemplate.opsForValue().get(key);

        if (codeObj == null) {
            throw new RuntimeException("验证码不正确");
        }
        String saveCode = codeObj.toString();
        if (StrUtil.isBlank(saveCode)) {
            redisCache.redisTemplate.delete(key);
            throw new RuntimeException("验证码不正确");
        }
        if (!StrUtil.equals(saveCode, code)) {
            throw new RuntimeException("验证码不正确");
        }
        redisCache.redisTemplate.delete(key);
    }

    private Boolean isSuperAccount(String mobile) {
        List<String> accountList = redisCache.redisTemplate.opsForList().range("SUPER_ACCOUNT:", 0, -1);
        if (accountList != null) {
            for (String s : accountList) {
                if (mobile.contains(s)) {
                    return true;
                }
            }
        }
        return false;
    }
}

到此策略就完成了

下面是用户登录操作

1. 修改LoginBody,添加登录类型

位置:com.ruoyi.common.core.domain.model

/**
     * 登录类型
     */
    private String loginType;

2. 原登录接口改为策略登录(/login)

位置:com.ruoyi.web.controller.system;

/**
     * 登录方法
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @PostMapping("/login")
    public AjaxResult login(@RequestBody LoginBody loginBody) {
        if (ObjectUtils.isEmpty(loginBody.getLoginType())) {
            return AjaxResult.error("登录类型不能为空");
        }
        // 生成令牌
        String token = loginContext.getHandler(loginBody.getLoginType())
                .login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());

        AjaxResult ajax = AjaxResult.success();
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }

到此,后端接口部分就完成了

前端修改 以手机号验证码为例

1. 修改src/api 下的login文件

修改

// 登录方法
export function login(username, password, code, uuid, loginType) {
  const data = {
    username,
    password,
    code,
    uuid,
    loginType
  }
  return request({
    url: '/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: data
  })
}

获取短信验证码

// 获取短信验证码
export function getSms(query) {
  return request({
    url: '/user/sms',
    method: 'get',
    params: query
  })
}

2. 创建短信登录页面smslogin.vue

位置:src/views/smslogin

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">一知随记</h3>
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          auto-complete="off"
          placeholder="手机号"
        >
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
        </el-input>
      </el-form-item>
      <el-form-item prop="code">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          placeholder="验证码"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
        </el-input>

        <div class="login-code">
          <el-button
            type="primary"
            @click="getVerify"
            style="width:100%;"
          >
            <span v-show="show">获取验证码</span>
            <span v-show="!show" class="count">{{ count }} s</span>
          </el-button>
        </div>

      </el-form-item>
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: left;" v-if="login">
          <router-link class="link-type" :to="'/login'">密码登录</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright yizhi-w.com All Rights Reserved.</span>
      <span>ICP主体备案号:  辽ICP备2022012082号</span>
    </div>
  </div>
</template>

<script>
import Cookies from 'js-cookie'
import { getSms } from '@/api/login'

export default {
  name: 'Login',
  data() {
    return {
      codeUrl: '',
      loginForm: {
        username: '',
        code: ''
      },
      loginRules: {
        username: [
          { required: true, trigger: 'blur', message: '请输入您的手机号' }
        ],
        code: [
          { required: true, trigger: 'blur', message: '请输入验证码' }
        ]
      },
      loading: false,
      login: true,

      redirect: undefined,
      show: true,
      count: '', // 初始化次数
      timer: null,

      queryParams: {
        phone: null
      }
    }
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect
      },
      immediate: true
    }
  },
  created() {
    this.getCookie()
  },
  methods: {
    getVerify() {
      // 验证手机号
      if (this.checkPhone() === false) {
        return false
      } else {
        this.queryParams.phone = this.loginForm.username
        getSms(this.queryParams).then(res => {
          this.$message.success('验证码发送成功')
          const TIME_COUNT = 60 //更改倒计时时间
          if (!this.timer) {
            this.count = TIME_COUNT
            this.show = false
            this.timer = setInterval(() => {
              if (this.count > 0 && this.count <= TIME_COUNT) {
                this.count--
              } else {
                this.show = true
                clearInterval(this.timer) // 清除定时器
                this.timer = null
              }
            }, 1000)
          }
        })
      }
    },

    checkPhone() {
      let phone = this.loginForm.username
      if (!/^1[3456789]\d{9}$/.test(phone)) {
        this.$message.error('请填写正确的手机号')
        return false
      }
    },

    getCookie() {
      const username = Cookies.get('username')
      const code = Cookies.get('code')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        code: code === undefined ? this.loginForm.code : code,
        loginType: 'sms'
      }
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('Login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' }).catch(() => {
            })
          }).catch(() => {
            this.loading = false
          })
        }
      })
    }
  }
}
</script>

<style rel="stylesheet/scss" lang="scss">
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}

.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}

.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;

  .el-input {
    height: 38px;

    input {
      height: 38px;
    }
  }

  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}

.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}

.login-code {
  width: 33%;
  height: 38px;
  float: right;

  img {
    cursor: pointer;
    vertical-align: middle;
  }
}

.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}

.login-code-img {
  height: 38px;
}
</style>

3. 修改login.vue页面

位置:src/views/login

部分代码 放在相应位置,51行左右

<div style="float: left;" v-if="smslogin">
          <router-link class="link-type" :to="'/smslogin'">短信登录</router-link>
</div>

loginForm添加 loginType: “password”,

loginForm: {
        username: "admin",
        password: "admin123",
        rememberMe: false,
        loginType: "password",
        code: "",
        uuid: ""
      },

短信登录开关设置,放在了注册开关下面

// 注册开关
register: true,
smslogin: true,

getCookie方法添加 loginType: ‘password’

getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
        loginType: 'password'
      };
    },

4. 修改登录相关,添加loginType

位置:src/store/modules/user.js

// 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      const loginType = userInfo.loginType
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid, loginType).then(res => {
          setToken(res.token)
          commit('SET_TOKEN', res.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

5. 页面路由添加短信页面路由

位置:src/router/index.js

{
    path: '/smslogin',
    component: () => import('@/views/smslogin'),
    hidden: true
  },

6. 修改权限,添加个smslogin登录不鉴权

位置:src/permission.js

const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/smslogin']

到此后端、前端的登录都完成了

以后要添加其他登录,

后端只需添加新的策略,

前端根据需求相应新增,后台管理,小程序免密等