OAuth2 授权码模式
3. 授权码模式
示例代码对应仓库:
本小节,我们来学习授权码模式(Authorization Code)。
授权码模式,是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与授权务器进行互动。
一般情况下,在有客户端的情况下,我们与第三方平台常常采用这种方式。
- (A)用户访问客户端,后者将前者跳转到到授权服务器。
- (B)用户选择是否给予客户端授权。
- (C)假设用户给予授权,授权服务器将跳转到客户端事先指定的"重定向 URI"(Redirection URI),同时附上一个授权码。
- (D)客户端收到授权码,附上早先的"重定向 URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- (E)认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌。
下面,我们来新建两个项目,搭建一个授权码模式的使用示例。如下图所示:
package com.example.demo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; @Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 用户认证 Manager */ @Autowired private AuthenticationManager authenticationManager; //配置使用的 AuthenticationManager 实现用户认证的功能 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); } //设置 /oauth/check_token 端点,通过认证后可访问。 //这里的认证,指的是使用 client-id + client-secret 进行的客户端认证,不要和用户认证混淆。 //其中,/oauth/check_token 端点对应 CheckTokenEndpoint 类,用于校验访问令牌的有效性。 //在客户端访问资源服务器时,会在请求中带上访问令牌。 //在资源服务器收到客户端的请求时,会使用请求中的访问令牌,找授权服务器确认该访问令牌的有效性。 @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.checkTokenAccess("isAuthenticated()"); } //进行 Client 客户端的配置。 //设置使用基于内存的 Client 存储器。实际情况下,最好放入数据库中,方便管理。 /* * * 创建一个 Client 配置。如果要继续添加另外的 Client 配置,可以在 <4.3> 处使用 #and() 方法继续拼接。 * 注意,这里的 .withClient("clientapp").secret("112233") 代码段,就是 client-id 和 client-secret。 *补充知识:可能会有胖友会问,为什么要创建 Client 的 client-id 和 client-secret 呢? *通过 client-id 编号和 client-secret,授权服务器可以知道调用的来源以及正确性。这样, *即使“坏人”拿到 Access Token ,但是没有 client-id 编号和 client-secret,也不能和授权服务器发生有效的交互。 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // <4.1> .withClient("clientapp").secret("112233") // <4.2> Client 账号、密码。 .authorizedGrantTypes("authorization_code") // <4.2> 授权码模式 .redirectUris("http://127.0.0.1:9090/callback") .scopes("read_userinfo", "read_contacts") // <4.2> 可授权的 Scope // .and().withClient() // <4.3> 可以继续配置新的 Client ; } }
仅仅需要修改 OAuth2AuthorizationServerConfig 类,设置使用 "authorization_code"
授权码模式,并设置回调地址。
注意,这里设置的回调地址,稍后我们会在「3.2 搭建资源服务器」中实现。
3.1.1 简单测试
执行 AuthorizationServerApplication 启动授权服务器。
① 使用浏览器,访问 http://127.0.0.1:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://127.0.0.1:9090/callback&response_type=code&scope=read_userinfo 地址,获取授权。请求参数说明如下:
client_id
参数,必传,为我们在 OAuth2AuthorizationServer 中配置的 Client 的编号。
redirect_uri
参数,可选,回调地址。当然,如果client_id
对应的 Client 未配置redirectUris
属性,会报错。
response_type
参数,必传,返回结果为code
授权码。
scope
参数,可选,申请授权的 Scope 。如果多个,使用逗号分隔。
state
参数,可选,表示客户端的当前状态,可以指定任意值,授权服务器会原封不动地返回这个值。
友情提示:state
参数,未在上述 URL 中体现出来。
因为我们并未登录授权服务器,所以被拦截跳转到登录界面。如下图所示:
② 输入用户的账号密码「yunai/1024」进行登录。登录完成后,进入授权界面。如下图所示:
和我们日常使用的腾讯 QQ、微信、微博等等三方登录,是一模一样的,除了丑了点,嘿嘿~
③ 选择 scope.read_userinfo
为 Approve 允许,点击「Authorize」按钮,完成授权操作。浏览器自动重定向到 Redirection URI 地址,并且在 URI 上可以看到 code
授权码。如下图所示:
友情提示:/oauth/authorize
对应 AuthorizationEndpoint 端点。
④ 因为我们暂时没有启动资源服务器,所以显示无法访问。这里,我们先使用 Postman 模拟请求 http://localhost:8080/oauth/token 地址,使用授权码获取到访问令牌。如下图所示:
请求说明:
- 通过 Basic Auth 的方式,填写
client-id
+client-secret
作为用户名与密码,实现 Client 客户端有效性的认证。
- 请求参数
grant_type
为"authorization_code"
,表示使用授权码模式。
- 请求参数
code
,从授权服务器获取到的授权码。
- 请求参数
redirect_uri
,Client 客户端的 Redirection URI 地址。
注意,授权码仅能使用一次,重复请求会报 Invalid authorization code:
错误。如下图所示:
3.2 搭建资源服务器
复制 lab-68-demo02-resource-server
项目,主要是提供回调地址。如下图所示:
① 新建 CallbackController 类,提供 /callback
回调地址。
② 在 OAuth2ResourceServerConfig 配置类中,设置 /callback
回调地址无需权限验证,不然回调都跳转不过来哈。
3.2.1 CallbackController
创建 CallbackController 类,提供 /callback
回调地址,在获取到授权码时,请求授权服务器,通过授权码获取访问令牌。代码如下:
@RestController @RequestMapping("/") public class CallbackController { @Autowired private OAuth2ClientProperties oauth2ClientProperties; @Value("${security.oauth2.access-token-uri}") private String accessTokenUri; @GetMapping("/callback") public OAuth2AccessToken login(@RequestParam("code") String code) { // 创建 AuthorizationCodeResourceDetails 对象 AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails(); resourceDetails.setAccessTokenUri(accessTokenUri); resourceDetails.setClientId(oauth2ClientProperties.getClientId()); resourceDetails.setClientSecret(oauth2ClientProperties.getClientSecret()); // 创建 OAuth2RestTemplate 对象 OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails); restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setAuthorizationCode(code); // <1> 设置 code restTemplate.getOAuth2ClientContext().getAccessTokenRequest().setPreservedState("http://127.0.0.1:9090/callback"); // <2> 通过这个方式,设置 redirect_uri 参数 restTemplate.setAccessTokenProvider(new AuthorizationCodeAccessTokenProvider()); // 获取访问令牌 return restTemplate.getAccessToken(); } }
代码比较简单,还是使用 OAuth2RestTemplate 进行请求授权服务器,胖友自己瞅瞅哈。
需要注意的是 <1>
和 <2>
处,设置请求授权服务器需要的 code
和 redirect_uri
参数。
3.2.2 简单测试
执行 ResourceServerApplication 启动资源服务器。
重复「3.2.1 简单测试」的过程,成功获取到访问令牌。如下图所示: