使用github账号登录自建项目(github授权第三方登录)

流程

1.登录Github→Setting→Developer settings→New Oauth App→填写自建项目主页地址和回调地址→生成 Client ID和Client Secret。

2.自建项目登录页,创建指向github授权页地址的a标签:

https://github.com/login/oauth/authorize?client_id=client_id&redirect_uri=http://localhost:8080/oauth/redirect

client_id:注册应用时的 Client ID
redirect_uri:自建项目的回调地址

3.访问授权页→填写github账号密码登录→信息正确→github会跳转到我们对应的回调地址上,并且携带授权码。

4.自建项目回调接口里面通过获取授权码,访问下面地址获取token:

https://github.com/login/oauth/access_token?client_id=client_id&client_secret=client_secret&code=code

5.通过token(携带到头信息中)访问下面地址获取用户信息:

https://api.github.com/user

6.后台存储用户信息到缓存,后面用户访问自建项目的接口时就可以进行鉴权。
其中一个实现是将得到的用户信息存储到session中,自建项目拦截器进行获取,如果不存在信息则判断用户没有登录。

session本质是通过cookie进行获取的,如果客户浏览器的session暴露,则攻击者就可以伪装用户了。这个攻击思维已经在已登录的自建项目上f12中提取JSESSIONID在apipost上模拟成功了,同样的方式,segmentfault上也可以模拟成功用户。

Client ID和Client Secret

id的作用是让授权服务器知道哪个应用在访问(github需要创建应用才能获取到id和secret),可以根据client id 来限制访问资源。

secret的作用是上面进行步骤4的时候,解决服务间信任问题。
client id是可以公开的,code可能会泄露,基于上面两点,如果不存在secret,那么就直接获取到token了,这就不安全了。(个人理解)

demo

https://heawill.top/upload/2022/12/aouth_github_login_test.zip

OAuth

概念

  • OAuth (Open Authority的缩写)是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无需将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。采用令牌(token)的方式可以让用户灵活的对第三方应用授权或者收回权限。
  • 上面的github登录,就是一个oauth2的样例。

角色

  • Resource owner 资源拥有者(用户)
    能够授予对受保护资源的访问权限的实体。当资源所有者是一个人时,它被称为最终用户
  • Resource Server 资源服务器
    托管受保护资源的服务器,能够使用访问令牌接受和响应受保护资源请求
  • Client 客户端
    代表资源所有者并经其授权发出受保护资源请求的应用程序。“客户”一词确实 不暗示任何特定的实现特征(例如, 应用程序是否在服务器、桌面或其他 设备上执行)。
  • Authorization server 授权服务器
    服务器在成功 验证资源所有者并获得授权后向客户端颁发访问令牌。授权服务器和资源服务器之间的交互超出了本规范的范围。授权服务器 可以是与资源服务器相同的服务器,也可以是单独的实体。 单个授权服务器可以发布多个资源服务器接受的访问令牌。

流程

屏幕截图 2022-12-12 223037

此流程符合上面的github登录的demo。

认证模式

  • 授权码模式
  • 密码模式
  • 隐式授权模式
  • 客户端凭证模式

疑问

为什么不直接返回token,而是要通过授权码去申请?

1.在跳转到第三方登录界面时,登录成功会跳转到我们传递的回调地址,这个地址会直接在浏览器的输入框显示,如果直接返回token,则会把token暴露在界面上,不安全。
而返回授权码,直接显示在界面上并不影响,但跳转到回调地址时,会访问到我们后端的接口,后端接口再通过授权码进行获取token,可以看出获取token是在后端进行的,保证了安全性。

2.所有授权客户都需要向授权中心注册获取 Client ID和Client Secret( appkey 与 appscrete),Client ID公开的,Client Secret只有客户端(后端)和授权服务器可见,为保证安全前端应该做到不可见。
通过上面的github第三方登录,我们可以看到跳转到github的授权页只携带了Client ID(前端处理,界面上输入框只能看到Client ID),而在获取token的过程中还需要携带了Client Secret(后端处理,界面上不可见),如果直接获取token,则需要直接在前端浏览器的输入框直接携带值进行访问,这也会造成暴露Client Secret。

总结就是避免一些关键的信息(token,secret)不在前端界面上暴露显示,让处理在后端进行。

参考

oauth2基本概念
Oauth2的授权码模式为什么要用code获取token
关于oauth2中为什么不直接返回token而是传授权码code

某个系统作为主系统的设计流程

