一、引言
微信小程序的登录可以说是必备的门户,作者今天介绍一下微信小程序如何登录。
二、前端
1、wxml
wxml其实和前端一样,就是html的意思。
其实就是给两个按钮触发,可以使用微信直接登录,可以让用户注册账号进行登录。
<view class="container">
<view class="login-box">
<button type="primary" class="wx-login-btn" bindtap="getUserProfile">微信直接登录</button>
<button type="primary" class="account-login-btn" bindtap="accountLogin">账号登录</button>
</view>
</view>
2、微信直接登录
重点在于很多功能都必须使用微信用户的openid,比如给他发消息,这就是每个微信用户的唯一编码,拿不到用户基本什么功能都用不了。
这个和用户的密码之类的毫无关系,也根本影响不到用户,微信的安全还是很严的
getUserProfile(e) {
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
// 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
wx.getUserProfile({
desc: '用于完善资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
success: (res) => {
user.checkLogin().catch(() => {
user.loginByWeixin(res.userInfo).then(res => {
console.log('微信登录成功');
app.globalData.hasLogin = true;
wx.navigateBack({
delta: 1
})
}).catch((err) => {
app.globalData.hasLogin = false;
util.showErrorToast('微信登录失败');
});
});
},
fail: (res) => {
app.globalData.hasLogin = false;
util.showErrorToast('微信登录失败');
}
});
},
/**
* 调用微信登录
*/
function loginByWeixin(userInfo) {
let shareUserId = wx.getStorageSync('shareUserId');
if (!shareUserId || shareUserId =='undefined'){
shareUserId = 1;
}
return new Promise(function(resolve, reject) {
return login().then((res) => {
//登录远程服务器
util.request(api.AuthLoginByWeixin, {
code: res.code,
userInfo: userInfo,
shareUserId: shareUserId
}, 'POST').then(res => {
if (res.errno === 0) {
//存储用户信息
wx.setStorageSync('userInfo', res.data.userInfo);
wx.setStorageSync('token', res.data.token);
resolve(res);
} else {
console.log(res);
reject(res);
}
}).catch((err) => {
console.log(err);
reject(err);
});
}).catch((err) => {
console.log(err);
reject(err);
})
});
}
3、账户登录
这个和微信直接登录的区别就是不会把一些昵称之类的带过去,和WEB网站一样可以进行注册就行。
个人注册的微信小程序有点坑,不给拿用户的手机号。
<view class="container">
<view class="form-box">
<view class="form-item">
<input class="username" value="{{username}}" bindinput="bindUsernameInput" placeholder="账号"/>
<image wx:if="{{ username.length > 0 }}" id="clear-username" class="clear"
src="/static/images/clear_input.png" catchtap="clearInput"></image>
</view>
<view class="form-item">
<input class="password" value="{{password}}" password bindinput="bindPasswordInput" placeholder="密码"/>
<image class="clear" id="clear-password" wx:if="{{ password.length > 0 }}"
src="/static/images/clear_input.png" catchtap="clearInput"></image>
</view>
<!-- <view class="form-item-code" wx-if="{{loginErrorCount >= 3}}">
<view class="form-item code-item">
<input class="code" value="{{code}}" bindinput="bindCodeInput" placeholder="验证码"/>
<image class="clear" id="clear-code" wx:if="{{ code.length > 0 }}" src="/static/images/clear_input.png" catchtap="clearInput"></image>
</view>
<image class="code-img" src="https://dl.reg.163.com/cp?pd=yanxuan_web&pkid=SkeBZeG&random=1489903563234"></image>
</view> -->
<button type="primary" class="login-btn" bindtap="accountLogin">账号登录</button>
<view class="form-item-text">
<navigator url="/pages/auth/register/register" class="register">注册账号</navigator>
<!-- <navigator url="/pages/auth/reset/reset" class="reset">忘记密码</navigator>-->
</view>
</view>
</view>
accountLogin: function() {
var that = this;
if (this.data.password.length < 1 || this.data.username.length < 1) {
wx.showModal({
title: '错误信息',
content: '请输入用户名和密码',
showCancel: false
});
return false;
}
wx.request({
url: api.AuthLoginByAccount,
data: {
username: that.data.username,
password: that.data.password
},
method: 'POST',
header: {
'content-type': 'application/json'
},
success: function(res) {
if (res.data.errno == 0) {
that.setData({
loginErrorCount: 0
});
app.globalData.hasLogin = true;
wx.setStorageSync('userInfo', res.data.data.userInfo);
wx.setStorage({
key: "token",
data: res.data.data.token,
success: function() {
wx.switchTab({
url: '/pages/ucenter/index/index'
});
}
});
} else {
that.setData({
loginErrorCount: that.data.loginErrorCount + 1
});
app.globalData.hasLogin = false;
util.showErrorToast('账户登录失败');
}
}
});
},
bindUsernameInput: function(e) {
this.setData({
username: e.detail.value
});
},
bindPasswordInput: function(e) {
this.setData({
password: e.detail.value
});
},
bindCodeInput: function(e) {
this.setData({
code: e.detail.value
});
},
clearInput: function(e) {
switch (e.currentTarget.id) {
case 'clear-username':
this.setData({
username: ''
});
break;
case 'clear-password':
this.setData({
password: ''
});
break;
case 'clear-code':
this.setData({
code: ''
});
break;
}
}
三、后端
1、pom
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.1.0</version>
</dependency>
2、配置
sh:
# 开发者应该设置成自己的wx相关信息
wx:
app-id: **
app-secret: **
3、微信直接登录
重点有两个
一个是要通过微信的WxMaService获取openid和sessionKey
另外一个是要放在缓存防止登录过期
@PostMapping("login_by_wx")
public Object loginByWx(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) {
logger.info("【请求开始】微信登录,请求参数,wxLoginInfo:{}", wxLoginInfo.toString());
String code = wxLoginInfo.getCode();
UserInfo userInfo = wxLoginInfo.getUserInfo();
if (code == null || userInfo == null) {
return ResponseUtil.badArgument();
}
Integer shareUserId = wxLoginInfo.getShareUserId();
String sessionKey = null;
String openId = null;
try {
WxMaJscode2SessionResult result = wxService.getUserService().getSessionInfo(code);
sessionKey = result.getSessionKey();
openId = result.getOpenid();
} catch (Exception e) {
e.printStackTrace();
}
if (sessionKey == null || openId == null) {
logger.error("微信登录,调用官方接口失败:{}", code);
return ResponseUtil.fail();
}
ArriveUser user = userService.queryByOid(openId);
if (user == null) {
user = new ArriveUser();
user.setUsername(openId);
user.setPassword(openId);
user.setWeixinOpenid(openId);
user.setAvatar(userInfo.getAvatarUrl());
user.setNickname(userInfo.getNickName());
user.setGender(userInfo.getGender());
user.setUserLevel((byte) 0);
user.setStatus((byte) 0);
user.setLastLoginTime(LocalDateTime.now());
user.setLastLoginIp(IpUtil.client(request));
user.setShareUserId(shareUserId);
userService.add(user);
} else {
user.setLastLoginTime(LocalDateTime.now());
user.setLastLoginIp(IpUtil.client(request));
if (userService.updateById(user) == 0) {
return ResponseUtil.updatedDataFailed();
}
}
// token
UserToken userToken = null;
try {
userToken = UserTokenManager.generateToken(user.getId());
} catch (Exception e) {
logger.error("微信登录失败,生成token失败:{}", user.getId());
e.printStackTrace();
return ResponseUtil.fail();
}
userToken.setSessionKey(sessionKey);
Map<Object, Object> result = new HashMap<Object, Object>();
result.put("token", userToken.getToken());
result.put("tokenExpire", userToken.getExpireTime().toString());
userInfo.setUserId(user.getId());
if (!StringUtils.isEmpty(user.getMobile())) {// 手机号存在则设置
userInfo.setPhone(user.getMobile());
}
try {
String registerDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.format(user.getAddTime() != null ? user.getAddTime() : LocalDateTime.now());
userInfo.setRegisterDate(registerDate);
userInfo.setStatus(user.getStatus());
userInfo.setUserLevel(user.getUserLevel());// 用户层级
userInfo.setUserLevelDesc(UserTypeEnum.getInstance(user.getUserLevel()).getDesc());// 用户层级描述
} catch (Exception e) {
logger.error("微信登录:设置用户指定信息出错:" + e.getMessage());
e.printStackTrace();
}
result.put("userInfo", userInfo);
logger.info("【请求结束】微信登录,响应结果:{}", (result));
return ResponseUtil.ok(result);
}
4、账号登录
先得注册
@PostMapping("register")
public Object register(@RequestBody String body, HttpServletRequest request) {
logger.info("【请求开始】账号注册,请求参数,body:{}", body);
String username = JacksonUtil.parseString(body, "username");
String password = JacksonUtil.parseString(body, "password");
// String mobile = JacksonUtil.parseString(body, "mobile");
// String code = JacksonUtil.parseString(body, "code");
String wxCode = JacksonUtil.parseString(body, "wxCode");
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)
|| StringUtils.isEmpty(wxCode)
// || StringUtils.isEmpty(code)
) {
return ResponseUtil.badArgument();
}
List<ArriveUser> userList = userService.queryByUsername(username);
if (userList.size() > 0) {
logger.error("请求账号注册出错:{}", AUTH_NAME_REGISTERED.desc());
return WxResponseUtil.fail(AUTH_NAME_REGISTERED);
}
// userList = userService.queryByMobile(mobile);
// if (userList.size() > 0) {
// logger.error("请求账号注册出错:{}", AUTH_MOBILE_REGISTERED.desc());
// return WxResponseUtil.fail(AUTH_MOBILE_REGISTERED);
// }
// if (!RegexUtil.isMobileExact(mobile)) {
// logger.error("请求账号注册出错:{}", AUTH_INVALID_MOBILE.desc());
// return WxResponseUtil.fail(AUTH_INVALID_MOBILE);
// }
// 判断验证码是否正确
// String cacheCode = CaptchaCodeManager.getCachedCaptcha(mobile);
// if (cacheCode == null || cacheCode.isEmpty() || !cacheCode.equals(code)) {
// logger.error("请求账号注册出错:{}", AUTH_CAPTCHA_UNMATCH.desc());
// return WxResponseUtil.fail(AUTH_CAPTCHA_UNMATCH);
// }
String openId = null;
try {
WxMaJscode2SessionResult result = this.wxService.getUserService().getSessionInfo(wxCode);
openId = result.getOpenid();
} catch (Exception e) {
logger.error("请求账号注册出错:{}", AUTH_OPENID_UNACCESS.desc());
e.printStackTrace();
return WxResponseUtil.fail(AUTH_OPENID_UNACCESS);
}
userList = userService.queryByOpenid(openId);
if (userList.size() > 1) {
return ResponseUtil.serious();
}
if (userList.size() == 1) {
ArriveUser checkUser = userList.get(0);
String checkUsername = checkUser.getUsername();
String checkPassword = checkUser.getPassword();
if (!checkUsername.equals(openId) || !checkPassword.equals(openId)) {
logger.error("请求账号注册出错:{}", AUTH_OPENID_BINDED.desc());
return WxResponseUtil.fail(AUTH_OPENID_BINDED);
}
}
ArriveUser user = null;
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedPassword = encoder.encode(password);
user = new ArriveUser();
user.setUsername(username);
user.setPassword(encodedPassword);
// user.setMobile(mobile);
user.setWeixinOpenid(openId);
// user.setAvatar(CommonConstant.DEFAULT_AVATAR);
user.setNickname(username);
user.setGender((byte) 0);
user.setUserLevel((byte) 0);
user.setStatus((byte) 0);
user.setLastLoginTime(LocalDateTime.now());
user.setLastLoginIp(IpUtil.client(request));
userService.add(user);
// userInfo
UserInfo userInfo = new UserInfo();
userInfo.setNickName(username);
userInfo.setAvatarUrl(user.getAvatar());
// token
UserToken userToken = null;
try {
userToken = UserTokenManager.generateToken(user.getId());
} catch (Exception e) {
logger.error("账号注册失败,生成token失败:{}", user.getId());
e.printStackTrace();
return ResponseUtil.fail();
}
Map<Object, Object> result = new HashMap<Object, Object>();
result.put("token", userToken.getToken());
result.put("tokenExpire", userToken.getExpireTime().toString());
result.put("userInfo", userInfo);
logger.info("【请求结束】账号注册,响应结果:{}", (result));
return ResponseUtil.ok(result);
}
然后再登录
@PostMapping("login")
public Object login(@RequestBody String body, HttpServletRequest request) {
logger.info("【请求开始】账户登录,请求参数,body:{}", body);
String username = JacksonUtil.parseString(body, "username");
String password = JacksonUtil.parseString(body, "password");
if (username == null || password == null) {
return ResponseUtil.badArgument();
}
List<ArriveUser> userList = userService.queryByUsername(username);
ArriveUser user = null;
if (userList.size() > 1) {
logger.error("账户登录 出现多个同名用户错误,用户名:{},用户数量:{}", username, userList.size());
return ResponseUtil.serious();
} else if (userList.size() == 0) {
logger.error("账户登录 用户尚未存在,用户名:{}", username);
return ResponseUtil.badArgumentValue();
} else {
user = userList.get(0);
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
if (!encoder.matches(password, user.getPassword())) {
logger.error("账户登录 ,错误密码:{},{}", password, AUTH_INVALID_ACCOUNT.desc());// 错误的密码打印到日志中作为提示也无妨
return WxResponseUtil.fail(AUTH_INVALID_ACCOUNT);
}
// userInfo
UserInfo userInfo = new UserInfo();
userInfo.setNickName(username);
userInfo.setAvatarUrl(user.getAvatar());
try {
LocalDateTime dateTime = user.getAddTime() != null ? user.getAddTime() : LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String registerDate = dateTime.format(formatter);
userInfo.setRegisterDate(registerDate);
userInfo.setStatus(user.getStatus());
userInfo.setUserLevel(user.getUserLevel());// 用户层级
userInfo.setUserLevelDesc(UserTypeEnum.getInstance(user.getUserLevel()).getDesc());// 用户层级描述
} catch (Exception e) {
logger.error("账户登录:设置用户指定信息出错:" + e.getMessage());
e.printStackTrace();
}
// token
UserToken userToken = null;
try {
userToken = UserTokenManager.generateToken(user.getId());
} catch (Exception e) {
logger.error("账户登录失败,生成token失败:{}", user.getId());
e.printStackTrace();
return ResponseUtil.fail();
}
Map<Object, Object> result = new HashMap<Object, Object>();
result.put("token", userToken.getToken());
result.put("tokenExpire", userToken.getExpireTime().toString());
result.put("userInfo", userInfo);
logger.info("【请求结束】账户登录,响应结果:{}", (result));
return ResponseUtil.ok(result);
}
5、用户token
public class UserTokenManager {
private static Map<String, UserToken> tokenMap = new HashMap<>();
private static Map<Integer, UserToken> idMap = new HashMap<>();
public static Integer getUserId(String token) {
UserToken userToken = tokenMap.get(token);
if (userToken == null) {
return null;
}
if (userToken.getExpireTime().isBefore(LocalDateTime.now())) {
tokenMap.remove(token);
idMap.remove(userToken.getUserId());
return null;
}
return userToken.getUserId();
}
public static UserToken generateToken(Integer id) {
UserToken userToken = null;
// userToken = idMap.get(id);
// if(userToken != null) {
// tokenMap.remove(userToken.getToken());
// idMap.remove(id);
// }
String token = CharUtil.getRandomString(32);
while (tokenMap.containsKey(token)) {
token = CharUtil.getRandomString(32);
}
LocalDateTime update = LocalDateTime.now();
LocalDateTime expire = update.plusDays(1);
userToken = new UserToken();
userToken.setToken(token);
userToken.setUpdateTime(update);
userToken.setExpireTime(expire);
userToken.setUserId(id);
tokenMap.put(token, userToken);
idMap.put(id, userToken);
return userToken;
}
public static String getSessionKey(Integer userId) {
UserToken userToken = idMap.get(userId);
if (userToken == null) {
return null;
}
if (userToken.getExpireTime().isBefore(LocalDateTime.now())) {
tokenMap.remove(userToken.getToken());
idMap.remove(userId);
return null;
}
return userToken.getSessionKey();
}
public static void removeToken(Integer userId) {
UserToken userToken = idMap.get(userId);
String token = userToken.getToken();
idMap.remove(userId);
tokenMap.remove(token);
}
}
四、总结
微信小程序的登录还是比较麻烦的,安全是微信能拥有这么大用户体量的原因之一,开发或者调试过程中遇到问题,欢迎和作者沟通,也可以关注作者的微信公众号:胖当当技术。