Day423.Oauth2&分布式Session -谷粒商城

一、Oauth2

1、介绍

  • OAuth: :

    • OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
  • OAuth2.0

    • 对于用户相关的 OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。

授权流程图示

Day423.Oauth2&分布式Session -谷粒商城


2、接入步骤

以微博为例 官方地址

  • 引导用户到微博的认证地址

    • https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
      • client_id:微博网站接入提供的 APP KEY;
      • redirect_uri:认证后重定向的地址;
  • 用户同意授权重定向到上面设置的地址并携带 code

    • http://www.achangmall.com/success?code=CODE
  • 使用 code 请求微博提供的地址换取 access_token

    • https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE

      • client_id:APP KEY;

      • client_secret:APP SECRET;

      • redirect_uri:认证后重定向的地址 http://www.achangmall.com/success

      • code:第二步返回的 code 值;

  • 返回响应报文

    {
        "access_token": "2.00pDpxyGd3J5bEef6b98778e0ZKsu4",
        "remind_in": "157679999",
        "expires_in": 157679999,
        "uid": "6397634785",
        "isRealName": "true"
    }
    
  • 根据 access_token 可以获取微博提供的公共接口数据

Day423.Oauth2&分布式Session -谷粒商城


3、代码举例

  • com.achang.achangmall.auth.vo.SocialUser
@Data
public class SocialUser {
    private String access_token;
    private String remind_in;
    private long expires_in;
    private String uid;
    private String isRealName;
}
  • com.achang.achangmall.auth.controller.OAuth2Controller
@Slf4j
@Controller	
public class OAuth2Controller {

    @Autowired
    private MemberFeignService memberFeignService;

    @GetMapping(value = "/oauth2.0/weibo/success")
    public String weibo(@RequestParam("code") String code, HttpSession session) throws Exception {

        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.achangmall.com/oauth2.0/weibo/success");
        map.put("code",code);

        //1、根据用户授权返回的code换取access_token
        HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", new HashMap<>(), map, new HashMap<>());

        //2、处理
        if (response.getStatusLine().getStatusCode() == 200) {
            //获取到了access_token,转为通用社交登录对象
            String json = EntityUtils.toString(response.getEntity());
            //String json = JSON.toJSONString(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("userInfo",data);

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

}
  • com.achang.achangmall.member.entity.MemberEntity
/**
	 * 社交登录UID
	 */
private String socialUid;

/**
	 * 社交登录TOKEN
	 */
private String accessToken;

/**
	 * 社交登录过期时间
	 */
private long expiresIn;
  • com.achang.achangmall.member.controller.MemberController
@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();
    }
}
  • com.achang.achangmall.member.service.impl.MemberServiceImpl
@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;
    }

}

二、分布式Session

1、Session共享问题

  • Session原理

Day423.Oauth2&分布式Session -谷粒商城


  • 分布式下Session共享问题

Day423.Oauth2&分布式Session -谷粒商城


2、Session共享问题解决方案

  • Session复制

Day423.Oauth2&分布式Session -谷粒商城

  • 客户端存储

Day423.Oauth2&分布式Session -谷粒商城

  • Hash一致性

通过对请求ip进行hash后获取对应值,根据值来让这个ip访问某个服务的时候,都对一个服务进行访问,保证都在一个服务的session
Day423.Oauth2&分布式Session -谷粒商城

  • 统一存储

Day423.Oauth2&分布式Session -谷粒商城

  • 子域名Session共享

Day423.Oauth2&分布式Session -谷粒商城

当浏览器请求服务之后,服务会返回浏览器一个卡信息,并指定放大域名,我们并将Session信息存储到redis中,下次,让浏览器请求我们所有的服务的时候都携带卡,每个服务都去redis去获取对应这个卡的Session信息,这样子就可以解决服务之间共享Session的问题

接下来我们通过SpringSession 来实现redis+session共享问题


上一篇:分布式微服务企业快速架构之SpringCloud分布式、微服务、云架构介绍


下一篇:Spring Security OAuth2 - 自定义 OAuth2.0 令牌发放接口地址