几种简单的登录方式的实现——前端+后端
登录方式的实现
引言
想了一下之前项目中用到的登录方式,简单的总结一下
1、普通登录
- 普通登录的实现:根据用户输入的用户名和密码,提交到后台,后台判断用户输入的信息是否在数据库中存在,如果存在就给前端返回数据。
- 出现的问题:只要数据库存在用户信息,不管任何时候都可以登录,所以存在安全问题,就需要考虑权限控制,安全认证,防止CSRF attack等问题。
前端代码
$.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 attack
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= //配置前端域名地址 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]\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 Exception(ResultCodeEnum.PARAM_ERROR); } //判断手机验证码和输入的验证码是否一致 String redisCode = redisTemplate.opsForValue().get(phone); if(!code.equals(redisCode)) { throw new Exception(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 Exception(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 Exception(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; }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
mysql5.6升级及mysql无密码登录
mysql5.6升级 mysql5.6的升级可以分为以下几个步骤: 安全关闭正在运行的MySQL实例 把/usr/local/mysql 的连接由MySQL5.6更改为MySQL5.7 启动MySQL实例,查看是否是MySQL5.7版本 使用mysql_upgrade命令升级系统表 首先:停止当前运行的MySQL实例,然后做如下操作 更改之后启动MySQL实例: [root@test3 local]# service mysqld start Starting MySQL.. SUCCESS! [root@test3 local]# netstat -lntp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 31179/sshd tcp 0 0 :::22 :::* LISTEN 31179/sshd tcp 0 0 :::3000 ...
- 下一篇
ubantu与CentOS虚拟机之间搭建GRE隧道
Author : Email : vip_13031075266@163.com Date : 2020.01.23 Copyright: 未经同意不得转载!!! Version : openswan-2.6.51.5 Reference:https://download.openswan.org/openswan/ 目录 0. 前言 1. Linux内核支持的隧道类型 2. GRE隧道跨(公)网连接相同子网地址主机 2.1 拓扑环境: 2.1 ubantu配置: 2.1.1 添加GRE隧道 2.1.2 配置GRE隧道接口IP 2.1.3激活GRE隧道接口IP 2.2CentOS配置: 2.3ping包测试通讯链路: 3. GRE隧道跨(公)网连接不同子网地址主机 3.1 拓扑环境 3.2Ubantu配置 3.2.1 配置ens33子接口IP 3.2.2添加另一个GRE隧道接口并up 3.2.3配置隧道接口IP 3.2.4 添加对端子网路由表 3.3CentOS配置 3.3.1 配置ens33子接口IP 3.3.2添加另一个GRE隧道接口并up 3.3.3配置隧道接口IP 3.3.4 ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8安装Docker,最新的服务器搭配容器使用
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2更换Tomcat为Jetty,小型站点的福音