admin管理员组

文章数量:1539850

文章目录

  • 一、需求:微信扫码登录
    • 1、接口文档
    • 2、开发环境准备
    • 3、接入分析
    • 4、接口定义
    • 5、申请令牌
    • 6、查询用户信息
    • 7、保存用户信息

一、需求:微信扫码登录

和第三方对接的流程

1、接口文档

找到第三方的接口文档【微信扫码登录】。可以看到:微信扫码登录是基于OAuth2.0的:


阅读文档,接入微信扫码登录的流程是:

  • 给自己的网站申请AppID和AppSecret
  • 获取授权码code
请求url:
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE

传参:

  • 传入APPID等参数,访问获取code的地址,需要用户扫码确认授权【扫码登录】

  • 用户允许授权后,重定向到redirect_uri,并带上授权码code和state参数

redirect_uri?code=CODE&state=STATE
  • 用户拒绝授权,则不发生重定向
  • 获取code还可以直接前端内嵌二维码到自己的网站,而不用跳转到微信域确认后再返回
//此时需要前端引入JS文件
http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js

//然后将传参直接实例化到JS对象中
ar obj = new WxLogin({
 self_redirect:true,
 id:"login_container", 
 appid: "", 
 scope: "", 
 redirect_uri: "",
 state: "",
 style: "",
 href: ""
 });

  • 拿着授权码请求另一个接口获取access_token
调用API:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
# 传参
appid: 应用唯一标识(申请)
secret: 应用密钥(申请)
code: 上一步的授权码
grant_type: authorization_code
#返回
{ 
"access_token":"ACCESS_TOKEN", 
"expires_in":7200, 
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID", 
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

  • 调用接口获取用户信息
API: (GET)
https://api.weixin.qq/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID

# 返回

{
	openid: #普通用户的标识,对当前开发者帐号唯一
	nickname: #普通用户昵称
	sex: #普通用户性别,1为男性,2为女性
	province: #普通用户个人资料填写的省份
	city: #普通用户个人资料填写的城市
	country: #国家,如中国为CN
	headimgurl: #用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
	privilege: #用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
	unionid: #用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的。

}

2、开发环境准备

  • 注册账号 https://open.weixin.qq/
  • 添加网站应用,输入网站外网域名作为微信回调域名
  • 审核后拿到AppID和AppSecret
  • 开发环境在内网,微信回调指向公网域名。想让微信回调请求到我们自己的内网开发计算机上,需要做内网穿透

简单的说就是,外网不能直接访问内网的资源,可以让内网先和一个特定的外网IP建立隧道连接,这个外网IP再和其他的外网建立连接。这样就实现了外部IP能访问我们内网的环境

3、接入分析

时序图如下:

认证服务在这里需要做:

  • 定义接口接收微信下发的授权码
  • 收到授权码,调用微信接口申请令牌
  • 收到令牌,调用微信接口获取用户信息
  • 将用户信息写入自己网站的数据库(同时还有其他逻辑,如这个用户的角色也要set)
  • 最后重定向到浏览器自动登录

4、接口定义


注意这里,前端代码中已经定义了,用户授权后的重定向地址。

=====>

根据这个地址定义WxLoginController类,接收授权码:

@Slf4j
@Controller
public class WxLoginController {

    @RequestMapping("/wxLogin")
    public String wxLogin(String code, String state) throws IOException {
        log.debug("微信扫码回调,code:{},state:{}",code,state);
        //请求微信申请令牌
        //拿到令牌查询用户信息
        //将用户信息写入本项目数据库
        XcUser xcUser = new XcUser();
        //暂时硬编写,目的是调试环境,看能不能跳到下面的地址
        xcUser.setUsername("t1");
        if(xcUser==null){
            return "redirect:http://www.51xuecheng/error.html";
        }
        String username = xcUser.getUsername();
        return "redirect:http://www.51xuecheng/sign.html?username="+username+"&authType=wx";
    }
}

接Day9内容,一个认证接口,写不同的实现类。定义微信认证方式的实现类:

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @description 微信扫码认证
 */
@Slf4j
@Service("wx_authservice")
public class WxAuthServiceImpl implements AuthService {

    @Autowired
    XcUserMapper xcUserMapper;

    @Override
    public XcUserExt execute(AuthParamsDto authParamsDto) {

        //账号
        String username = authParamsDto.getUsername();
        XcUser user = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, username));
        if(user==null){
            //返回空表示用户不存在
            throw new RuntimeException("账号不存在");
        }
        XcUserExt xcUserExt = new XcUserExt();
        BeanUtils.copyProperties(user,xcUserExt);
        return xcUserExt;
    }
}

