几种简单的登录方式的实现——前端+后端

Posted 全栈开发Dream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几种简单的登录方式的实现——前端+后端相关的知识,希望对你有一定的参考价值。

登录方式的实现

引言

想了一下之前项目中用到的登录方式,简单的总结一下

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.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

前端代码

//后台接口
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.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]\\d9$/.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 = "验证码已发送至" + 以上是关于几种简单的登录方式的实现——前端+后端的主要内容,如果未能解决你的问题,请参考以下文章

项目——博客系统

加盐加密的介绍

前端ajax异步传值以及后端接收参数的几种方式

前端ajax异步传值以及后端接收参数的几种方式

SpringBootSecurity学习(12)前后端分离版之简单登录

微信小程序 - 获取用户信息的几种方式