首页  > 02年世界杯巴西

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

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

1、普通登录普通登录的实现:根据用户输入的用户名和密码,提交到后台,后台判断用户输入的信息是否在数据库中存在,如果存在就给前端返回数据。出现的问题:只要数据库存在用户信息,不管任何时候都可以登录,所以存在安全问题,就需要考虑权限控制,安全认证,防止CSRF攻击等问题。前端代码

代码语言:txt复制$.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代码

代码语言:txt复制@RequestMapping("/login")

@ResponseBody

public Map userLogin(@RequestParam("username") String username,

@RequestParam("password") String password,

HttpServletRequest request) {

Users users = userService.userLogin(username, password);

Map result = new HashMap();

if (users != null) {

result.put("flag", "true");

} else {

result.put("flag", "false");

}

return result;

}后端Service代码

代码语言:txt复制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,保证了登录安全性前端代码

代码语言:txt复制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代码

代码语言:txt复制 @PostMapping("/login")

public ResponseBean 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代码

代码语言:txt复制 @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授权登录系统,时序图如下

img官方文档

https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

前端代码

代码语言:txt复制//后台接口

const api_name = `/api/ucenter/wx`

export default {

getLoginParam() {

return request({

url: `${api_name}/getLoginParam`,

method: `get`

})

}

}代码语言:txt复制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文件配置

代码语言:txt复制//微信开发平台appid

wx.open.app_id=

//微信开发平台appsecret

wx.open.app_secret=

//微信开发平台重定向地址

wx.open.redirect_url=

//配置前端域名地址

baseUrl=后端Controller代码

代码语言:txt复制//微信扫码

@GetMapping("getLoginParam")

@ResponseBody

public Result genQrConnect() {

try {

Map 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 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中的验证码对比,如果相同就返回数据给前端

引入依赖

代码语言:txt复制

com.aliyun

aliyun-java-sdk-core

application.properties配置

代码语言:txt复制//配置阿里云API的密钥

aliyun.sms.regionId=default

aliyun.sms.accessKeyId=

aliyun.sms.secret=前端代码

代码语言:txt复制

{{ dialogAtrr.labelTips }}

v-model="dialogAtrr.inputValue"

:placeholder="dialogAtrr.placeholder"

:maxlength="dialogAtrr.maxlength"

class="input v-input"

>

slot="suffix"

class="sendText v-link"

v-if="dialogAtrr.second > 0"

>{{ dialogAtrr.second }}s

slot="suffix"

class="sendText v-link highlight clickable selected"

v-if="dialogAtrr.second == 0"

@click="getCodeFun()"

>重新发送

{{ dialogAtrr.loginBtn }}

第三方账号登录

代码语言:txt复制//后台接口

const api_name = `/api/sms`

export default {

sendCode(mobile) {

return request({

url: `${api_name}/send/${mobile}`,

method: `get`

})

}

}代码语言:txt复制 // 获取验证码

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代码

代码语言:txt复制 // 用户手机号登录接口

@PostMapping("login")

public Result login(@RequestBody LoginVo loginVo) {

Map info = userInfoService.loginUser(loginVo);

return Result.ok(info);

}代码语言:txt复制 // 发送手机验证码

@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代码

代码语言:txt复制 // 手机号登录service

@Override

public Map 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 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 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;

}代码语言:txt复制 //提交验证码

@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 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;

}代码语言:txt复制@Data

@ApiModel(description = "短信实体")

public class MsmVo {

@ApiModelProperty(value = "phone")

private String phone;

@ApiModelProperty(value = "短信模板code")

private String templateCode;

@ApiModelProperty(value = "短信模板参数")

private Map param;

}