中途测试一下,这时扫码授权后要能进入到/wxLogin接口,然后跳转到http://www.51xuecheng/sign.html?username=t1&authType=wx

边写边调,可以写死点数据,硬编码,最后统一测有问题修改代价太大。

5、申请令牌

请求第三方的服务,使用restTemplate,在启动类中配置RestTemple的bean

@Bean
   RestTemplate restTemplate(){
       RestTemplate restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
       return  restTemplate;
   }

定义微信认证的service接口:

public interface WxAuthService {

    public XcUser wxAuth(String code);

}

让之前的WxAuthServiceImpl类也实现WxAuthService 接口

@Slf4j
@Service("wx_authservice")
public class WxAuthServiceImpl implements AuthService, WxAuthService {
	@Autowired
	XcUserMapper xcUserMapper;
	
	@Autowired
	RestTemplate restTemplate;  //注入远程调用的bean
	
	@Value("${weixin.appid}")   //appid和密钥都写在配置文件中
	String appid;
	@Value("${weixin.secret}")  //使用value注解+刀乐儿大括号赋值
	String secret;
	
	public XcUser wxAuth(String code){
	
	    //收到code调用微信接口申请access_token
	    Map<String, String> access_token_map = getAccess_token(code);
	    if(access_token_map==null){
	        return null;
	    }
	    //获取用户信息
	   
	    //添加用户到数据库
	    XcUser xcUser = null;
	   
	    return xcUser;
	}
	
	
	/**
	 * 定义申请访问令牌的方法
	 * 响应示例:
	 {
	 "access_token":"ACCESS_TOKEN",
	 "expires_in":7200,
	 "refresh_token":"REFRESH_TOKEN",
	 "openid":"OPENID",
	 "scope":"SCOPE",
	 "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
	 }
	*/
	private Map<String,String> getAccess_token(String code) {
	
	    String wxUrl_template = "https://api.weixin.qq/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
	    //请求微信地址
	    String wxUrl = String.format(wxUrl_template, appid, secret, code);
	
	    log.info("调用微信接口申请access_token, url:{}", wxUrl);
	
	    ResponseEntity<String> exchange = restTemplate.exchange(wxUrl, HttpMethod.POST, null, String.class);
	
	    String result = exchange.getBody();
	    log.info("调用微信接口申请access_token: 返回值:{}", result);
	    Map<String,String> resultMap = JSON.parseObject(result, Map.class);
	
	    return resultMap;
}

这种URL,可以现在String中用%s占位,后续再String.format方法传参,很像log.debug中的花括号{}占位

6、查询用户信息

private Map<String,String> getUserinfo(String access_token,String openid) {

    String wxUrl_template = "https://api.weixin.qq/sns/userinfo?access_token=%s&openid=%s";
    //请求微信地址
    String wxUrl = String.format(wxUrl_template, access_token,openid);

    log.info("调用微信接口申请access_token, url:{}", wxUrl);

    ResponseEntity<String> exchange = restTemplate.exchange(wxUrl, HttpMethod.POST, null, String.class);

    //防止乱码进行转码
    String result = new     String(exchange.getBody().getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8);
    log.info("调用微信接口申请access_token: 返回值:{}", result);
    Map<String,String> resultMap = JSON.parseObject(result, Map.class);

    return resultMap;
}

7、保存用户信息

@Autowired
XcUserRoleMapper xcUserRoleMapper;

@Transactional
public XcUser addWxUser(Map userInfo_map){
    String unionid = userInfo_map.get("unionid").toString();
    //根据unionid查询数据库
    XcUser xcUser = xcUserMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getWxUnionid, unionid));
    if(xcUser!=null){
        return xcUser;
    }
    String userId = UUID.randomUUID().toString();
    xcUser = new XcUser();
    xcUser.setId(userId);
    xcUser.setWxUnionid(unionid);
    //记录从微信得到的昵称
    xcUser.setNickname(userInfo_map.get("nickname").toString());
    xcUser.setUserpic(userInfo_map.get("headimgurl").toString());
    xcUser.setName(userInfo_map.get("nickname").toString());
    xcUser.setUsername(unionid);
    xcUser.setPassword(unionid);
    xcUser.setUtype("101001");//学生类型
    xcUser.setStatus("1");//用户状态
    xcUser.setCreateTime(LocalDateTime.now());
    xcUserMapper.insert(xcUser);
    XcUserRole xcUserRole = new XcUserRole();
    xcUserRole.setId(UUID.randomUUID().toString());
    xcUserRole.setUserId(userId);
    xcUserRole.setRoleId("17");//学生角色
    xcUserRoleMapper.insert(xcUserRole);
    return xcUser;
}