一.子系统配置为子模块模式,当拦截器检查到用户无token时(包含根路径/),进行拦截,跳转到主系统的登录界面(类似https://github.com/login/oauth/authorize?client_id=client_id&redirect_uri=http://localhost:8080/oauth/redirect)
二.登录成功,主系统会访问我们的redirect_uri,并携带授权码,我们的redirect_uri的后台处理逻辑为:
1.根据授权码访问主系统(类似https://github.com/login/oauth/access_token?client_id=client_id&client_secret=client_secret&code=code)获取token
2.token在子系统拦截器中对应的判断合法的缓存标识为有效,并设定有效时间
3.在request获取到的cookie存入此token,并在数据库生成一个临时的用户给此对应的token,并把用户数据存入cookie(其中用户名的名称或头像等资源可以通过token跟主系统拿取,拿取的链接比如https://api.github.com/user)
4.response定向跳转到子系统主界面的链接
5.当浏览器跳转到子系统主界面的链接进行访问时,已经有了cookie的token和用户的数据,所以可以正常访问了
三.子系统的前端的各个界面要这样处理:头携带的token和用户的信息要存放在cookie中,每次访问http接口时都从cookie拿取,这样在二.1步骤才会被替换。如果不是存放在cookie,而是local storage或session storage中,则前端可以判断local storage或session storage不存在,则多加一步处理,从cookie拿取存入local storage或session storage中。

OpenID Connect(OIDC)

概念

  • OpenID Connect遵循oAuth2.0协议流程,并在这个基础上提供了id token来解决三方应用的用户身份认证问题。oAuth2.0使用access token来授权三方应用访问受保护的信息。
  • OpenID Connect将用户身份认证信息以id token的方式给到第三方应用。
  • id token基于JWT(json web token)进行封装,具有自包含性、紧凑性和防篡改性等特点。
  • 第三方应用在验证完id token的正确性后,可以进一步通过oAuth2授权流程获得的access token读取更多的用户信息

id token(identity token)

属性说明

认证服务返回的 ID Token 需要严格遵守 JWT(JSON Web Token)的定义,下面是
JWT(JSON Web Token)的定义细节

除以下值,还可额外带上自定义的用户信息,比如email,name,phone等

  • iss(Issuer Identifier,发行者标识符,必须):标识谁签发的,认证服务的唯一标识,一个区分大小写的 httpsURL,不包含 query 和 fragment 组件
  • sub(Subject Identifier, 对象标识符,必须):说明是哪位用户,iss 提供的终端用户的标识,在 iss 范围内唯一,最长为 255 个 ASCII 个字符,区分大小写
  • aud(Audience,观众,必须):标识 ID Token 的受众,必须包含 OAuth2 的client_id,分大小写的字符串数组
  • exp(Expiration time,到期时间,必须):过期时间,超过此时间的 ID Token 会作废
  • iat(Issued At Time,发行时间,必须):签发时间,JWT 的构建的时间
  • auth_time(Authentication Time,认证时间,可选):认证时间,终端用户完成认证的时间
  • nonce(随机数,可选):发送认证请求的时候提供的随机字符串,用来减缓重放攻击,也可以用来关联客户端 Session。如果 nonce 存在,客户端必须验证 nonce
  • acr(Authentication Context Class Reference,认证上下文类引用,可选):认证强度,表示一个认证上下文引用值,可以用来标识认证上下文类
  • amr(Authentication Methods References,认证方法参考,可选):表示一组认证方法
  • azp(Authorized party,授权方,可选):结合aud使用,只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用

示例:

{
	"iss": "https://1.2.3.4:8443/auth/realms/kubernetes",
	"sub": "547cea22-fc8a-4315-bdf2-6c92592a6e7c",
	"aud": "kubernetes",
	"exp": 1525158711,
	"iat": 1525158411,
	"auth_time": 0,
	"nonce": "n-0S6_WzA2Mj",
	"acr": "1",
	"azp": "kubernetes",
	"nbf": 0,
	"typ": "ID",
	"session_state": "150df80e-92a1-4b0c-a5c5-8c858eb5a848",
	"userId": "123456",
	"preferred_username": "theone",
	"given_name": "the",
	"family_name": "one",
	"email": "theone@mycorp.com"
}

大致接入流程

1.主系统提供认证地址和client_id,client_secret
2.子系统跳转认证地址,携带client_id,client_secret,回调地址
3.主系统认证中心输入用户/密码(如已登录,自动跳过)
4.认证中心验证通过回调,携带code
5.子系统后台调用code获取token,其中包含ID Token和Access Token,ID Token用于获取用户信息,Access Token用于获取其他信息

参考

具体文档参考

使用keycloak认证中心示例

  1. 安装keycloak并运行,普通安装docker安装
  2. 普通安装如果下载的程序运行不起来,可以考虑使用低版本,比如11.0.3
  3. 运行后,可参考此此教程进行配置

配置时,是需要录入对应的回调地址的,可能有疑问,就是我们第6步已经传回调地址了,为什么还得事先录入?猜测可能是用来验证对比的,只能client_id和redirect_uri跟系统录入的一样才行。
通过实验,我们 修改第6步传的回调地址跟在系统配置的回调地址不一样,会直接提示Invalid parameter: redirect_uri。

  1. 默认只能以127.0.0.1访问keycloak,外部访问需要配置,参考此教程
  2. 运行后,我们可以获取code:
http://IP:8080/auth/realms/SSS/protocol/openid-connect/auth?client_id=XXX&response_type=code&redirect_uri=YYY&scope=openid profile email&state=ZZZ

//其中IP替换成安装keycloak的电脑IP,SSS替换成具体创建的域的名称,XXX替换成具体创建的客户端id,YYY替换成具体的http回调地址,ZZZ替换成随机的字符串。
  1. 运行第五5并成功登录后,我们可以看到回调了并携带state,session_state,code参数:
回调地址?state=233&session_state=779f7671-f8a2-400c-9a29-4bbd5c54212d&code=93bb175f-21dd-4ff7-9a0b-cc29176eb33e.779f7671-f8a2-400c-9a29-4bbd5c54212d.d3340054-6db1-4d74-bbed-787138225615
  1. 使用code拿取token(access_token和id_tokden)
    1684805407783

  2. 使用access_token拿取用户信息
    1684805636915

单点登录

概念

用户只需要登录一次就可以访问权限范围内的所有应用子系统。
可以看出单点登录好像跟OAuth没啥关系,是不同概念。
应用场景区别,就是不同公司之间的系统,可以使用oauth;同一个公司的不同子系统,可以使用单点登录。
SSO是Single Sign On的缩写,OAuth是Open Authority的缩写,这两者都是使用令牌的方式来代替用户密码访问应用。

同域下的单点登录方案

概念

  • 该方案通过cookie+session实现。
  • 为了方便理解,我们确定同个域名下有三个子系统,sso.a.com登录系统,platform1.a.com平台1,platform2.a.com,我们需要实现只在登录系统登录过,即可不用在平台1和平台2再次登录。

问题及处理

  • cookie跨域问题

此跨域问题指的是跨二级域名,并不是跨不同域名。
cookie是不能跨域的,登录系统cookie的domain属性是sso.a.com,在访问给平台1和平台2的请求是带不上的

处理该问题,可通在登录系统将cookie的域设置为为顶域,这样所有子域的系统都可以访问到顶域的cookie。

代码样例:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  Cookie cookie = new Cookie("sessionID","sessionIDValue");
  cookie.setMaxAge(60*60*24);
  cookie.setDomain(".a.com");
  response.addCookie(cookie);
}
  • session跨域问题

处理完cookie跨域问题后,登录系统,平台1,平台2之间在用户访问时都可以拿到cookie,但是存储在不同系统间的session都是各自不一样的,这样就拿不到登录成功的信息了。或者说在登录系统中存储的已登录成功的用户信息,平台1和平台2并不知道。

处理该问题,可以通过一些session共享方案,比如Spring-Session,可参考:
https://blog.csdn.net/YXXXYX/article/details/125342292

参考

JAVA学习笔记_cookie.setPath()_setDomain()跨域共享
java 操作 Cookie 跨域(同顶级域名)
java 单点登录(SSO)

CAS方案(适用不同域)

概念

  • CAS是Central Authentication Service的缩写,即中央认证服务。具体指的是一个用来实现单点登录的项目,我们需要进行部署才能使用。
  • CAS 包含两个部分: CAS Server 和 CAS Client,Server需要到对应项目官方下载部署运行。Client为我们各自要进行单点登录的子系统,也需要适配对应代码才能与Server进行联动。

安装

简单点就是下载一个war部署到tomcat上。

默认账号casuser,密码Mellon。

CAS Server 需要https支持,如果简单测试,我们可以通过以下教程关闭https验证:https://blog.csdn.net/xuelang1478/article/details/90406168

美化登录界面

参考:https://blog.51cto.com/u_9806927/4991759

配置账号数据库

参考:https://mp.weixin.qq.com/s/dWwscpe9okkJjkGQMWVYvA