边缘计算是什么,2020年边缘计算概念股一览
OAuth 2.0 允许第三方应用程序访问受限的HTTP资源的授权协议,像平常大家使用 Github 、 Google 账号来登陆其他系统时使用的就是 OAuth 2.0 授权框架,下图就是使用 Github 账号登陆 Coding 系统的授权页面图:
类似使用 OAuth 2.0 授权的还有很多,本文将介绍 OAuth 2.0 相关的概念如:角色、授权类型等知识,以下是我整理一张 OAuth 2.0 授权的脑头,希望对G y n a大家了解 OAuth 2.0 授权协议有帮助。
文章将以脑图中的内容展开 OAuth 2.0 协议同时除, S [ 3 z k (了9 e q d OAuth 2.0 外,还会配合 Spring Security OAuth2 来搭建 OAuth2客y D 4 { X ( * _ W户端 ,这也是学习 OAuth 2.0 的目的,直接应用到实际项目中,加深对 OAuth 2.0 和 Spring Security 的理解。
O% q QAuth 2.0 角色
OAuth 2.0 中有四种类型的角色分别为: 资源Owner 、 授权服务 、 客户端 、 资源服务 ,这四个角色负责不同的工作,为了方便理解先给出一张大概的流程图,细节部分后面再分o P 7 Y = .别展开:
OAutE , 4 R . | 1 @h 2.0 大概授权流程
资源 Owner
资源 Owner可以理解为一个用户,如之前提到使用 Github 登陆 Coding 中的例子中,用户使用G~ k a G EitHub账号登陆Coding,Coding就需要知道用户在GitHub系统中的的头像、用户名、email等信息,这些账$ p # % v p l户信息都是属于用户的这样就不难理解 资源 Owner 了。在CodinO ~ Z 6 y ! mg请求从GitHub中获取想要的用户信息时也是没那容易的,Gi6 y U ( v 2 qtHub为了安全起见,至少要通过用户g q 6 H F a R(资源 Owner)的同意才行。
资源服务器
明白 资源 Owner 后,相信你已经知道什i h i d么是 资源服务器 ,在这个例子中用户账号的信息都存放在GitHub的服务器中,所以这里的资源服务器就是GitHub服务器。GitHub服务器负责保存、保护用户的资v o m U _ h ^源,任何其他第三方系统想到使用这些信息的系统都需要经过 资源 Owner 授权q : ; ^ n * X N,同时依照 OAuth 2.0 授权流程进行交互。
客户端
知道 资源 Owner 和 资源服务器 后,OAuth中的客户端角色也相对容易理解了,简单的说 客F L Z ) & ^ T户端就是想要获取资源的系统 ,如例子中的使用GitHub登陆Cod[ 7 E C 1 ~ I ting时,Coding就是OAuthD } F K q ! a中的客户端4 T。客户端主要负责发起授权请求、获取AccessToken、获取用户资源。
授权服务器
有了 资源 Owner 、 资源服务器 、 客户端 还不能完成OAuth授权的,还需要有 授权服务器 。在OAuth中授权服务器除了负责与用户(资源 Ownel c B -r)、客房端(Coding)交互外,还要生成AccessToken、验证AcJ d b J ccessTok1 | I A 9 p O /en等功能 X | H I I,它是OAuth授权中的非常重要的一环,在例子中授权服务器就是GitHub的服务器。
小结
OAuth中: 资源Owner 、 授权服务 、 客户端 、 资源服务 有四个角色在使用GitHub登陆Coding的例子中分别表示:
- 资源Owner:GitHub用户
- 授权服务:GitHub服务器
- 客户端:Coding系统
- 资G o p 5 s @ ( &源服务:GitHub服务器
其中授权服务服务器、资源服务器可以单独搭建(鬼知道GitHub怎么搭建的)。在微服务} ,同意授权;
以上是大致的 授权码授权 流程,大部分是客户端与授权服务器的交互,整个过程中有X c 6 r |几个参数说明如下:
- client_id:在Github中注册的AI F l , nppidP + u H a,用于标记客户端
- redirect_uri:可以理解一个 callback^ - n D m ,授权服务器验证完客户端与用户名等信息后将浏览器重定向到此地址并带上 code 参数
- code:由授权服务器返回的一个凭证,用于获取AccessToken@ ( $ 6 7 : o
- state:由客户端传递给授权服务器,授权服务器一般到调用 redirect_uri 时原样返回
授权码授权请求
在使用授权码授权的模式中,作为客户端请求授^ & w = Z ! O权的的时候都需要按规范请求,以下是使用授权码授权发起授权时所需要的参数 :
如使用Github登陆Coding例子中的 hR ) c | j S :ttps:/7 h J z m + l -/github.com/login/oauth/authoriz_ { A e H ne?cl= 8 B y 3 F Kient_id=a5ce5a6c7e8c39567ca0&scope=user:email&redirect_uri=https://coding.net/api/Z V 。
客户端凭证授权请求
客房端凭证授权中客户端会直接发起获取AccessToken请求授权服务器的 AccessToken Endpoint,请求参数如, - ! , # ( G下:
注意:在OAuth中 AccessToken Endpoint是使用HTTP Basic认证,在请求时还需要携带 Authorization 请求| p q v } T x T头,如使用 postman 测试请求时:
其中的 username 和 password 参数对于OAn ` / U A d c G 6uth协议中的 client_id 和 client_secret , client_id 和 client_secret 都是由授权服务器生成的。
客户端凭证授权响应
授权服务器验证完 client_id 和 client_secret 后返回token:
{
\"access_tokenM L - { i\":\"2YotnFZFEjr1zCsicMWpAA\",
\"token_type\":\"example\",U Q _ * l w
\"expires_in\":3600,
\"example_parameter\":\"example_value\"
}用户凭证授权
用户凭证授权 与 客户端凭证授权 类似,不同的地方是进行授权时要提供用户名和用户的密码。
基本流程如下:
- A、客户端首先需要知S ? Y道用户的凭证o $ f !
- B、使用用户凭证获f x J (取AccessToken
- C、授权服务器验证U c":\"tGzv3JOkF0XG5Qx2TlKWIAh ( A ~ S C Q\",
\"example_parameter\":\"example_value\"
}隐式授权
隐式授权用于 x q获取AccessToken,但是获取的方式与 用户凭证授权 和 客户端授权 不同的是,它是在访问 授权Endpoint 的时候就会获取AccessToken而不是访问 Token Endpoing ,而且AccessTok~ K 8 @ @ 7 Den的w c ?会作为 redirect_uri 的Segment返回。
- A.1、A.2、浏览器访问支持隐式授权的服务器的授权Endpointf r b N u & E 3 7;
- B.1、用户L S B V w , E输入账号密码;
- B.2、用户点击 授权按钮 ,同意授权;
- C、授权服务器使用 redirect_uri 返回AccessToken;
- D、授权服务器将浏览器重定向到 redirect_uri ,并携带AccessToken如: http://example.com/cb#a) 2 dccess_token=2YotnFZFEjr1zCsicMWpAA&* G Mamp;state=xyz&token_D q 7 MtW % b ( { 4 % A Vype=example&expires_in=3600 ;
- D、 redirect_uri 的地址是指向一个 Web资源客户端
- E、 Web资源客户端 返回一段脚本
- F、浏览Y i / m & / y器执行脚本
- D、客户端获得AccessToken
隐式授权不太好理解,但是仔细比较 客户端凭证授权 和 用户凭证授` ) @权 会发现 隐式授权 不需要知道 用户凭证 或 客户端凭证 ,这样做相对更安全。
隐式授权请求
再使用 隐式授权 时,所需f f x _要请求参数如下:
隐式授权响应
隐式授权响应参数是通过 redirect_uriA d Y + Y 0 ) m t 回调返回的,如 http://example.com/cb#access_token=2YotnFZFEj& I H @ a C yr1zCsicMWpAA &state=xyz&token_type=example&expires_in=3600 就是隐式授权响应参数,其中需要注意的是响应的参数是使用Segment的形式的,而a O . + 9 4 J ~不是普通的URL参数。
OAut/ g { Jh2 客户端
前面提到过OAuth协议中有四个角色,这一节使用Sp} H X - I + . Q +ring Boot实现一个登陆 GitHub 的 OAuthClient ,要使: ] } + _用OAuth2协议登陆GitHub首先要云GitHub里面申请:
申请 OAuth App
填写必需的信息
上图中的 Authorization callback URL 就是 redirect_uri 用户同意授权f - ~ [ C后GitHub会将浏览器重定向到该地址,因此先要在本地的OAuth客户端服务中添加一个接口响应GitHub的重定向请求。
配置OAuthClient
熟悉O$ p & @Auth2协议后,我们在使用 Spring Security OAuthz H 1 e y d D k h2 配置一个GitHub授权客户端,使用 认证码 授权流程(可以先T _ } S Z u x ( .去看一遍认证码授权流程图),示例工程依p K ^ K C s m l |赖:
<dependency>
<groupId>org.springframework.i s ( s b y Y N xboot</groupId>
<artif! u n P actId>spring-boot-starter-oauth2-client</artifactId>
</dependencC ! c @y>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-st; ~ b D 6 6 y _ xarter-web</w / 1 1artifactId>
&lo Q R r O A g Yt;/dependency>Spring SecuM C G urity OAuth4 n n 6 a { %2F V N d ay OAuth2 已经帮我们配置了,开发都只需要指定必需的信息就行如:clientId、clientSecret。
Spring Security OAuth2 使用 Registration 作为客户端的的配置实体:
public static class Registration {
//授权服务器提供者名称
private String provider;
//客户端id
privated J 9 H ` 9 StrinU j # Z a P 0 t 9g clientId;
//客户端凭证
private String clientSecret;
....下面是之前注册好的 GitHubi x b b B OAuth App 的信息:
spring.sec) V j . : v 5 urity.oaut$ ! x V a ( 1 x &h2.client.registration.github.clientId=5fefca2daccf85bede32
spring.securi/ ] l K Oty.oauth2.client.registration.github.clientSecret=01dde7a7239bd1e S 2 . 8 / p ( A8bd8a83de67f99dde864fb6524``配置redirect_uri
Spring Security OAuth2 内置了一个redirect_uri模板: {baseUrl}/login/oauth2/code/{registrationId} ,其中的 registrationId 是在从配置中提取出来的:
spring.security.oauth2[ ! o 0 g n F w.client.registration..clientId=xxxxx如在上面的5 | bGitHub客户端的配置中,因为指定的 registrationId 是 github ,所以重定向uri地址就是:
{baseUrl}/login/oauth2/! M 1 * k $ . O ncode/github启动服务器
OAuth2客户端 ( Q j N W ~ / n和重定向Uri配置好后,将服务器启动,然后打开浏览器进入: http://localhost:8080/ 。第一次打开因为没有认证会将浏览器重客向到GitHub的 授权= : Q ] | @ c * CEndpoint :
常用授权服务器(| A . & y } l rCommonOAuth2Provider)
Spring Security OAuth2 内置了一些常用的授权服务器的配置,这些配置都在 CommonOAuth2Provider 中:
public enum CommonOAuth2Proc n O u vider {
GOOGLE {
@OverrV ! } M { 2 Oide
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.C H O % H :BASIC, DEFAU0 j y q = rLT_REDIRECT_URL);
builderr : z ` bhttps://# } gistration.Builder builder = getBuilder(registrationId,
ClientAd A AuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
builder.scope(\"read:user\")i + + X C = j;
builder.authorizationUri(\"https://github.com/. r 5login/oauth/authorize\");
builder.tokenUri(\"https://github.com/login/oauth/access_tokenn 7 1 G c z a T t\");
builder.userInfoUri(\"https:O d B c//api.github.com/user\")j 7 j $ - & k ;;
b@ z 2uilder.userNameAttributeName(\"id\");
b, u euilder.clk 3 R ( $ientName(\"GitHub\");
return builder;
}
},
FA_ , Y nCEBOOK {
@Override
publicN o Y y z g Builder getBuilder(String registrationId) {
ClientRegistration.% v | * h - 4 GBuH 0 { a N } f r filder builder = getBuilder(registrationId,
Cli H W FentAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
builderg b V M.scope(\"public_profile\", \"email\");
builder.authorizationUri(\"https://www.facebook.com/v2.8/dialog/oauth\");
builder.tokenUri(\"https://graph.facebook.com/v2.8/oauth/access_token\");
builder.1 P G K OuserInfoUri(\"https://graph.facebook.com/me?fief x d d lds=id,name,ema1 h ( 4 ? i z vil\");
builder.userNameAttributeNamE , v % i @e(\"id\"r U h m q ~);
b~ D ^ L Nuilder.clientName(\"Facebook\");
return builde5 0 M q z Y F Or;
}
},
OKTA {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethoV 8 Q | k cd.BASIC, DEFAULT_REDIRECT_U- t 9RL);
buiJ U Elder.sc{ F s 1 ]ope(\"openid\S c B % w v 7 t F", K b | A = ) S m\"profile\", \y S _"email\") 6 ~;
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName(\"Okta\");
return builder;
}
};
private static final String DEFAULT_R; u 8 0EDIRECT_URL = \"{baseUrl}/{action}/oauth2/code/{registrationId}\";
}Co4 U S Y cmmonOAuth2Provis e P C } : o Mder 中有四个授权服务器配置: Oh z ? ;KTA 、 FACEBOOK 、 GITHUB 、 GOOGLE 。在OAuth2协议中的配置项 redirJ p d =ect_uri 、 Token Endpoint 、 授权 Endpoint 、 scope 都会在这里配置:
GITHUB {
@Override
public Builder getBuilder T ; a b e C L 2(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.BASIC, DEFA; B v c b h $ULT_REDIRECT_URL);
builder.scope(\"read:user\");
builder.authorizationUri(\"https://github.com/login/oauth/authorize\");
builde3 d qr.tokenUri(\"https://github.com/login/oauth/access_toke; 0 5 ( J T b W _n\");
builM - w Oder.userInfoUri(\"https://api.g) L P % A $ /ithub.com/usec v / e _ O Z =r\");
builder.userNameAttributeName(\"id\");
builder.clientName(\"GitHub\");
return builder;
}
}重定向Uri拦截
脑瓜子有点蒙了,感 ` H S P P M 2觉自己就配置了 c: a D B V = v ilientid 和$ 3 X & clientSecret 一个OAuth2客户端就完成了,其中的一些原由还没搞明白啊。。。,最好奇的是重定向Uri是怎么被处理的。
Spring Security OAuth2 是基于 Spring Security 的,之前看过 Spring Security 文章,知道它的处理原理是基于过滤器的,如果你不知道的话推荐看这篇文章: 《Spring Security 架构》 。在源码中找了一下,发现R M # ! P一个可疑的Security 过滤器:
- OAuth2LoginAuthenticationFilter:处理OAuth2授权的过滤器
这个 Security 过滤器有个常量:
pub/ E 0 V 2 W 3 +lic static final String DEFAULT_FILTER_PROCESSES_URI = \"/login/oauth2/code/*\";是一个匹配器,之前提到过 Spring SecuritI N - ^ 1y OAuK p v M w D # , th2 中有一个默认的redirect_uri模板: {baseUrl}/{action}/oauth2/code/{registrat~ , 2 z kionId} , /login/oauth2/code/* 正好能与redz E E o { Sirect_uri模板匹配成功,所以 OAuth2LoginAuthenticationFilter 会在用户同意授权后执行,它的构造方法如下:
public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository clientRegiO # A { dstray GtionRepository% { 6 F u,
OAuth2AuthorizedClientService authorize_ @ D X 8 QdClientService) {
thisj 3 N , l i 3 3(clientRegistrationRepositon @ l cry, authoF D k a ; wrizedClientService, DEFAULT_FILTER_PROCESSES_URI);
}OAutg ~ t a N V 8h2LoginAuthenticationFilter 主要将授权服务器返回的 code 拿出来,然后通过AuthenticationManager 来认证(获取AccessToken),下来是移? * N x l ;除部分代码后的源代码:
@Override
public Authentication attemptAuthentication(HttpServlS 7 & / B * . Net~ Y y n JRequest request, HttpServletResponse response. @ ^ T $ 0 ~ 0 :)
tc Q E ! hrows AuthenticationEx{ j Lception {
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
//检查没code与state
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2ErroP 1 B B m S F ; +r = new OAuth2Error(OAuth2ErrorCodes.INVA6 h j ( lLID_REQUEST);
throwQ 5 c * { & p 2 : new OAuth2Authentic2 W w fationException(oauth2Error, oauth2Error.toString());
}
//获取 OAuth2AuthorizationRequest
OAuth2AuthorizationRequest authorL 1 D D @ ^ /izationRequest =
this.authorizationRequestRepository.removeAu@ K !thorizationRequest(requ+ } dest, response);
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUE A Y I 3 & HND_ERROR_CODW P k & O @E);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); S Z D = 3 & @;T Q ~ & j @ e ?
}
//取出 ClientRegistration
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clm | [ientRegistr( ^ L c ) o * a `ation = this.clientRegisty y F 7rationR1 ! l m B j & W $eposito( c 3 ry.findByRS , X D begistrationId(registrationId);
if (clientRegistratio, Y % un == null) {
OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT3 f & s h_FOUND_ERROR_CODE,
\"Client Registr= ~ U Q [ation not found with Id: \" + registrationId, null);
throw new OAuth2Authentic4 3 8ationException(oauth2Error, oauth2Error.toString());
}
String redirectUri = UriCompon3 O l J H rentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.ry L b .eplaceQuery(null)
.z v : ) N 3 * [ /build()
.toUriString();
//认证、获取V E A 5 7AccessToken
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri);
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenP g R = FticationToken(
clienv i , x h % ,tRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationP e 4 d d Z { DRequest.setDetails(authenticationDetails);
OAuth2LoginAuthenticationToken authenticationResult =
(OAuth2LoginAuthenticationTokJ & t - T i l [en) this.getAuthenticationManager; L L &().authenticate(authenticationRequest);
...
return oauth2Authenti5 P h .cation;
}获取AccessTok* * J ; m K den
前面提到 OAu6 L X C vth2LoginAuthentf - g ; * S [ 9 EicationFilter 是使用 Authenticati* C $ ; - K W e bonManager 来进行OAB d R 5 u V . W vuth2认证的,一般j * b 3情况下在 Spring Security 中的 AuthenticationManageq ~ ` pr 都是使用的 ProviderManager 来进行认证的,所以对应在 S& _ n e ~ * a , 4pring Security OAuth2 中有一个 OAuth2Lx # K ~ g # 8 (oginAuthenticationProvid& [ K J q Per 用于获A j A 9 w } k j W取AccessToken:
publij ` { :c class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGran* 5 0 0tRequest> ack % ~ p Y ocessTokQ G q | d b ; m X ! h t ionProvider handle it instead
return null;* 9 , & E 2 Y g B
}
OA$ b ^ G o C . S nuth2AccessTokeI & Z T ; znResponse accessToke l M?> authentication) {
return OAuth2LoginAuthenticationTok{ ~ 6 J y ien.class.isAssignableFrom(au~ n h & k M | Kthentication);
}
}参考资料
- OAuth 2 Developers Guide
- draft-ietf-oauth-vZ b _ W2