关于前端:几种简单的登录方式的实现前端后端

3次阅读

共计 12761 个字符,预计需要花费 32 分钟才能阅读完成。

登录形式的实现

引言

想了一下之前我的项目中用到的登录形式,简略的总结一下

1、一般登录

  • 一般登录的实现:依据用户输出的用户名和明码,提交到后盾,后盾判断用户输出的信息是否在数据库中存在,如果存在就给前端返回数据。
  • 呈现的问题:只有数据库存在用户信息,不论任何时候都能够登录,所以存在平安问题,就须要思考权限管制,平安认证,避免 CSRF 攻打等问题。

前端代码

$.ajax({
            url: '/login',
            type: 'POST',
            dataType: "json",
            data: {
                "username": username,
                "password": password,
            },
            success: function (result1) {
                // 获取后盾数据 result1
                if ("true" === result1.flag) {
                    // 信息正确,跳转到首页
                    window.location.href = "/common/index";
                } else if ("false" === result1.flag) {$("#tip2").html("用户不存在!");
                }
            },
            async: true,
            error: function () {
                // 申请失败跳转到登录
                window.location.href = "/tologin";
           }
})

后端 Controller 代码

@RequestMapping("/login")
    @ResponseBody
    public Map<String, String> userLogin(@RequestParam("username") String username,
                                         @RequestParam("password") String password,
                                         HttpServletRequest request) {Users users = userService.userLogin(username, password);
        Map<String, String> result = new HashMap<String, String>();
        if (users != null) {result.put("flag", "true");
        } else {result.put("flag", "false");
        }
        return result;
    }

后端 Service 代码

public Users userLogin(String username, String password) {return usermapper.userLogin(username, password);
 }

2、Token 验证

  • 什么是 Token

    它是在后盾也就是服务端产生的一串字符串,用来给前端鉴权的一种办法,前端如果遇到很频繁的申请后盾数据时,每次都须要把以后登录用户信息与数据库的比对,判断是否正确,才返回数据,这样无疑会减少服务器压力

  • Token 的作用

    防止 CSRF 攻打

    Token 属于无状态的,能够在多个服务中共享

  • 在我的项目中的实现:把用户登录信息提交到后盾,后盾会先判断数据库表中是否有这个人,如果不等于空,就生成 Token 令牌,把信息传给前端,前端收到 Token 令牌后,保留到 Local Storage,能够弄一个 axios 的拦截器,每次进行 axios 申请时,判断一下 Local Storage 中是否含有 Token,保障了登录安全性

前端代码

