0
点赞
收藏
分享

微信扫一扫

Spring boot + Vue 微信第三方登录实践

岛上码农 2021-09-25 阅读 84

1. 流程图

微信登录流程如下:

  1. 点击微信登录,新开窗口,跳转到微信扫码页面
  2. 用户扫码登录,新开窗口跳转到后台微信登录接口
  3. 后台判断用户是否成功登录,将新开窗口重定向到前台微信登录处理页面
  4. 微信登录处理页面会关闭新开窗口,判断用户是否成功登录,如果用户成功登录,则写入cookie并且跳转首页,如果用户没有注册用户,则携带后端的token跳转注册页面

2. 前端代码核心逻辑实现

对于前端来说,主要

第一步 编写前端第三方登录处理类

import URL from 'url-parse'
export default class OauthHandler {
  constructor(name, thirdBaseUrl, appId) {
    // 当前第三方登录名称
    this.name = name
    // 第三方登录跳转URL
    this.thirdBaseUrl = thirdBaseUrl
    // 第三方登录APPID
    this.appId = appId
    this.newPage = null
  }
  login() {
    const referrer = (window.globalPortalVue.$route.query && window.globalPortalVue.$route.query.referrer) || '/'
    localStorage.setItem(constant['COMMON|OAUTH'].referrerLocalName, referrer)
    localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].login)
    this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].login)
  }
  bind() {
    localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].bind)
    this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].bind)
  }
  unBind() {
    localStorage.setItem(constant['COMMON|OAUTH'].handlerLocalName, constant['COMMON|OAUTH_HANDLER'].unbind)
    this.openThirdPage(constant['COMMON|OAUTH_HANDLER'].unbind)
  }
  async genThirdUrl(type) {
    const thirdUrl = new URL(this.thirdBaseUrl)
    return thirdUrl
  }
  getThirdPageSize() {
    return {
      width: 500,
      height: 500
    }
  }

  getType() {
    throw new Error('请覆写此方法')
  }
  async openThirdPage(type) {
    const thirdUrl = await this.genThirdUrl(type)
    const { width, height } = this.getThirdPageSize()
    this.unMountWindowOauthFunction()
    this.mountWindowOauthFunction()
    const windowHeight = window.screen.availHeight
    const windowWidth = window.screen.availWidth
    console.log(this.newPage)
    if (this.newPage != null && !this.newPage.closed) {
      return
    }
    this.newPage = window.open(
      thirdUrl.toString(),
      '_blank',
      `toolbar=no,directories=no,status=no,menubar=no,height=${height},width=${width},top=${(windowHeight - height) / 2},left=${(windowWidth - width) / 2}`
    )
    if (this.newPage) {
      return
    }
    window.open(
      thirdUrl.toString(),
      '_self'
    )
  }
  unMountWindowOauthFunction() {
    window.globalPortalVue.$off('oauthCallBack')
  }
  mountWindowOauthFunction() {
    console.log('监听事件')
    window.globalPortalVue.$on('oauthCallBack', (result) => {
      console.log('监听到事件')
      if (result.code === constant['COMMON|CODE'].success) {
        const name = localStorage.getItem(constant['COMMON|OAUTH'].handlerLocalName)
        this.successHandler(name, result)
      } else {
        this.errorHandler(result)
      }
    })
  }
  successHandler(name) {
    console.log('成功')
    // 登录
    if (name === constant['COMMON|OAUTH_HANDLER'].login) {
      new Promise((resolve, reject) => {
        event.emit('user/oauthLogin', resolve, reject)
      }).then(() => {
        new Promise((resolve, reject) => {
          event.emit('user/setUserEnterpriseList', resolve, reject)
        }).then(enterpriseList => {
          loginSuccessRedirect(enterpriseList)
        }).catch(err => {
          console.error(err.message)
          window.globalPortalVue.$Message.error(this.name + '登录失败!')
        })
      }).catch((err) => {
        console.error(err.message)
        window.globalPortalVue.$Message.error(this.name + '微信登录失败!')
      })
      return
    }
    // 绑定
    if (name === constant['COMMON|OAUTH_HANDLER'].bind) {
      new Promise((resolve, reject) => {
        event.emit('user/updateUserInfo', resolve, reject)
      }).then(re => {
        window.globalPortalVue.$Message.success('绑定' + this.name + '成功!')
      }).catch(err => {
        console.error(err)
        window.globalPortalVue.$Message.error('绑定' + this.name + '失败,请重试!')
      })
    }
    // 解除绑定
    if (name === constant['COMMON|OAUTH_HANDLER'].unbind) {
      new Promise((resolve, reject) => {
        event.emit('user/updateUserInfo', resolve, reject)
      }).then(re => {
        window.globalPortalVue.$Message.success('解绑' + this.name + '成功!')
      }).catch(err => {
        console.error(err)
        window.globalPortalVue.$Message.error('解绑' + this.name + '失败,请重试!')
      })
    }
  }
  errorHandler(result) {
    console.log('授权失败', result)
    if (result.code === constant['COMMON|CODE'].error) {
      window.globalPortalVue.$Message.error(result.message)
      return
    }
    if (result.code === constant['COMMON|CODE'].userNotFound) {
      window.globalPortalVue.$router.push({
        path: '/oauthLogin',
        query: {
          token: result.token,
          type: this.getType()
        }
      })
    }
  }
}

