谷粒商城 高级篇 (十六) --------- 社交登录
目录
- 前言
- 一、OAuth 2.0
- 二、微博登陆准备工作
- 三、整合微博登录
- 1、引导用户到如下地址
- 2. 授权登录后进入回调函数
- 3. Session共享问题解决
- ① session复制
- ② 客户端存储
- ③ hash一致性
- ④ 统一存储
- 4. 整合 SpringSession
- 5. SpringSession 核心原理
- 6. 使用返回的 code,换取 access token
前言
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 {@Beanpublic CookieSerializer cookieSerializer(){DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();cookieSerializer.setDomainName("gulimall.com");cookieSerializer.setCookieName("GULISESSION");return cookieSerializer;}@Beanpublic RedisSerializer<Object> springSessionDefaultRedisSerializer() {return new GenericJackson2JsonRedisSerializer();}
}
5. SpringSession 核心原理
@EnableRedisHttpSession 导入 RedisHttpSessionConfiguration 配置1、给容器中添加了一个组件SessionRepository = 》》》【RedisOperationsSessionRepository】==》redis操作session。session的增删改查封装类2、SessionRepositoryFilter == 》Filter: session'存储过滤器;每个请求过来都必须经过filter1、创建的时候,就自动从容器中获取到了SessionRepository;2、原始的request,response都被包装。SessionRepositoryRequestWrapper,SessionRepositoryResponseWrapper3、以后获取session。request.getSession();//SessionRepositoryRequestWrapper4、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){//获取到了 accessTokenString 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_tokenMemberEntity 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 永久固定
谷粒商城 高级篇 (十六) --------- 社交登录相关推荐
- 谷粒商城 高级篇 (十四) ---------- 商品详情
目录 一.详情数据 二.查询详情 三.sku 组合切换 四.关键 SQL 一.详情数据 封装成 vo 如下: SkuItemVo: @Data public class SkuItemVo {//1. ...
- 谷粒商城高级篇上(未完待续)
谷粒商城高级篇(上)保姆级整理 之前整理了基础篇,Typora提示将近20000词,谷粒商城基础篇保姆级整理 在学高级篇的时候,不知不觉又整理了两万多词,做了一阶段,先发出来,剩余部分整理好了再发.自 ...
- 谷粒商城 高级篇 (十七) --------- 单点登录
目录 一.SSO 介绍 1. 单点登录业务介绍 2. 什么是跨域 Web SSO 3. 浏览器读写 cookie 的安全性限制 4. http 协议是无状态协议. 二.单点登录框架 三.登录接入方式 ...
- 【谷粒商城高级篇】商品服务 商品上架
谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...
- 【谷粒商城高级篇】商城业务:商品检索
谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...
- 【谷粒商城高级篇】Elasticsearch:全文检索
谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...
- 谷粒商城高级篇笔记1
这里写自定义目录标题 0.ElasticSearch 1.Nginx配置域名问题 01.Nginx(反向代理) 配置 02.Nginx(负载均衡)+ 网关 配置 03.Nginx动静分离 2.JMet ...
- 谷粒商城-高级篇-aiueo
105 初步检索 105.1 _cat GET /_cat/nodes : 查看所有节点 GET /_cat/health : 查看es健康状况 GET /_cat/master : 查看主节点 GE ...
- 谷粒商城高级篇资料_一文搞定剑指offer面试题【分文别类篇】
点击上方"蓝字",关注了解更多 数组: 面试题3:数组中重复的数字 面试题4:二维数组中的查找 面试题21:调整数组顺序使奇数位于偶数前面 面试题39:数组中出现次数超过一半的数字 ...
最新文章
- HDU 3015 Disharmony Trees(树状数组)
- c语言,如何产生随机数
- Struts2的配置文件——web.xml
- @Transient注解作用
- C++设计模式-代理模式
- android方向触摸事件,Android触摸事件传递机制,这一篇就够了
- Qt——P12 信号连接信号
- 数据库点滴积累——索引
- 做食品检测1年,自学软件测试,最后心惊胆战转行
- Hibernate4实战 之 第五部分:Hibernate的事务和并发
- 破圈了!完美日记凭什么让周迅成为全球品牌代言人
- 如何搭建视频点播服务
- 到极地拍摄北极熊 你需要这样的装备
- 育英oj——LZY逃命路线总数
- 基本绘图全面攻略——turtle(海龟)库 Python
- 【Unity3D--自由观察模型】模型自动旋转+触屏旋转和缩放
- 根据三个点的坐标计算三角形面积
- golang中的字符串
- 易语言多线程崩溃解决的原因
- TODO List—2018今日头条校招