OAuth2.0 第三方登录
对于用户相关的OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。
- 以微博登录为例:
- 使用code换取accessToken,一个code只能换取一次
- 同一个用户的accessToken一段时间内是不会变化的,可以通过accessToken多次获取客户的信息
1、引导需要授权的用户到如下地址:
https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
2、如果用户同意授权,页面跳转至 YOUR_REGISTERED_REDIRECT_URI/?code=CODE
3、访问如下地址换取Access Token
为避免暴露CODE以及用户的密码,在后台服务中获取到CODE后直接由后台发起HTTP请求换取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=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET可以使用basic方式加入header中,返回JSON
{
"access_token": "SlAV32hkKG",
"remind_in": 3600,
"expires_in": 3600
}
- 换取Token后往往需要在数据库中注册新用户
4、使用获得的Access Token调用API
分布式下session共享问题
- 同一个服务可能部署了多个服务器,跨服务器访问导致找不到session
- 访问不同的服务,不同服务间session不能共享
- 解决方案1-session复制
优点:web-server(Tomcat)原生支持,只需要修改配置文件
缺点:
session同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力;
任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平扩展更多的web-server;
大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取。
- 解决方案2-一致性hash负载均衡
优点:
只需要改nginx配置,不需要修改应用代码
负载均衡,只要hash属性的值分布是均匀的,多台 web-server的负载是均衡的
可以支持web-server水平扩展(session同步法是不行的,受内存限制)
缺点:
session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录
如果web-server水平扩展,rehash后session重新分布, 也会有一部分用户路由不到正确的session
但是以上缺点问题也不是很大,因为session本来都是有有效期的。所以这两种反向代理的方式可以使用
- 解决方案3-统一存储
优点:
没有安全隐患
可以水平扩展,数据库/缓存水平切分即可
web-server重启或者扩容都不会有session丢失
不足:
增加了一次网络调用,并且需要修改应用代码;如将所有的getSession方法替换为从Redis查数据的方式。redis获取数据比内存慢很多
上面缺点可以用SpringSession完美解决
- 不同服务间的子域session共享
jsessionid这个cookie默认的作用范围是当前域名内,当部署不同域名的服务时,可以手动指定session域名,扩大其作用范围,达到子域session共享的目的
如上图,将jessionid的domain设置为.gulimall.com之后,其下的所有子域(如会员服务、订单服务等)都可以通过该cookie获取session
可以通过整合spring-session解决这一系列问题
spring-session
在spring官网查看spring-session的project
步骤:
1、添加依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2、配置session缓存方式
spring.session.store-type=redis
3、主配置类中开启spring-session:@EnableRedisHttpSession
Spring Boot 在启动时会通过自定义的filter替换之前默认使用的HttpSession,在需要使用session时,直接在controller中注入即可(HttpSession)
默认发放的令牌的作用域为当前域,需要解决子域session共享问题:注册一个自定义的cookie序列化器
使用JSON格式将对象序列化到redis中
@Configuration public class GulimallSessionConfig{ @Bean public CookieSerializer cookieserializer() { DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); // 设置cookie的作用域 cookieSerializer.setDomainName(".gulimall.com"); return cookieSerializer; } @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { // 返回一个JSON格式的Redis序列化器 return new XXXJsonRedisSerializer(); } }
原理
单点登录SSO
single sign on 一次登录处处可用
在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。如登录gulimall.com后,访问gulifunding.com、gulixueyuan.com等应用时可以不用再进行登录验证
- 流程
1、访问应用1时,未携带用户信息,需要进行登录操作。通过请求头或者cookie携带网站地址信息,便于登录服务器完成操作后进行跳转
2、登录服务器根据用户名和密码进行登录验证,将查询到的用户信息作为value放入session中(Redis提供session服务);为此次登录操作生成一个UUID(相当于一个token令牌),作为key放入Redis;并且将UUID作为cookie返回给浏览器。
3、登录成功,根据携带的地址信息进行跳转。需要使用用户信息时,浏览器根据返回的UUID(cookie)获取用户信息
4、访问应用2时,判断cookie中是否存在UUID,若存在,说明已经在别的站点进行过登录
- 单点登录是一种有状态登录
为了保证客户端 cookie 的安全性,服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 tomcat 中的 session。
例如登录:用户登录后,我们把登录者的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session。然后下次请求,用户携带 cookie 值来,我们就能识别到对应 session, 从而找到用户的信息。
- 缺点
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,无法进行水平扩展
- 客户端请求依赖服务端,多次请求必须访问同一台服务器
JWT
JWT,全称是 Json Web Token, 是 JSON 风格轻量级的授权和身份认证规范,可实现无状态、 分布式的 Web 应用授权
JWT其实就是一个很长的字符串,字符之间通过”.”分隔符分为三个子串,各字串之间没有换行符。每一个子串表示了一个功能块,总共有三个部分:JWT头(header)、有效载荷(payload)、签名(signature)
基于Shiro框架的认证鉴权
JWT与基于Shiro的认证授权比较
- Shiro通过Realm处理认证和鉴权流程,并且通过SessionManager会话管理器将用户安全数据存入共享的缓存中(由Redis实现)
- 用户登录时可以将sessionId保存在请求头中,通过携带sessionId进行权限认证
- 类似于cookie-session机制,也是一种有状态的登录方式,但支持分布式架构,同时不依赖cookie(sessionId存储在请求头中)