第三步 实现后台微信登录处理接口

实现微信登录Controller

@RequestMapping("/wechatOpenPlatform")
public class WechatOpenPlatformController{
    @Autowired
    WechatOauthService wechatOauthService;

    @GetMapping("login")
    @ApiOperation(value = "微信网页登录", notes = "微信网页登录")
    public void login(@PathVariable String region, @NotNull(message = "微信登录参数错误") String code, HttpServletResponse response) {
        wechatOauthService.wechatLogin(code, response);
    }
}

实现微信登录service实现

public interface WechatOauthService {

    /**
     * 微信网页登录
     * 
     * @param oauth2Handler
     * @param callbackUrl
     * @param authTokenForm
     * @param response
     */
    void wechatLogin(String code,
        HttpServletResponse response);

}
@Service
public class WechatOauthServiceImpl implements WechatOauthService {
  private static String callBackUrl = "http://www.xxxx.com/#/wechat/callback";
  private static String openAppId= "xxxxx";
  private static String openSecret= "xxxx";

  @Autowired
  UserService userService;

  @Autowired
  AutowireCapableBeanFactory autowireCapableBeanFactory;

  @Override
  public void wechatLogin (String code, HttpServletResponse response) {
    WechatHandler wechatHandler= new WechatHandler(openAppId, openSecret);
    // 实现容器外bean注入
    autowireCapableBeanFactory.autowireBean(wechatHandler);
    // 获取用户token
    AuthTokenVo authTokenVo = wechatHandler.fetchToken(code);
    // 根据token获取用户信息
    WechatUserVo wechatUserVo = wechatHandler.fetchUserInfo(authTokenVo);
    // 根据unionId判断用户是否存在
    UserMo userMo = userService.selectUserByUnionId(wechatUserVo.getUnionId());
    if (userMo == null) {
    // 登录失败
    response.sendRedirect(callBackUrl + "?code=500&token=" + wechatUserVo.getUnionId());
    }
    // 调用登录方法,写入cookie
    userService.login(userMo);
    // 登录成功
    response.sendRedirect(callBackUrl + "?code=200");
  }
}

// 调用微信接口获取微信信息处理类

public class WechatHandler {

   /**
     * 应用唯一标识
     */
    private String appId;

    /**
     * 应用密钥
     */
    private String secret;

    /**
     * 请求方法
     */
    @Resource
    RestTemplate restTemplate;

    public WechatHandler(String appId, String secret) {
        this.appId = appId;
        this.secret = secret;
    }