写完这些方法,最后完善wxAuth方法:

@Autowired
WxAuthServiceImpl currentProxy;

public XcUser wxAuth(String code){

    //收到code调用微信接口申请access_token
    Map<String, String> access_token_map = getAccess_token(code);
    if(access_token_map==null){
        return null;
    }
    System.out.println(access_token_map);
    String openid = access_token_map.get("openid");
    String access_token = access_token_map.get("access_token");
    //拿access_token查询用户信息
    Map<String, String> userinfo = getUserinfo(access_token, openid);
    if(userinfo==null){
        return null;
    }
    //将用户信息保存到数据库
    XcUser xcUser = currentProxy.addWxUser(userinfo);
    return xcUser;
}

最后完善/wxLogin接口

@Slf4j
@Controller //返回一个String,别RestController
public class WxLoginController {

    @Autowired
    WxAuthService wxAuthService;

    @RequestMapping("/wxLogin")
    public String wxLogin(String code, String state) throws IOException {
        log.debug("微信扫码回调,code:{},state:{}",code,state);
        //请求微信申请令牌,拿到令牌查询用户信息,将用户信息写入本项目数据库
        XcUser xcUser = wxAuthService.wxAuth(code);
        if(xcUser==null){
            return "redirect:http://www.51xuecheng/error.html";
        }
        String username = xcUser.getUsername();
        return "redirect:http://www.51xuecheng/sign.html?username="+username+"&authType=wx";
    }
}

最后梳理下整个流程:

  • 前端嵌入微信二维码到网站登录页
  • 网页中实例化js对象,传入appid等参数 ,并指定授权后重定向后到Auth服务的/wxLogin入口
  • /wxLogin接口收到的请求参数是授权码
  • /wxLogin接口调用wxAuth方法
  • 在wxAuth方法中,远程调用微信的API,申请令牌、查询用户、同步用户信息
  • 最后接口return一个重定向地址,携带用户名和authType到我们网站的登录页面,走统一认证
  • 到自定义的UserServiceImpl(UserDetailsService)中的loadUserByUsername,根据authType参数,走WxAuthServiceImpl中的execute方法
  • WxAuthServiceImpl中的execute方法只校验现在数据库中有没有这个用户,有则返回,自动登录成功

本文标签: 第三方平台微信扫码