1. 授权码模式(Authorization Code Grant)

image.png

如上图,我们可以看到此流程可大致分为三大部分:

  • Client Side:用户+客户端与授权服务器的交互。
  • Server Side:客户端与授权服务器之间的交互。
  • Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互。

用一句话概括授权码模式授权流程: 客户端换取授权码,客户端使用授权码换token,客户端使用token访问资源。
前提条件:

  • 第三方客户端需要提前与资源拥有方(同时也是授权所有方)协商客户端id(client_id),客户端密钥(client_secret)
  • 文中暂时未将scope、state等依赖具体框架的内容写进来,这里可以参考Spring Security OAuth2.0的实现。

Client Side

  • 这个客户端可以是浏览器,APP。
  • 客户端将client_id + client-secret + 授权模式标识(grant_type) + 授权范围(scope) + 回调地址(redirect_uri)拼成url访问授权服务器授权端点。
  • 授权服务器返回登录界面,要求用户登录(此时用户提交的密码等直接发到授权服务器,进行校验)。
  • 授权服务器返回授权审批界面,用户授权完成。
  • 授权服务器返回授权码到回调地址。

Server Side

  • 客户接收到授权码,并使用授权码 + client_id + client_secret访问授权服务器颁发token端点。
  • 授权服务器校验通过,颁发token返回给客户端。
  • 客户端保存token。

Check Access Token

  • 客户端在请求头中添加token,访问资源服务器。
  • 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)。
  • 资源服务器校验成功,返回资源。

隐式授权模式(Implicit Grant)

image.png
隐式授权模式大致可分为两部分:

  • Client Side:用户+客户端与授权服务器的交互
  • Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互

用一句话概括隐式授权模式授权流程: 客户端让用户登录授权服务器换token,客户端使用token访问资源

Client Side

  • 这个客户端可以是浏览器,APP。
  • 客户端将client_id + client-secret + 授权模式标识(grant_type)+ 回调地址(redirect_uri)拼成url访问授权服务器授权端点。
  • 授权服务器跳转用户登录界面,用户登录。
  • 用户授权。
  • 授权服务器访问回调地址返回token给客户端。

    Check Access Token

  • 客户端在请求头中添加token,访问资源服务器。

  • 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)。
  • 资源服务器校验成功,返回资源。

2. OAuth2.0认证方案

  1. 网关负责转发,认证和鉴权都在每个微服务
  2. 统一在网关层进行认证和鉴权,微服务只负责业务

优劣: 方案1:每个微服务都需要一套认证和鉴权,代码会严重耦合;且无法实现同一的认证,开发难度大涉及面广。 方案2:实现统一的认证鉴权,微服务只需专注业务;代码耦合度低,便于后期扩展。

image.png

2.1 四大角色

  1. 客户端: 需要访问微服务资源
  2. 网关: 负责转发、认证、鉴权
  3. OAuth2.0授权服务:负责认证授权颁发令牌
  4. 微服务集合:提供资源的一系列服务

2.2 授权码模式

提前向认证中心申请的几个参数如下:

  1. client_id:客户端唯一id,认证中心颁发的唯一标识。
  2. client_secret:客户端的秘钥,相当于密码。
  3. scope:客户端的权限。
  4. redirect_uri:授权码模式使用的跳转uri,需要事先告知认证中心。

请求授权码:

/oauth/authorize?client_id=&response_type=code&scope=&redirect_uri=

  • client_id:客户端的id,这个由认证中心分配,并不是所有的客户端都能随意接入认证中心
  • response_type:固定值为code,表示要求返回授权码。
  • scope:表示要求的授权范围,客户端的权限
  • redirect_uri:跳转的uri,认证中心同意或者拒绝授权跳转的地址,如果同意会在uri后面携带一个code=xxx,这就是授权码

返回授权码:

http://xxxx?code=NMoj5y 上述连接中,后面跟的code,即是授权码

请求令牌:

