用户不想去注册你的网站,觉得输入这些信息麻烦。更愿意像直接扫码进行登录这样,简单。
扫码人的信息自动注册,加入到你的数据库中。
微信登录是一个固定的流程,是腾讯规定的固定流程去做。
微信登录讲解之前,先讲解OAuth2
OAuth2是针对特定问题的一种解决方案。
主要可以解决两个问题:
1. 开放系统间授权 2.分布式访问问题
开放系统间授权:
lucy照了很多照片,她把它们都存到了自己的百度网盘上去了。但是百度网盘没法打印照片啊,某公司自己研发了一款系统,专门用于这种云存储的打印服务。
但是,默认情况下,这款系统默认是不能操作lucy存到她百度网盘上的东西的。没有权限。
但是百度网盘提供了授权服务的相关方法。
这个时候,只要lucy授权给这款打印系统,那么就可以实现打印服务。
具体怎么做到的,这就是OAuth2要做的事情。也是百度网盘要规定的流程。
分布式访问
我们只需要把token相关的代码抽取出来,生成一个公共的类。
这样,即使是不同的模块之间,他们的token创建和解析的加密方式也都是一样的,也就能解析到cookie中传过来的token信息,进行登录。
当然了,我们可以使用redis存储token以及对应用户信息,这样不同模块就都能拿到用户信息。否则的话,我们只知道是当前用户,当前用户的用户信息,也只是token解析出来的信息。比如用户id。当然,因为用户id具有唯一性嘛,所以通过用户id关联自己模块的业务也是可以的。比如:某人登录了视频服务,在视频服务会存储一个视频记录的数据表。这个时候,只要数据表关联用户id就可以了。对于视频服务而言,是没有问题的。
OAuth2解决方案:按照一定规则生成字符串,字符串包含用户信息。
OAuth2只是一种解决方案,这种方案具体怎么做由方案提供者自己定。
准备工作
1. 首先你想要使用腾讯公司微信做登录这样的相关操作,必须在腾讯的开发者平台注册,获得资质!微信开放平台
(1) 支持企业类型(以前只支持企业注册)
(2) 注册之后,会给你提供微信id和微信密钥
ps:注册需要准备营业执照、1-2个工作日审批、300元认证费
2. 申请网站应用名称。
就是说你扫描二维码,它会显示当前网站的应用名称,比如:我的谷粒。这个一般在7个工作日内审批。
3. 需要域名地址。
地址作用:微信扫完二维码之后,找你的域名做跳转。
熟悉微信登录流程:
下面我们快速看一下
参考文档:微信开放平台
1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
在你的用户模块中,application.properties添加相关配置信息
# 微信开放平台 appid
wx.open.app_id=你的appid
# 微信开放平台 appsecret
wx.open.app_secret=你的appsecret
# 微信开放平台 重定向url
wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback
创建util包,创建ConstantWxUtils.java常量类
package com.atguigu.educenter.utils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ConstantWxUtils 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;
public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;
@Override
public void afterPropertiesSet() throws Exception {
WX_OPEN_APP_ID = appId;
WX_OPEN_APP_SECRET = appSecret;
WX_OPEN_REDIRECT_URL = redirectUrl;
}
}
直接请求微信提供的固定的地址,向地址的后面拼接参数即可。
即重定向到微信的二维码生成地址去。
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirects
参数说明:
参数 是否必须 说明 appid 是 应用唯一标识 redirect_uri 是 请使用urlEncode对链接进行处理 response_type 是 填code scope 是 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即 state 否 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
在当前educenter模块中创建api包
api包中创建WxApiController
代码的写法
package com.atguigu.educenter.controller;
import com.atguigu.educenter.utils.ConstantWxUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URLEncoder;
@CrossOrigin
@Controller //只是请求地址,不需要返回数据,这个时候不能用@RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
//1 生成微信扫描二维码
@GetMapping("login")
public String getWxCode() {
//固定地址,后面拼接参数
//写法一(这么做效率很低,多了还容易写错):
// String url = "https://open.weixin.qq.com/connect/qrconnect" +
// "?appid="+
// ConstantWxUtils.WX_OPEN_APP_ID +
// "&redirect_uri=" +
// ConstantWxUtils.WX_OPEN_REDIRECT_URL +
// "&response_type=code" +
// "&scope=snsapi_login" +
// "state=atguigu" +
// "#wechat_redirect";
//当然,上面这个redirect_uri地址要先编码再进行传,不能那样直接传。
//写法二(推荐)
// 微信开放平台授权baseUrl %s 相当于?标识占位符
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
//对redirect_url进行URLEncoder编码
String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
try {
redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
}catch(Exception e) {
}
//设置%s里面值
String url = String.format(
baseUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
redirectUrl,
"atguigu"
);
//重定向到请求微信地址里面
return "redirect:" + url;
}
}
此时启动改服务,然后请求当前接口:localhost:8160/api/ucenter/wx/login
它会根据你接口中写的代码,重定向到微信的二维码生成地址,拼接的参数就是你传的参数
比如:
https://open.weixin.qq.com/connect/qrconnect?appid=wxed9954c01bb80b37&redirect_uri=http%3A%2F%2Flocalhost%3A8160%2Fapi%2Fucenter%2Fwx%2Fcallback&response_type=code&scope=snsapi_login&state=atguigu#wechat_redirect
扫描成功确认之后,微信会获取到用户的一个code值。然后微信会把code值给到我们,跳转到如下我们的回调地址,地址后拼接了code参数和state参数:
localhost:8160/api/ucenter/wx/callback?code=021Grg0w34r37Z2c9S2w3xmv180Grg0o&state=atguigu
api/ucenter/wx/callback肯定不是我们的地址,我们还没有写这个方法。
ps:这个回调地址就是我们之前在配置文件中配置的redirect_url重定向地址,它是当初注册资质时填写的域名地址。在获取验证码时就传递给了微信,这个时候微信在将地址返回给我们。
wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
这个回调方法的作用就是用于我们获取用户信息。微信说让我们用这个接口去请求用户信息。
下面我们就需要自己来写这个回调方法,在方法里面,通过获取到微信传回来的code值,带着这个值去请求微信另外的一个固定地址获取asses_token,openid
通过httpclint直接请求微信,然后获取到返回的accsess_token 和 openid信息。
接着我们可以把它当前用户信息加入到自己的数据库。(你也可以选择不加入)
首先判断当前用户的openid是否在自己的数据库已经存在,如果存在不用添加了。
如果不存在,那么就通过这个accsess_token 和 openid再去请求微信的又一个固定地址,获取用户信息。
还是通过HttpClient发送请求,然后接收返回值。
一样也需要通过json转换工具转换成字符串。否则是json格式的数据无法操作。
拿到用户信息后我们就可以加入到自己的数据库里面了。
我们看看代码怎么写:
package com.atguigu.educenter.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.atguigu.commonutils.JwtUtils;
import com.atguigu.educenter.bean.UcenterMember;
import com.atguigu.educenter.service.UcenterMemberService;
import com.atguigu.educenter.utils.ConstantWxUtils;
import com.atguigu.educenter.utils.HttpClientUtils;
import com.atguigu.servicebase.exceptionHandler.GuliException;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin
@Controller //只是请求地址,不需要返回数据。这个时候不能用@RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {
@Autowired
private UcenterMemberService memberService;
//2 获取扫描人信息,添加数据
@GetMapping("callback")
public String callback(String code, String state) {
try {
//1 获取code值,临时票据,类似于验证码
//2 拿着code请求 微信固定的地址,得到两个值 accsess_token 和 openid
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
//拼接三个参数 :id 秘钥 和 code值
String accessTokenUrl = String.format(
baseAccessTokenUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_APP_SECRET,
code
);
//请求这个拼接好的地址,得到返回两个值 accsess_token 和 openid
//使用httpclient发送请求,得到返回结果
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
//从accessTokenInfo字符串获取出来两个值 accsess_token 和 openid
//把accessTokenInfo字符串转换map集合,根据map里面key获取对应值
//使用json转换工具 Gson
Gson gson = new Gson();
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String access_token = (String)mapAccessToken.get("access_token");
String openid = (String)mapAccessToken.get("openid");
//把扫描人信息添加数据库里面
//判断数据表里面是否存在相同微信信息,根据openid判断
UcenterMember member = memberService.getOpenIdMember(openid);
if(member == null) {//memeber是空,表没有相同微信数据,进行添加
//3 拿着得到accsess_token 和 openid,再去请求微信提供固定的地址,获取到扫描人信息
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
//拼接两个参数
String userInfoUrl = String.format(
baseUserInfoUrl,
access_token,
openid
);
//发送请求
String userInfo = HttpClientUtils.get(userInfoUrl);
//获取返回userinfo字符串扫描人信息
HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
String nickname = (String)userInfoMap.get("nickname");//昵称
String headimgurl = (String)userInfoMap.get("headimgurl");//头像
member = new UcenterMember();
member.setOpenid(openid);
member.setNickname(nickname);
member.setAvatar(headimgurl);
memberService.save(member);
}
//使用jwt根据member对象生成token字符串
String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());
//最后:返回首页面,通过路径传递token字符串
return "redirect:http://localhost:3000?token="+jwtToken;
}catch(Exception e) {
throw new GuliException(20001,"登录失败");
}
}
//1 生成微信扫描二维码
@GetMapping("login")
public String getWxCode() {
//固定地址,后面拼接参数
//写法一(这么做效率很低,多了还容易写错):
// String url = "https://open.weixin.qq.com/connect/qrconnect" +
// "?appid="+
// ConstantWxUtils.WX_OPEN_APP_ID +
// "&redirect_uri=" +
// ConstantWxUtils.WX_OPEN_REDIRECT_URL +
// "&response_type=code" +
// "&scope=snsapi_login" +
// "state=atguigu" +
// "#wechat_redirect";
//当然,上面这个redirect_uri地址要先编码再进行传,不能那样直接传。
//写法二(推荐)
// 微信开放平台授权baseUrl %s 相当于?标识占位符
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
//对redirect_url进行URLEncoder编码
String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
try {
redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
}catch(Exception e) {
}
//设置%s里面值
String url = String.format(
baseUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
redirectUrl,
"atguigu"
);
//重定向到请求微信地址里面
return "redirect:" + url;
}
}
当然json转换工具你自己选择,fastjson也可以。比如:
//对比Gson和FastJson
//Gson
Gson gson = new Gson();
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String access_token = (String)mapAccessToken.get("access_token");
String openid = (String)mapAccessToken.get("openid");
//FastJson
Map parse = (Map) JSON.parse(accessTokenInfo);
String access_token1 = (String) parse.get("access_token");
System.out.println(access_token1 +"这是fastjson解析出来的");
我们这里怎么写都对,因为都是localhost,但是实际项目中,我们不建议这么做。