async success() {
      // 发动登入申请
      const {data: res} = await this.$http.post(
        "api/system/user/login",
        this.userLoginForm
      );
      if (res.success) {
        this.$message({
          title: "登入胜利",
          message: "欢送你进入零碎",
          type: "success"
        });
        // 把后盾返回的 token 信息保留到 LocalStorage
        LocalStorage.set(LOCAL_KEY_XINGUAN_ACCESS_TOKEN, res.data);
        // 获取以后登录用户用户信息
        await this.getUserInfo();} else {
        this.$message.error({
          title: "登入失败",
          message: res.data.errorMsg,
          type: "error"
      });
}

后端 Controller 代码

    @PostMapping("/login")
public ResponseBean<String> login(@RequestBody UserLoginDTO userLoginDTO, HttpServletRequest request) throws SystemException {log.info(userLoginDTO.getUsername()+userLoginDTO.getPassword()+userLoginDTO.getImageCode());
String token=
userService.login(userLoginDTO.getUsername(),userLoginDTO.getPassword(),userLoginDTO.getImageCode());
       loginLogService.add(request);
       return ResponseBean.success(token);
}

后端 Service 代码

 @Override
    public String login(String username, String password,String code) throws SystemException {
        String token;
        // 获取随机验证码
        String verifyCode = (String) redisTemplate.opsForValue().get("imageCode");
        if(code.equals(verifyCode)){User user = apiUserService.findUserByName(username);
            if (user != null) {
                // 对明码进行加盐加密
                String salt = user.getUSalt();
                // 秘钥为盐
                String target = MD5Utils.md5Encryption(password, salt);
                // 生成 Token
                token = JWTUtils.sign(username, target);
                JWTToken jwtToken = new JWTToken(token);
                try {SecurityUtils.getSubject().login(jwtToken);
                } catch (AuthenticationException e) {throw new SystemException(SystemCodeEnum.PARAMETER_ERROR,e.getMessage());
                }
            } else {throw new SystemException(SystemCodeEnum.PARAMETER_ERROR,"用户不存在");
            }
        }else{throw new SystemException(SystemCodeEnum.PARAMETER_ERROR,"验证码谬误");
        }
        return token;
    }

3、微信登录

微信登录也是一种平安登录形式,它录是基于 OAuth 2.0 协定规范构建的微信 OAuth 2.0 受权登录零碎,时序图如下

官网文档

https://developers.weixin.qq….

前端代码

// 后盾接口
const api_name = `/api/ucenter/wx`
export default {getLoginParam() {
    return request({url: `${api_name}/getLoginParam`,
      method: `get`
    })
  }
}
weixinApi.getLoginParam().then(response => {console.log(response);
        let REDIRECT_URI = encodeURIComponent(response.data.redirectUri);
        var obj = new WxLogin({
          self_redirect: true,
          id: "weixinLogin", // 须要显示的容器 id
          appid: response.data.appid, // 公众号 appid wx*******
          scope: response.data.scope, // 网页默认即可
          redirect_uri: REDIRECT_URI, // 受权胜利后回调的 url
          state: response.data.state, // 可设置为简略的随机数加 session 用来校验
          style: "black", // 提供 "black"、"white" 可选。二维码的款式
          href: "" // 内部 css 文件 url,须要 https
        });
});

后端代码

application.properties 文件配置

// 微信开发平台 appid
wx.open.app_id=
// 微信开发平台 appsecret
wx.open.app_secret=
// 微信开发平台重定向地址
wx.open.redirect_url=
// 配置前端域名地址
yygh.baseUrl=

后端 Controller 代码

// 微信扫码
@GetMapping("getLoginParam")
    @ResponseBody
    public Result genQrConnect() {
        try {Map<String, Object> map = new HashMap<>();
            map.put("appid", ConstantWxPropertiesUtils.WX_OPEN_APP_ID);
            map.put("scope","snsapi_login");
            String wxOpenRedirectUrl = ConstantWxPropertiesUtils.WX_OPEN_REDIRECT_URL;
            wxOpenRedirectUrl = URLEncoder.encode(wxOpenRedirectUrl, "utf-8");
            map.put("redirect_uri",wxOpenRedirectUrl);
            map.put("state",System.currentTimeMillis()+"");
            return Result.ok(map);
        } catch (UnsupportedEncodingException e) {e.printStackTrace();
            return null;
        }
}


    // 微信扫描后回调的办法
    @GetMapping("callback")
    public String callback(String code,String state) {
        // 第一步 获取长期票据 code
        System.out.println("code:"+code);
        // 第二步 拿着 code 和微信 id 和秘钥,申请微信固定地址,失去两个值
        // 应用 code 和 appid 以及 appscrect 换取 access_token
        //  %s 占位符
        StringBuffer baseAccessTokenUrl = new StringBuffer()
                .append("https://api.weixin.qq.com/sns/oauth2/access_token")
                .append("?appid=%s")
                .append("&secret=%s")
                .append("&code=%s")
                .append("&grant_type=authorization_code");
        String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
                ConstantWxPropertiesUtils.WX_OPEN_APP_ID,
                ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET,
                code);
        // 应用 httpclient 申请这个地址
        try {String accesstokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accesstokenInfo:"+accesstokenInfo);
            // 从返回字符串获取两个值 openid  和  access_token
            JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
            String access_token = jsonObject.getString("access_token");
            String openid = jsonObject.getString("openid");

            // 判断数据库是否存在微信的扫描人信息
            // 依据 openid 判断
            UserInfo userInfo = userInfoService.selectWxInfoOpenId(openid);
            if(userInfo == null) { // 数据库不存在微信信息
                // 第三步 拿着 openid 和 access_token 申请微信地址,失去扫描人信息
                String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                        "?access_token=%s" +
                        "&openid=%s";
                String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
                String resultInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println("resultInfo:"+resultInfo);
                JSONObject resultUserInfoJson = JSONObject.parseObject(resultInfo);
                // 解析用户信息
                // 用户昵称
                String nickname = resultUserInfoJson.getString("nickname");
                // 用户头像
                String headimgurl = resultUserInfoJson.getString("headimgurl");

                // 获取扫描人信息增加数据库
                userInfo = new UserInfo();
                userInfo.setNickName(nickname);
                userInfo.setOpenid(openid);
                userInfo.setStatus(1);
                userInfoService.save(userInfo);
            }
            // 返回 name 和 token 字符串
            Map<String,String> map = new HashMap<>();
            String name = userInfo.getName();
            if(StringUtils.isEmpty(name)) {name = userInfo.getNickName();
            }
            if(StringUtils.isEmpty(name)) {name = userInfo.getPhone();
            }
            map.put("name", name);

            // 判断 userInfo 是否有手机号,如果手机号为空,返回 openid
            // 如果手机号不为空,返回 openid 值是空字符串
            // 前端判断:如果 openid 不为空,绑定手机号,如果 openid 为空,不须要绑定手机号
            if(StringUtils.isEmpty(userInfo.getPhone())) {map.put("openid", userInfo.getOpenid());
            } else {map.put("openid", "");
            }
            // 应用 jwt 生成 token 字符串
            String token = JwtHelper.createToken(userInfo.getId(), name);
            map.put("token", token);
            // 跳转到前端页面
            return "redirect:" + ConstantWxPropertiesUtils.YYGH_BASE_URL + "/weixin/callback?token="+map.get("token")+ "&openid="+map.get("openid")+"&name="+URLEncoder.encode(map.get("name"),"utf-8");
        } catch (Exception e) {e.printStackTrace();
            return null;
        }
}

