【项目业务逻辑】单点登录问题具体实现

业务难点:

1.项目采用全后端分离开发

2.实现单点登录(即一处登录,其他模块也直接登录)

3.清楚JWT+Token的原理

首先登录问题并不是这个业务的难点,重点在于如何实现单点登录呢?

实现单点登录目前所学到的有三种方法:

① session广播机制(session复制到每个服务器)

② 使用cookie+redis实现:

在项目任何一个模块进行登录后,把数据放在redis中,key保存唯一随机值(ip或者用户id等),value保存用户数据,再把redis生成的key值放到cookie中

这样每次访问项目中任意模块,发送请求都会带着cookie到redis中进行查询

③ 使用token实现

在项目任何一个模块进行登录后,按照一定规则生成字符串返回给前端(可以通过cookie也可以通过地址栏)

每次再访问其他模块时,每次访问都带着特定字符串,根据字符串获取用户信息,如果获取到了就是登陆

JWT原理

 

代码具体实现过程:

为了方便我们对前端传来的数据进行操作,所以先封装一个Vo类

@Data
@ApiModel(value="登录对象", description="登录对象")
public class LoginVo {

    @ApiModelProperty(value = "手机号")
    private String mobile;

    @ApiModelProperty(value = "密码")
    private String password;
}

首先编写后端服务器Controller接口

public class UcenterMemberController {

    @Autowired
    private UcenterMemberService memberService;

    //登录
    @PostMapping("/login")
    public R loginUser(@RequestBody LoginVo loginVo){
        String token = memberService.login(loginVo);

        return R.ok().data("token",token);
    }

    //根据token获取用户信息
    @GetMapping("/getMemberInfo")
    public R getMemberInfo(HttpServletRequest request){
        //调用jwt工具类的方法,根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);

        UcenterMember member = memberService.getById(memberId);

        return R.ok().data("userInfo",member);
    }
}

接下来调Service层方法:

public String login(LoginVo loginVo) {
        //获取登录的手机号和密码
        String mobile = loginVo.getMobile();
        String password = loginVo.getPassword();

        //判断手机和密码是否非空
        if (StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new GuliException(20001, "登陆失败");
        }

        //判断手机号是否正确
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile", mobile);
        UcenterMember mobileMember = this.baseMapper.selectOne(wrapper);
        if (mobileMember == null) {
            throw new GuliException(20001, "登陆失败");
        }

        //判断密码是否正确
        //因为存储到数据库的密码是加密的,需要加密后比较

        if (!MD5.encrypt(password).equals(mobileMember.getPassword())) {
            throw new GuliException(20001, "登陆失败");
        }

        //判断用户是否禁用
        if (mobileMember.getIsDisabled()) {
            throw new GuliException(20001, "登陆失败");
        }

        //登录成功
        String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());

        return jwtToken;
    }

Ps: 判断类似非空这样的过滤检查,可以在前端直接过滤,无需在后端再进行检查

由于我们在将密码存放到数据库,为了保证我们数据的安全性,采用了MD5加密,由于MD5是不可逆的,所以我们将用户传来的密码进行加密后对比,即可检查。

重点是,如果登陆成功,我们需要用JWT生成token返回给前端页面。

接下来就是前端页面进行页面请求以及渲染:

先用Axios封装Ajax请求,这样方便我们对后端接口进行请求:

import request from '@/utils/request'

export default {

    //登录
  submitLoginUser(userInfo) {
    return request({
      url: `/eduucenter/member/login`,
      method: 'post',
      data: userInfo
    })
  },

  //获取用户信息
  getLoginUserInfo() {
    return request({
      url: `/eduucenter/member/getMemberInfo`,
      method: 'get'
    })
  }
}

登录页面完整代码:

<template>
  <div class="main">
    <div class="title">
      <a class="active" href="/login">登录</a>
      <span>·</span>
      <a href="/register">注册</a>
    </div>

    <div class="sign-up-container">
      <el-form ref="userForm" :model="user">
        <el-form-item
          class="input-prepend restyle"
          prop="mobile"
          :rules="[{ required: true, message: '请输入手机号码', trigger: 'blur' },{validator: checkPhone, trigger: 'blur'}]"
        >
          <div>
            <el-input type="text" placeholder="手机号" v-model="user.mobile" />
            <i class="iconfont icon-phone" />
          </div>
        </el-form-item>

        <el-form-item
          class="input-prepend"
          prop="password"
          :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
        >
          <div>
            <el-input type="password" placeholder="密码" v-model="user.password" />
            <i class="iconfont icon-password" />
          </div>
        </el-form-item>

        <div class="btn">
          <input type="button" class="sign-in-button" value="登录" @click="submitLogin()" />
        </div>
      </el-form>
      <!-- 更多登录方式 -->
      <div class="more-sign">
        <h6>社交帐号登录</h6>
        <ul>
          <li>
            <a
              id="weixin"
              class="weixin"
              target="_blank"
              href="http://qy.free.idcfengye.com/api/ucenter/weixinLogin/login"
            >
              <i class="iconfont icon-weixin" />
            </a>
          </li>
          <li>
            <a id="qq" class="qq" target="_blank" href="#">
              <i class="iconfont icon-qq" />
            </a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
import "~/assets/css/sign.css";
import "~/assets/css/iconfont.css";
import cookie from "js-cookie";

import loginApi from "@/api/login";

export default {
  layout: "sign",

  data() {
    return {
      user: {
        mobile: "",
        password: ""
      },
      loginInfo: {}
    };
  },

  methods: {
    //登录
    submitLogin() {
      //调用接口返回token
      loginApi.submitLoginUser(this.user).then(response => {
        //获取token字符串放到cookie中
        var token = response.data.data.token;
        //第一个参数是cookie中值的名称,第二个参数是值,第三个参数是作用范围(在哪些地址进行传递这个cookie)
        cookie.set("guli_token", token, { domain: "localhost" });

        //第四步 调用获取用户信息的接口
        loginApi.getLoginUserInfo().then(response => {
          this.loginInfo = response.data.data.userInfo;
          //获取返回的用户信息,放到cookie中
          cookie.set("guli_ucenter", JSON.stringify(this.loginInfo), { domain: "localhost" });

          //跳转页面
          window.location.href = "/";
        });
      });
    },

    checkPhone(rule, value, callback) {
      //debugger
      if (!/^1[34578]\d{9}$/.test(value)) {
        return callback(new Error("手机号码格式不正确"));
      }
      return callback();
    }
  }
};
</script>
<style>
.el-form-item__error {
  z-index: 9999999;
}
</style>

注意:这里我们先调用登录接口,如果成功登陆了再带着返回来的token值去根据token查询用户信息的接口,获得用户信息,再在页面进行返回,之后再返回首页。

其次,我们为了其他页面实现单点登录,我们需要将用户信息也放入到token值,所以,这样我们在别的页面在初次渲染的时候,调用created方法,先尝试获取我们的用户信息,下面以首页代码为例:

export default {
  data() {
    return {
      token: "",
      loginInfo: {
        id: "",
        age: "",
        avatar: "",
        mobile: "",
        nickname: "",
        sex: ""
      }
    };
  },

  created() {

    this.showInfo();
  },

  methods: {
    //创建方法,从cookie中获取用户信息
    showInfo() {
      //从cookie获取用户信息
      var userStr = cookie.get("guli_ucenter");
      // 把字符串转换为json对象
      if (userStr) {
        this.loginInfo = JSON.parse(userStr);
      }
    }
  }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值