谷粒商城 高级篇 (十六) --------- 社交登录


前言

QQ、微博、github 等网站的用户量非常大,别的网站为了简化自我网站的登陆与注册逻辑,引入社交登陆功能,步骤:

1、用户点击 QQ 按钮。
2、引导跳转到 QQ 授权页。
3、用户主动点击授权,跳回之前网页。

在这里插入图片描述

在这里插入图片描述


一、OAuth 2.0

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

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

  • 官方版流程:

在这里插入图片描述
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

社交登录流程:

在这里插入图片描述

在这里插入图片描述

二、微博登陆准备工作

1、进入微博开放平台

在这里插入图片描述
2、登陆微博,进入微连接,选择网站接入

在这里插入图片描述

3、选择立即接入

在这里插入图片描述

4、创建自己的应用

在这里插入图片描述
5、我们可以在开发阶段进行测试了

在这里插入图片描述
记住自己的 app key 和 app secret 我们一会儿用

6、进入高级信息,填写授权回调页的地址

在这里插入图片描述
7、添加测试账号

在这里插入图片描述
8、进入文档,按照流程测试社交登陆

在这里插入图片描述
在这里插入图片描述

三、整合微博登录

1、引导用户到如下地址

https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI

前端中进行编写:

<a href="https://api.weibo.com/oauth2/authorize?client_id=2636917288&response_type=code&redirect_uri=http://auth.gulimall.com/oauth2.0/weibo/success">
    <img style="width: 50px;height: 18px" src="/static/login/JD_img/weibo.png"/>
</a>

在这里插入图片描述

2. 授权登录后进入回调函数

在这里插入图片描述
然后在登录时,我们将登录信息保存到了 session 中,但是在分布式架构下,不同服务之间涉及到 session 不同步问题,我们需要在此处提出 session 共享的解决方案。。。

首先我们来看 session原理:
在这里插入图片描述
分布式下的 session 共享问题:

在这里插入图片描述

3. Session共享问题解决

① session复制

  • 优点
    • web-server(Tomcat)原生支持,只需要修改配置文件
  • 缺点
    • session同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力
    • 任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平扩展更多的web-server
    • 大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取。

在这里插入图片描述

② 客户端存储

  • 优点
    • 服务器不需存储session,用户保存自己的session信息到cookie中。节省服务端资源。
  • 缺点
    • 都是缺点,这只是一种思路。• 具体如下:
    • 每次http请求,携带用户在cookie中的完整信息,浪费网络带宽。
    • session数据放在cookie中,cookie有长度限制4K,不能保存大量信息• session数据放在cookie中,存在泄漏、篡改、窃取等安全隐患。
    • 这种方式不会使用。

在这里插入图片描述

③ hash一致性

  • 优点:
    • 只需要改nginx配置,不需要修改应用代码
    • 负载均衡,只要hash属性的值分布是均匀的,多台web-server的负载是均衡的
    • 可以支持web-server水平扩展(session同步法是不行的,受内存限制)
  • 缺点
    • session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录
    • 如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
    • 但是以上缺点问题也不是很大,因为session本来都是有有效期的。所以这两种反向代理的方式可以使用

在这里插入图片描述

④ 统一存储

  • 优点:
    • 没有安全隐患
    • 可以水平扩展,数据库/缓存水平切分即可
    • web-server重启或者扩容都不会有session丢失
  • 不足
    • 增加了一次网络调用,并且需要修改应用代码;如将所有的getSession方法替换为从Redis查数据的方式。redis获取数据比内存慢很多
    • 上面缺点可以用 SpringSession 完美解决

在这里插入图片描述

4. 整合 SpringSession

接下来我们来整合 SpringSession 来解决 session 共享问题

引入2个 start 依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>

application.properties 进行相关配置

在这里插入图片描述
A、spring.session.store-type=redis

设置 Spring Session 使用 Redis 进行存储。默认配置就是 redis

B、spring.session.timeout=10m

设置 Spring Session 的过期时间。如果不指定单位模式是 s。

在主启动类上加上注解 @EnableRedisHttpSession 开启将 session 统一存储到 redis 中