    private final static String BASE_URL = "https://api.weixin.qq.com";

    private final static String FETCH_TOKEN_GRANT_TYPE = "authorization_code";

    private final static String TOKEN_URL =
        BASE_URL + "/sns/oauth2/access_token?appid={appId}&secret={secret}&code={code}&grant_type={grantType}";

    private final static String USER_INFO_URL = BASE_URL + "/sns/userinfo?access_token={accessToken}&openid={openId}";

    @Override
    public AuthTokenVo fetchToken(AuthTokenForm authTokenForm) {
        Map<String, String> query = new HashMap<>();
        query.put("appId", getAppId());
        query.put("secret", getSecret());
        query.put("code", authTokenForm.getCode());
        query.put("grantType", FETCH_TOKEN_GRANT_TYPE);
        String result = restTemplate.getForObject(TOKEN_URL, String.class, query);
        JSONObject jsonObject = JSON.parseObject(result);
        if (jsonObject.getInteger("errcode") != null) {
            throw new BusinessException("获取TOKEN失败");
        }
        AuthTokenVo authTokenVo = new AuthTokenVo(jsonObject);
        return authTokenVo;
    }

    @Override
    public AuthTokenVo refreshToken(AuthRefreshTokenForm authRefreshTokenForm) {
        return null;
    }

    @Override
    public Object fetchUserInfo(AuthTokenVo authTokenVo) {
        Map<String, String> query = new HashMap<>();
        query.put("accessToken", authTokenVo.getAccessToken());
        query.put("openId", authTokenVo.getOpenId());
        String result = restTemplate.getForObject(USER_INFO_URL, String.class, query);
        JSONObject jsonObject = JSON.parseObject(result);
        if (jsonObject.getInteger("errcode") != null) {
            throw new BusinessException("获取用户信息失败");
        }
        WechatUserVo wechatUserVo = new WechatUserVo(jsonObject);
        return wechatUserVo;
    }

}

微信用户实体类

@Data
@NoArgsConstructor
public class WechatUserVo {

    /**
     * OPEN_ID
     */
    private String openId;

    /**
     * 唯一标识
     */
    private String unionId;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 性别
     */
    private Integer sex;

    /**
     * 省份
     */
    private String province;

    /**
     * 城市
     */
    private String city;

    /**
     * 国家
     */
    private String country;

    /**
     * 头像
     */
    private String headImgUrl;

    /**
     * 特权信息
     */
    private String privilege;

    public WechatUserVo(JSONObject jsonObject) {
        this.unionId = jsonObject.getString("unionid");
        this.openId = jsonObject.getString("openid");
        this.nickName = jsonObject.getString("nickname");
        this.sex = jsonObject.getInteger("sex");
        this.province = jsonObject.getString("province");
        this.city = jsonObject.getString("city");
        this.country = jsonObject.getString("country");
        this.headImgUrl = jsonObject.getString("headimgurl");
        this.privilege = jsonObject.getString("privilege");
    }
}

认证token实体类

@Data
@NoArgsConstructor
public class AuthTokenVo {

    /**
     * 用户接口调用凭证
     */
    private String accessToken;

    /**
     * token过期时间
     */
    private String expiresIn;

    /**
     * 刷新token凭证
     */
    private String refreshToken;

    /**
     * 用户标识
     */
    private String openId;

    /**
     * 授权作用域
     */
    private String scope;

    public AuthTokenVo(JSONObject jsonObject) {
        this.accessToken = jsonObject.getString("access_token");
        this.expiresIn = jsonObject.getString("expires_in");
        this.refreshToken = jsonObject.getString("refresh_token");
        this.openId = jsonObject.getString("openid");
        this.scope = jsonObject.getString("scope");
    }
}

微信token请求表单类

@Data
public class AuthTokenForm {

    /**
     * 用户CODE
     */
    private String code;

}

举报

相关推荐

0 条评论