OAuth主要角色
现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式
核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录
这个是微信登录的开发平台
微信开放平台:https://open.weixin.qq.com
授权的流程
https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN ,
我们现在需要将二维码内嵌到网页当中
getLoginParam() {
return request({
url: `${api_name}/getLoginParam`,
method: `get`,
})
}
这是发送请求的js
后端写一个接口来接收这个
//1.生成微信二维码
//返回生成二维码需要的参数
@GetMapping("getLoginParam")
@ResponseBody
public Result getQrConnect() {
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("redirectUri", wxOpenRedirectUrl);
map.put("state", System.currentTimeMillis() + "");
return Result.ok(map);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
Controller用到的帮助类
@Component
public class ConstantWxPropertiesUtils implements InitializingBean {
@Value("${wx.open.app_id}")
private String appId;
@Value("${wx.open.app_secret}")
private String appSecret;
@Value("${wx.open.redirect_url}")
private String redirectUrl;
@Value("${yygh.baseUrl}")
private String yyghBaseUrl;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
public static String YYGH_BASE_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
YYGH_BASE_URL = yyghBaseUrl;
}
}
properties配置类
# 微信登录的配置文件
wx.open.app_id=wxed9954c01bb89b47
wx.open.app_secret=a7482517235173ddb4083788de60b90e
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
yygh.baseUrl=http://localhost:3000
vue前端
weixinLogin() {
this.dialogAtrr.showLoginType = 'weixin'
weixinApi.getLoginParam().then(response => {
var obj = new WxLogin({
self_redirect:true,
id: 'weixinLogin', // 需要显示的容器id
appid: response.data.appid, // 公众号appid wx*******
scope: response.data.scope, // 网页默认即可
redirect_uri: response.data.redirectUri, // 授权成功后回调的url
state: response.data.state, // 可设置为简单的随机数加session用来校验
style: 'black', // 提供"black"、"white"可选。二维码的样式
href: '' // 外部css文件url,需要https
})
})
},
这样就可以生成二维码了
虽然说现在有二维码了但是现在在手机上登录之后也不好进行校验,会报错404
第一步从回调url中取出code
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
通过配置文件当中的这个描述我们可以知道现在在扫描完二维码之后会回调这个方法,于是我们来写一下这个方法.
我们的需求是扫描以后获得用户昵称,还有openid存放到数据库中
在官方文档当中我们知道了,回调函数是通过Get请求提交的,同时还附带了2个参数code和state
code=CODE&state=STATE
于是我们的方法就写成这样
//0.微信扫描后回调的方法
@GetMapping("callback")
public String callback(String code, String state) throws UnsupportedEncodingException {
}
第二步通过code获取access_token
第三步通过access_token获取登录人的信息
下面我们来在业务层中实现这些
//0.微信扫描后回调的方法
@GetMapping("callback")
public String callback(String code, String state) throws UnsupportedEncodingException {
log.info("code为" + code);
log.info("state为" + state);
//获取code
//拿着code和微信id和密钥,请求微信的固定地址
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code";
String accesstokenInfo = restTemplate.getForObject(url, String.class, ConstantWxPropertiesUtils.WX_OPEN_APP_ID, ConstantWxPropertiesUtils.WX_OPEN_APP_SECRET, code);
log.info("accesstokenInfo为" + accesstokenInfo);
//把accesstokenInfo封装成一个json对象
JSONObject jsonObject = JSONObject.parseObject(accesstokenInfo);
String accessToken = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
UserInfo userInfo = userInfoService.selectByOpenid(openid);
//判断现在是否已经有了这个微信用户
if (userInfo == null) {
//拿着accessToken,openid请求微信地址得到扫描人的信息
String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token={access_token}&openid={openid}";
String userVoInfo = restTemplate.getForObject(userInfoUrl, String.class, accessToken, openid);
log.info(userVoInfo);
JSONObject userVoObject = JSONObject.parseObject(userVoInfo);
//用户昵称
String nickname = userVoObject.getString("nickname");
//用于昵称是windows1252格式转换为utf-8
nickname = new String(nickname.getBytes("windows-1256"),"UTF-8");
log.info("nickname为" + nickname);
//用户头像
String headimgurl = jsonObject.getString("headimgurl");
userInfo = new UserInfo();
userInfo.setNickName(nickname);
userInfo.setOpenid(openid);
//返回name和token字符串
userInfo.setStatus(1);
userInfoService.save(userInfo);
}
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);
//判断是否有手机号,如果手机号为空,返回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.YYGH_BASE_URL + "/weixin/callback?token=" + map.get("token") + "&openid=" + map.get("openid") + "&name=" + URLEncoder.encode((String) map.get("name"), "utf-8");
}
userInfoservice也做了一定的调整
@Override
public UserInfo selectByOpenid(String openid) {
QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("openid", openid);
UserInfo userInfo = baseMapper.selectOne(queryWrapper);
return userInfo;
}
在进行登录接口保存用户时也需要根据openid来判断用户是微信用户还是邮箱用户如果是微信登录那么也是需要绑定微信的.
//绑定手机号
UserInfo userInfo = null;
if (!StringUtils.isEmpty(loginVo.getOpenid())) {
userInfo = this.selectByOpenid(loginVo.getOpenid());
if (null != userInfo) {
userInfo.setPhone(loginVo.getPhone());
this.updateById(userInfo);
} else {
throw new YyghException(ResultCodeEnum.DATA_ERROR);
}
}
这样就实现了我们的功能