谷粒商城-高级-59 -商城业务-认证服务-OAuth2.0 及社交登录

一、OAuth2.0简介

OAuth 2.0是一个应用之间彼此访问数据的开源授权协议。该指南的目标是提供一个OAuth 2.0的很容易理解的概述。

file

1、使用Code换取AccessToken,Code只能用一次
2、同一个用户的accessToken一段时间是不会变化的,即使多次获取

二、微博授权登录

新浪微博开放平台

点击微博登录跳转页:

https://api.weibo.com/oauth2/authorize?client_id=2077705774&response_type=code&redirect_uri=http://auth.gulimall.com/oauth2.0/weibo/success###

该页面扫码,可以获取到code:
file

扫码成功后,会带参数Code到本地服务获取accessToken:

http://auth.gulimall.com/oauth2.0/weibo/success?code=412c169a1e47f3b3f292d299ffbd4d7e

调用授权接口返回的结果:

{"access_token":"2.00Ry49DC9LqbQCb9cb0e7037dRRo8E","remind_in":"142942","expires_in":142942,"uid":"1883360005","isRealName":"true"}

获取用户详情:

https://api.weibo.com/2/users/show.json?access_token=2.00Ry49DC9LqbQCb9cb0e7037dRRo8E&uid=1883360005

逻辑处理代码:
gulimall-auth-server/xxx/auth/controller/OAuth2Controller.java

package com.atguigu.gulimall.auth.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.HttpUtils;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberResponseVo;
import com.atguigu.gulimall.auth.feign.MemberFeignService;
import com.atguigu.gulimall.auth.vo.SocialUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

import static com.atguigu.common.constant.AuthServerConstant.LOGIN_USER;

/**
 * @author: kaiyi
 * @create: 2020-09-09 10:21
 */
@Slf4j
@Controller
public class OAuth2Controller {

  @Autowired
  MemberFeignService memberFeignService;

/**
*
* 这里接收到的参数时k v,所以,不需要@RequestBody接收,如果是Json,则需要@RequestBody注解
*
*/
  @GetMapping(value = "/oauth2.0/weibo/success")
  public String weibo(@RequestParam("code") String code, HttpSession session) throws Exception {

    // 1、封装请求参数
    Map<String, String> map = new HashMap<>();
    map.put("client_id","2077705774");
    map.put("client_secret","40af02bd1c7e435ba6a6e9cd3bf799fd");
    map.put("grant_type","authorization_code");
    map.put("redirect_uri","http://auth.gulimall.com/oauth2.0/weibo/success");
    map.put("code",code);

    // 2、根据Code获取AccessToken
    HttpResponse response = HttpUtils
        .doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<>(), map, new HashMap<>());

    // 3、处理
    if(response.getStatusLine().getStatusCode() == 200) {
      // 获取到了Token
      String json = EntityUtils.toString(response.getEntity());
      SocialUser socialUser = JSON.parseObject(json, SocialUser.class);

      //知道了哪个社交用户
      //1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)
      //登录或者注册这个社交用户
      System.out.println(socialUser.getAccess_token());

      //调用远程服务
      R oauthLogin = memberFeignService.oauthLogin(socialUser);

      if (oauthLogin.getCode() == 0) {
        MemberResponseVo data = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
        log.info("登录成功:用户信息:{}",data.toString());

        //1、第一次使用session,命令浏览器保存卡号,JSESSIONID这个cookie
        //以后浏览器访问哪个网站就会带上这个网站的cookie
        //TODO 1、默认发的令牌。当前域(解决子域session共享问题)
        //TODO 2、使用JSON的序列化方式来序列化对象到Redis中
        session.setAttribute(LOGIN_USER, data);

        //2、登录成功跳回首页
        return "redirect:http://gulimall.com";
      } else {

        return "redirect:http://auth.gulimall.com/login.html";
      }

    }else{

      return "redirect:http://auth.gulimall.com/login.html";
    }

  }
}

三、登录回调及测试

认证微服务远程调用会员微服务进行数据校验和持久化:
com/atguigu/gulimall/auth/feign/MemberFeignService.java

package com.atguigu.gulimall.auth.feign;

import com.atguigu.common.utils.R;
import com.atguigu.gulimall.auth.vo.SocialUser;
import com.atguigu.gulimall.auth.vo.UserLoginVo;
import com.atguigu.gulimall.auth.vo.UserRegistVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author: kaiyi
 * @create: 2020-09-08 16:20
 */
@FeignClient("gulimall-member")
public interface MemberFeignService {

  @PostMapping(value="/member/member/register")
  R register(@RequestBody UserRegistVo vo);

  @PostMapping(value = "/member/member/login")
  R login(@RequestBody UserLoginVo vo);

  @PostMapping(value = "/member/member/oauth2/login")
  R oauthLogin(@RequestBody SocialUser socialUser) throws Exception;

}

会员微服务:
gulimall-member/xxx/member/controller/MemberController.java

    @PostMapping(value = "/oauth2/login")
    public R oauthLogin(@RequestBody SocialUser socialUser) throws Exception {

        MemberEntity memberEntity = memberService.login(socialUser);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
        }
    }

业务实现类:
gulimall-member/xxx/member/service/impl/MemberServiceImpl.java

 @Override
    public MemberEntity login(SocialUser socialUser) throws Exception {

        //具有登录和注册逻辑
        String uid = socialUser.getUid();

        //1、判断当前社交用户是否已经登录过系统
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));

        if (memberEntity != null) {
            //这个用户已经注册过
            //更新用户的访问令牌的时间和access_token
            MemberEntity update = new MemberEntity();
            update.setId(memberEntity.getId());
            update.setAccessToken(socialUser.getAccess_token());
            update.setExpiresIn(socialUser.getExpires_in());
            this.baseMapper.updateById(update);

            memberEntity.setAccessToken(socialUser.getAccess_token());
            memberEntity.setExpiresIn(socialUser.getExpires_in());
            return memberEntity;
        } else {
            //2、没有查到当前社交用户对应的记录我们就需要注册一个
            MemberEntity register = new MemberEntity();
            //3、查询当前社交用户的社交账号信息(昵称、性别等)
            Map<String,String> query = new HashMap<>();
            query.put("access_token",socialUser.getAccess_token());
            query.put("uid",socialUser.getUid());
            HttpResponse response = HttpUtils
                .doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<String, String>(), query);

            if (response.getStatusLine().getStatusCode() == 200) {
                //查询成功
                String json = EntityUtils.toString(response.getEntity());
                JSONObject jsonObject = JSON.parseObject(json);
                String name = jsonObject.getString("name");
                String gender = jsonObject.getString("gender");
                String profileImageUrl = jsonObject.getString("profile_image_url");

                register.setNickname(name);
                register.setGender("m".equals(gender)?1:0);
                register.setHeader(profileImageUrl);
                register.setCreateTime(new Date());
                register.setSocialUid(socialUser.getUid());
                register.setAccessToken(socialUser.getAccess_token());
                register.setExpiresIn(socialUser.getExpires_in());

                //把用户信息插入到数据库中
                this.baseMapper.insert(register);

            }
            return register;
        }

    }

为者常成,行者常至