7

前言

实际开发中,使用第三方登录是非常常见的业务。

比如很多微信的网页中,就会有网页向用户申请使用权限。

image.png

通过实现网页授权并获取用户基本信息是一种比较好的选择。

比如如下就是申请微信授权,获取微信用户的openId

正文

微信官方公众号文档

准备

  1. 准备公众号和测试环境
    微信公众平台接口测试帐号申请
  2. 网页授权获取用户基本信息配置
    申请完测试账号后, 会获得一个appId和appSecret, 随后会用到
    image.png
    页面下方有接口权限,设置网页回调地址
    image.png
    不要带http,此处地址填写本地ip即可。
    image.png
    到此公众号配置已经完成

实现网页授权(微信登陆)

简易流程图:

image.png

第一步:用户同意授权,获取code

在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(一种是需要用户授权,可以获取昵称、头像、openId, 一种是不需要授权, 只能获取openId),引导关注者打开如下页面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

这个地址需要 appid , redirect_uri, response_type, scope 等几个参数。

而这个地址就给交给后台构建,前台负责访问该地址。

而微信授权回调后,就会设置 header的 location 属性 为 redirect_uri?code = xxxxx。 使得我们会跳转到这个地址

例如, 我填写的redirect_uri 为 http://192.168.3.234:8009/oauth。 微信根据这个回调地址进行携带code回跳。

跳转前:
image.png

跳转后
image.png

具体代码

这里引用了weixin-java-mp提供的包, 可以很便捷地与微信请求,

<dependency>
      <groupId>com.github.binarywang</groupId>
      <artifactId>weixin-java-mp</artifactId>
      <version>4.4.0</version>
    </dependency>

这里需要将appId和appsecret配置给wxMpService,配置过程具体可以看该包的文档,简单来说就是继承WxMpServiceImpl类,并调用setWxMpConfigStorage方法设置config

@Service
public class WeChatMpServiceImpl extends WxMpServiceImpl {

  @Autowired
  private WxMpService wxMpService;

  @Autowired
  private WxMpConfig wxMpConfig;

  @PostConstruct
  public void init() {
    final WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl();
    // 设置微信公众号的appId
    config.setAppId(this.wxMpConfig.getAppid());
    // 设置微信公众号的app corpSecret
    config.setSecret(this.wxMpConfig.getAppSecret());
    // 设置微信公众号的token
    config.setToken(this.wxMpConfig.getToken());
    // 设置消息加解密密钥
    config.setAesKey(this.wxMpConfig.getAesKey());
    super.setWxMpConfigStorage(config);
  }

如下的buildAuthorizationUrl方法可以很快速的生成授权连接的地址

@Autowired
  private WxMpService wxMpService;

/**
   * 构建网页授权链接
   *
   * @return
   */
  public String getOauthUrl() {
    return wxMpService.getOAuth2Service().buildAuthorizationUrl(this.wxMpConfig.getRedirectUri(), WxConsts.OAuth2Scope.SNSAPI_BASE, null);
  }

第二步:通过code换取网页授权access_token

这时候,微信已经回调到我们的redirect_uri, 并且前端可以从 url 的参数中获取到code
image.png

这时候我们就可以用code向后台登录
(这里在向后台请求的header中传或者在param中传都可以)

ts code:

/**
   * 使用code登录
   * @param code
   */
  loginWithCode(code: string): Observable<HttpResponse<WechatUser>> {
    Assert.isString(code, "code must be string")
    let headers = new HttpHeaders();
    // 添加code作为 header认证
    headers = headers.append('wechat-code', code)
    return this.httpClient.get<WechatUser>('wechat/login', {
      headers,
      observe: 'response'
    });
  }

后台:

通过getAccessToken(code)方法可以获取到 accessToken,此时已经可以获取到用户的OpenId

WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
String openId = accessToken.getOpenId();

当前需求获取到openId已足够, 若想要获取到用户信息(昵称、头像等)则需要再用accessToken去获取 用户信息

// 获取用户信息
wxMpUser = wxMpService.getUserService().userInfo(accessToken.getOpenId());
log.info("wxMpUser={}", JSONUtil.toJsonStr(wxMpUser));

此时就可以根据用户唯一的OpenId去登录了。具体看各个项目的实现。

image.png

当前项目是定义一个过滤器,用spring security来登录, 设置安全上下文的认证。

@Component
public class WechatAuthFilter extends OncePerRequestFilter {
  private final Logger logger = LoggerFactory.getLogger(this.getClass());
  /**
   * 微信第一次认证需要的code信息
   */
  public static final String codeKey = "wechat-code";
  private final WeChatMpServiceImpl weChatMpService;

  public WechatAuthFilter(WeChatMpServiceImpl weChatMpService) {
    this.weChatMpService = weChatMpService;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    String code = request.getHeader(codeKey);
    if (code != null) {
      try {
        WeChatUser wechatUser = this.weChatMpService.oauth(code);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken;
        // 设置认证用户:微信用户、安全令牌设置为openid、认证权限为空(后期可变更为正确的微信权限名称)
        usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
            wechatUser,
            wechatUser.getOpenid(),
            wechatUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
      } catch (WxErrorException exception) {
        this.logger.warn("虽然接收到了code,但是没有通过code换取有效的微信数据: " + exception.getMessage());
        exception.printStackTrace();
      }
    }

    filterChain.doFilter(request, response);
  }
}

weiweiyi
1k 声望123 粉丝