在这里插入图片描述

同时待解决的还有 不同服务,子域session共享 问题,比如说我们 auth.gulimall.com 中存储session,session 域仅在此域名中,我们想要在 gulimall.com 中获取session 不行。。。,解决方式就是 自定义 SpringSession 配置文件将作用域范围扩大。。

在这里插入图片描述

package com.fancy.gulimall.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

@Configuration
public class GulimallSessionConfig {

    @Bean
    public CookieSerializer cookieSerializer(){
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();

        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");

        return cookieSerializer;
    }

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

5. SpringSession 核心原理

@EnableRedisHttpSession 导入 RedisHttpSessionConfiguration 配置
      
1、给容器中添加了一个组件
       
       SessionRepository = 》》》【RedisOperationsSessionRepository==》redis操作session。session的增删改查封装类

2SessionRepositoryFilter ==Filter: session'存储过滤器;每个请求过来都必须经过filter
       
       1、创建的时候,就自动从容器中获取到了SessionRepository2、原始的request,response都被包装。SessionRepositoryRequestWrapperSessionRepositoryResponseWrapper
       3、以后获取session。request.getSession();
       //SessionRepositoryRequestWrapper
       4、wrappedRequest.getSession();===> SessionRepository 中获取到的。
 
装饰者模式;
 
   自动延期;redis中的数据也是有过期时间。

在这里插入图片描述

6. 使用返回的 code,换取 access token

在这里插入图片描述

注意,上面这个是 post 请求

{ 
	"access_token": "2.00pDpxyGd3J5bEef6b98778e0ZKsu4", 
	"remind_in": "157679999", 
	"expires_in": 157679999, 
	"uid": "6397634785", 
	"isRealName": "true"
}

在这里插入图片描述

接口的完整代码:

@GetMapping("/oauth2.0/weibo/success")
public String weibo(@RequestParam("code") String code, HttpSession session, HttpServletResponse servletResponse, HttpServletRequest request) throws Exception {
    Map<String,String> header = new HashMap<>();
    Map<String,String> query = new HashMap<>();

    Map<String,String> map = new HashMap<>();
    map.put("client_id","2636917288");
    map.put("client_secret","6a263e9284c6c1a74a62eadacc11b6e2");
    map.put("grant_type","authorization_code");
    map.put("redirect_uri","http://auth.gulimall.com/oauth2.0/weibo/success");
    map.put("code",code);
    //1、根据code换取accessToken;
    HttpResponse response =  HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", header, query, map);

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

        //知道当前是哪个社交用户
        //1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息账号,以后这个社交账号就对应指定的会员)
        //登录或者注册这个社交用户
        R oauthlogin = memberFeignService.oauthLogin(socialUser);
        if(oauthlogin.getCode() == 0){
            MemberRespVo data = oauthlogin.getData("data", new TypeReference<MemberRespVo>() {
            });
            log.info("登录成功:用户:{}",data.toString());
            //1、第一次使用session;命令浏览器保存卡号。JSESSIONID这个cookie;
            //以后浏览器访问哪个网站就会带上这个网站的cookie;
            //子域之间; gulimall.com  auth.gulimall.com  order.gulimall.com
            //发卡的时候(指定域名为父域名),即使是子域系统发的卡,也能让父域直接使用。
            //TODO 1、默认发的令牌。session=dsajkdjl。作用域:当前域;(解决子域session共享问题)
            //TODO 2、使用JSON的序列化方式来序列化对象数据到redis中
            session.setAttribute("loginUser",data);
//                new Cookie("JSESSIONID","dadaa").setDomain("");
//                servletResponse.addCookie();
            //2、登录成功就跳回首页
            return "redirect:http://gulimall.com";

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

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

这里登录是远程调用 MemberService 的 oathLogin 方法。。。

 @Override
public MemberEntity authLogin(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;
    }
}

同时,我们在其他服务也要配置 SpringSession

在这里插入图片描述
Oauth2.0:授权通过后,使用 code 换取 access_token,然后去访问任何开放API

A、code 用后即毁
B、access_token 在几天内是一样的
C、uid 永久固定

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值