/oauth/token?
client_id=&
client_secret=&
grant_type=authorization_code&
code=NMoj5y&
redirect_uri= 相同的参数同上,不同参数解析如下:

  • grant_type:授权类型,授权码固定的值为authorization_code
  • code:这个就是上一步获取的授权码

返回令牌:

认证中心收到令牌请求之后,通过认证就会,其中包含了令牌access_token,如下:
{
“access_token”:”ACCESS_TOKEN”,
“token_type”:”bearer”,
“expires_in”:2592000,
“refresh_token”:”REFRESH_TOKEN”,
“scope”:”read”,
“uid”:100101
} access_token则是颁发的令牌,refresh_token是刷新令牌,一旦令牌失效则携带这个令牌进行刷新

2.3 配置方式

OAuth2.0的配置类

不是所有配置类都可以做OAuth2.0认证中心的配置类,需要满足以下两点:

  1. 继承AuthorizationServerConfigurerAdapter
  2. 标注 @EnableAuthorizationServer 注解
  1. /**
  2. * 认证中心配置
  3. * @EnableAuthorizationServer:这个注解标注,是认证中心
  4. * 继承AuthorizationServerConfigurerAdapter
  5. */
  6. public class AuthorizationServerConfig extends AuthoriaztionServerConfigurerAdapter {
  7. // 令牌端点安全约束配置,如 /oauth/token对那些开放
  8. @Override
  9. public void configure(AuthorizationServerSecurityCOnfigurer security) throws Exception {}
  10. // 客户端详情配置,例如秘钥、唯一ID
  11. @Override
  12. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}
  13. // 令牌访问端点的配置
  14. @Override
  15. public void configure(AuthorizationServerEndpointConfigurer endpoints) throws Exception {}
  16. }

客户端配置:
并非所有客户端,都有权限向认证中心申请令牌的;一些必要认证中心分配给你,如:客户端唯一ID、秘钥、权限。客户端配置也支持多个方式,如:内存、数据库。对接接口为:org.springframework.security.oauth2.provider.ClientDetailsService

  1. public interface ClientDetailsService {
  2. /**
  3. * Load a client by the client id. This method must not return null
  4. * @param clientId the client ID
  5. * @return The client details (never null)
  6. * @throws ClientRegistrationException if the client account is locked,expired,disabled or invalid for any other reason.
  7. */
  8. clientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;
  9. }
  1. /**
  2. * 配置客户端详情,并不是所有客户端都能接入
  3. */
  4. @Override
  5. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  6. // TODO: 内存方式演示
  7. clients.inMemory()
  8. // 客户端ID
  9. .withClient("myjszl")
  10. // 客户端秘钥
  11. .secret(new BCryptPasswordEncoder().encode("123ta"))
  12. // 资源id,唯一,可以设置多个
  13. .resourceIds("res1")
  14. // 授权模式:1. authorization_code(授权码模式)、password(密码模式)、client_credentials(客户端模式)、implicit(简化模式)
  15. // refresh_token -- token过期,需要用这个去重新请求
  16. .authorizedGrantTypes("authoriaztion_code", "client_credentials", "refresh_token")
  17. //允许的授权范围,客户端的权限,这里all只是一种标识,可以自定义;为了后续资源服务进行权限控制
  18. .scopes("all")
  19. // false 则跳转到授权页面
  20. .autoApprove(false)
  21. // 授权码模式的回调地址
  22. .redirectUris("http://www.baidu.com");
  23. }
  • .withClient(“myjszl”):指定客户端唯一ID为myjszl
  • .secret():指定秘钥,使用加密算法加密了,秘钥为123
  • .resourceIds(“res1”):给客户端分配的资源权限,对应的是资源服务,比如订单这个微服务就可以看成一个资源,作为客户端肯定不是所有资源都能访问。
  • authorizedGrantTypes():定义认证中心支持的授权类型,总共支持五种
    • 授权码模式:authorization_code
    • 密码模式:password
    • 客户端模式:client_credentials
    • 简化模式:implicit
    • 令牌刷新:refresh_token,这并不是OAuth2的模式,定义这个表示认证中心支持令牌刷新
  • scopes():定义客户端的权限,这里只是一个标识,资源服务可以根据这个权限进行鉴权。
  • autoApprove:是否需要授权,设置为true则不需要用户点击确认授权直接返回授权码
  • redirectUris:跳转的uri