4、手机号登录

手机号的登录实现:依据用户输出的手机号,当提交登录后,后盾会先判断手机号是否会空,如果不为空,利用一个能够生成随机验证码的办法,把验证码保留到 Redis 中,并设置无效工夫,再把配置参数信息包含生成的验证码,提交到阿里云那里,判断配置信息是否正确,如果正确,向用户手机号发送短信验证码,用户输出验证码后,最初把输出的验证码用来与 Redis 中的验证码比照,如果雷同就返回数据给前端

引入依赖

<dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
</dependency>

application.properties 配置

// 配置阿里云 API 的密钥
aliyun.sms.regionId=default
aliyun.sms.accessKeyId=
aliyun.sms.secret=

前端代码

<div class="operate-view" v-if="dialogAtrr.showLoginType ==='phone'">
          <div class="wrapper" style="width: 100%">
            <div class="mobile-wrapper" style="position: static;width: 70%">
              <span class="title">{{dialogAtrr.labelTips}}</span>
              <el-form>
                <el-form-item>
                  <el-input
                    v-model="dialogAtrr.inputValue"
                    :placeholder="dialogAtrr.placeholder"
                    :maxlength="dialogAtrr.maxlength"
                    class="input v-input"
                  >
                    <span
                      slot="suffix"
                      class="sendText v-link"
                      v-if="dialogAtrr.second > 0"
                    >{{dialogAtrr.second}}s</span>
                    <span
                      slot="suffix"
                      class="sendText v-link highlight clickable selected"
                      v-if="dialogAtrr.second == 0"
                      @click="getCodeFun()"
                    > 从新发送 </span>
                  </el-input>
                </el-form-item>
              </el-form>
              <div class="send-button v-button" @click="btnClick()">{{ dialogAtrr.loginBtn}}</div>
            </div>
            <div class="bottom">
              <div class="wechat-wrapper" @click="weixinLogin()">
                <span class="iconfont icon"></span>
              </div>
              <span class="third-text"> 第三方账号登录 </span>
          </div>
      </div>
</div>

// 后盾接口
const api_name = `/api/sms`
export default {sendCode(mobile) {
    return request({url: `${api_name}/send/${mobile}`,
      method: `get`
    })
  }
}

    // 获取验证码
    getCodeFun() {if (!/^1[34578]\d{9}$/.test(this.userInfo.phone)) {this.$message.error("手机号码不正确");
        return;
      }

      // 初始化验证码相干属性
      this.dialogAtrr.inputValue = "";
      this.dialogAtrr.placeholder = "请输出验证码";
      this.dialogAtrr.maxlength = 6;
      this.dialogAtrr.loginBtn = "马上登录";

      // 管制反复发送
      if (!this.dialogAtrr.sending) return;
      // 发送短信验证码
      this.timeDown();
      this.dialogAtrr.sending = false;
      smsApi
        .sendCode(this.userInfo.phone)
        .then(response => {this.timeDown();
        })
        .catch(e => {this.$message.error("发送失败,从新发送");
          // 发送失败,回到从新获取验证码界面
          this.showLogin();});
    },

    // 倒计时
    timeDown() {if (this.clearSmsTime) {clearInterval(this.clearSmsTime);
      }
      this.dialogAtrr.second = 60;
      this.dialogAtrr.labelTips = "验证码已发送至" + this.userInfo.phone;
      this.clearSmsTime = setInterval(() => {
        --this.dialogAtrr.second;
        if (this.dialogAtrr.second < 1) {clearInterval(this.clearSmsTime);
          this.dialogAtrr.sending = true;
          this.dialogAtrr.second = 0;
        }
      }, 1000);
    },