授权码服务配置
使用授权码模式必须配置一个授权码服务,用来办不和删除授权码。当然授权码也支持多种方式存储,如:内存、数据库

  1. /**
  2. * 授权码模式的service,必须注入authorization_code
  3. */
  4. @Bean
  5. public AuthorizationServices authorizationCodeServices() {
  6. // todo 授权码暂存内存
  7. return new InMemoryAuthorizationCodeServices();
  8. }

令牌服务的配置
除了令牌的存储策略需配置,还需配置令牌服务AuthorizationServerTokenServices用来创建、获取、刷新令牌

  1. /**
  2. * 令牌管理服务的配置
  3. */
  4. @Bean
  5. public AuthorizationServerTokenServices tokenServices() {
  6. DefaultTokenServices services = new DefaultTokenServices();
  7. // 客户端配置策略
  8. services.setClientDetailsService(clientDetailsService);
  9. // 支持令牌的刷新
  10. services.setSupportRefreshToken(true);
  11. // 令牌服务
  12. services.setTokenStore(tokenStore);
  13. // access_token过期时间
  14. services.setAccessTokenValiditySeconds(60*60*2);
  15. //refresh_token的过期时间
  16. services.setRefreshTokenValiditySeconds(60*60*24*3);
  17. return services;
  18. }

令牌访问端点的配置

  • 配置了授权码模式所需要的服务,AuthorizationCodeServices
  • 配置了密码模式所需要的AuthenticationManager
  • 配置了令牌管理服务,AuthorizationServerTokenServices
  • 配置/oauth/token申请令牌的uri只允许POST提交。
  1. /**
  2. * 配置令牌访问的端点
  3. */
  4. @Override
  5. public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
  6. endpoints
  7. //授权码模式所需的authorizationCodeServices
  8. .authorizationCodeServices(authorizationCodeServices())
  9. //密码模式所需的authenticationManager
  10. .authenticationManager(authenticationManager)
  11. // 令牌管理服务,无论哪种模式都需要
  12. .tokenServices(tokenServices())
  13. //只允许POST提交访问令牌,uri:/oauth/token
  14. .allowedTokenEndpointRequestMethods(HttpMethod.POST);
  15. }

spring Security框架默认的访问端点有如下6个:

  • /oauth/authorize:获取授权码的端点
  • /oauth/token:获取令牌端点。
  • /oauth/confifirm_access:用户确认授权提交端点。
  • /oauth/error:授权服务错误信息端点。
  • /oauth/check_token:用于资源服务访问的令牌解析端点。
  • /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。

当然如果业务要求需要改变这些默认的端点的url,也是可以修改的,AuthorizationServerEndpointsConfigurer

令牌访问安全约束配置

  1. /**
  2. * 配置令牌访问的安全约束
  3. */
  4. @Override
  5. public void configure(AuthorizationServerSecurityConfigurer security) {
  6. security
  7. // 开启/oauth/token_key验证端口权限访问
  8. .tokenKeyAccess("permitAll()")
  9. // 开启/oauth/check_token验证端口认证权限访问
  10. .checkTokenAccess("permitAll()")
  11. // 表示支持client_id 和 client_secret 做登录认证
  12. .allowFormAuthenticationForClients();
  13. }

参考文档:

  1. https://blog.csdn.net/weixin_44865916/article/details/124752981
  2. https://blog.csdn.net/eason612/article/details/125001594
  3. https://juejin.cn/post/7044360566294446117