后端 Controller 代码

    // 用户手机号登录接口
    @PostMapping("login")
    public Result login(@RequestBody LoginVo loginVo) {Map<String,Object> info = userInfoService.loginUser(loginVo);
        return Result.ok(info);
   }
    // 发送手机验证码
    @GetMapping("send/{phone}")
    public Result sendCode(@PathVariable String phone) {
        // 从 redis 获取验证码,如果获取获取到,返回 ok
        // key 手机号  value 验证码
        String code = redisTemplate.opsForValue().get(phone);
        if(!StringUtils.isEmpty(code)) {return Result.ok();
        }
        // 如果从 redis 获取不到,// 生成验证码,code = RandomUtil.getSixBitRandom();
        // 调用 service 办法,通过整合短信服务进行发送
        boolean isSend = msmService.send(phone,code);
        // 生成验证码放到 redis 外面,设置无效工夫
        if(isSend) {redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);
            return Result.ok();} else {return Result.fail().message("发送短信失败");
        }
    }

后端 Service 代码

    // 手机号登录 service
    @Override
    public Map<String, Object> loginUser(LoginVo loginVo) {
        // 从 loginVo 获取输出的手机号,和验证码
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();

        // 判断手机号和验证码是否为空
        if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }

        // 判断手机验证码和输出的验证码是否统一
        String redisCode = redisTemplate.opsForValue().get(phone);
        if(!code.equals(redisCode)) {throw new YyghException(ResultCodeEnum.CODE_ERROR);
        }

        // 绑定手机号码
        UserInfo userInfo = null;
        if(!StringUtils.isEmpty(loginVo.getOpenid())) {userInfo = this.selectWxInfoOpenId(loginVo.getOpenid());
            if(null != userInfo) {userInfo.setPhone(loginVo.getPhone());
                this.updateById(userInfo);
            } else {throw new YyghException(ResultCodeEnum.DATA_ERROR);
            }
        }

        // 如果 userinfo 为空,进行失常手机登录
        if(userInfo == null) {
            // 判断是否第一次登录:依据手机号查询数据库,如果不存在雷同手机号就是第一次登录
            QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
            wrapper.eq("phone",phone);
            userInfo = baseMapper.selectOne(wrapper);
            if(userInfo == null) { // 第一次应用这个手机号登录
                // 增加信息到数据库
                userInfo = new UserInfo();
                userInfo.setName("");
                userInfo.setPhone(phone);
                userInfo.setStatus(1);
                baseMapper.insert(userInfo);
            }
        }

        // 校验是否被禁用
        if(userInfo.getStatus() == 0) {throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        // 不是第一次,间接登录
        // 返回登录信息
        // 返回登录用户名
        // 返回 token 信息
        Map<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        if(StringUtils.isEmpty(name)) {name = userInfo.getNickName();
        }
        if(StringUtils.isEmpty(name)) {name = userInfo.getPhone();
        }
        map.put("name",name);

        //jwt 生成 token 字符串
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("token",token);
        return map;
    }
    // 提交验证码
    @Override
    public boolean send(String phone, String code) {
        // 判断手机号是否为空
        if(StringUtils.isEmpty(phone)) {return false;}
        // 整合阿里云短信服务
        // 设置相干参数
        DefaultProfile profile = DefaultProfile.
                getProfile(ConstantPropertiesUtils.REGION_Id,
                        ConstantPropertiesUtils.ACCESS_KEY_ID,
                        ConstantPropertiesUtils.SECRECT);
        IAcsClient client = new DefaultAcsClient(profile);
        CommonRequest request = new CommonRequest();
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        // 手机号
        request.putQueryParameter("PhoneNumbers", phone);
        // 签名名称
        request.putQueryParameter("SignName", "****");
        // 模板 code
        request.putQueryParameter("TemplateCode", "******");

        request.putQueryParameter("TemplateCode", "*****");
        // 验证码  应用 json 格局   {"code":"123456"}
        Map<String,Object> param = new HashMap();
        param.put("code",code);
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        // 调用办法进行短信发送
        try {CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {e.printStackTrace();
        } catch (ClientException e) {e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean send(MsmVo msmVo) {if(!StringUtils.isEmpty(msmVo.getPhone())) {boolean isSend = this.send(msmVo.getPhone(), msmVo.getParam());
            return isSend;
        }
        return false;
}
@Data
@ApiModel(description = "短信实体")
public class MsmVo {@ApiModelProperty(value = "phone")
    private String phone;

    @ApiModelProperty(value = "短信模板 code")
    private String templateCode;

    @ApiModelProperty(value = "短信模板参数")
    private Map<String,Object> param;
}
正文完
 0