1. 授权码模式(Authorization Code Grant)
如上图,我们可以看到此流程可大致分为三大部分:
- 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)
隐式授权模式大致可分为两部分:
- Client Side:用户+客户端与授权服务器的交互
- Check Access Token:客户端与资源服务器之间的交互 + 资源服务器与授权服务器之间的交互
用一句话概括隐式授权模式授权流程: 客户端让用户登录授权服务器换token,客户端使用token访问资源。
Client Side
- 这个客户端可以是浏览器,APP。
- 客户端将client_id + client-secret + 授权模式标识(grant_type)+ 回调地址(redirect_uri)拼成url访问授权服务器授权端点。
- 授权服务器跳转用户登录界面,用户登录。
- 用户授权。
-
Check Access Token
客户端在请求头中添加token,访问资源服务器。
- 资源服务器收到请求,先调用校验token的方法(可以是远程调用授权服务器校验端点,也可以直接访问授权存储器手动校对)。
- 资源服务器校验成功,返回资源。
2. OAuth2.0认证方案
- 网关负责转发,认证和鉴权都在每个微服务
- 统一在网关层进行认证和鉴权,微服务只负责业务
优劣: 方案1:每个微服务都需要一套认证和鉴权,代码会严重耦合;且无法实现同一的认证,开发难度大涉及面广。 方案2:实现统一的认证鉴权,微服务只需专注业务;代码耦合度低,便于后期扩展。
2.1 四大角色
- 客户端: 需要访问微服务资源
- 网关: 负责转发、认证、鉴权
- OAuth2.0授权服务:负责认证授权颁发令牌
- 微服务集合:提供资源的一系列服务
2.2 授权码模式
提前向认证中心申请的几个参数如下:
- client_id:客户端唯一id,认证中心颁发的唯一标识。
- client_secret:客户端的秘钥,相当于密码。
- scope:客户端的权限。
- 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认证中心的配置类,需要满足以下两点:
- 继承AuthorizationServerConfigurerAdapter
- 标注 @EnableAuthorizationServer 注解
/**
* 认证中心配置
* @EnableAuthorizationServer:这个注解标注,是认证中心
* 继承AuthorizationServerConfigurerAdapter
*/
public class AuthorizationServerConfig extends AuthoriaztionServerConfigurerAdapter {
// 令牌端点安全约束配置,如 /oauth/token对那些开放
@Override
public void configure(AuthorizationServerSecurityCOnfigurer security) throws Exception {}
// 客户端详情配置,例如秘钥、唯一ID
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}
// 令牌访问端点的配置
@Override
public void configure(AuthorizationServerEndpointConfigurer endpoints) throws Exception {}
}
客户端配置:
并非所有客户端,都有权限向认证中心申请令牌的;一些必要认证中心分配给你,如:客户端唯一ID、秘钥、权限。客户端配置也支持多个方式,如:内存、数据库。对接接口为:org.springframework.security.oauth2.provider.ClientDetailsService
public interface ClientDetailsService {
/**
* Load a client by the client id. This method must not return null
* @param clientId the client ID
* @return The client details (never null)
* @throws ClientRegistrationException if the client account is locked,expired,disabled or invalid for any other reason.
*/
clientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;
}
/**
* 配置客户端详情,并不是所有客户端都能接入
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO: 内存方式演示
clients.inMemory()
// 客户端ID
.withClient("myjszl")
// 客户端秘钥
.secret(new BCryptPasswordEncoder().encode("123ta"))
// 资源id,唯一,可以设置多个
.resourceIds("res1")
// 授权模式:1. authorization_code(授权码模式)、password(密码模式)、client_credentials(客户端模式)、implicit(简化模式)
// refresh_token -- token过期,需要用这个去重新请求
.authorizedGrantTypes("authoriaztion_code", "client_credentials", "refresh_token")
//允许的授权范围,客户端的权限,这里all只是一种标识,可以自定义;为了后续资源服务进行权限控制
.scopes("all")
// false 则跳转到授权页面
.autoApprove(false)
// 授权码模式的回调地址
.redirectUris("http://www.baidu.com");
}
- .withClient(“myjszl”):指定客户端唯一ID为myjszl
- .secret():指定秘钥,使用加密算法加密了,秘钥为123
- .resourceIds(“res1”):给客户端分配的资源权限,对应的是资源服务,比如订单这个微服务就可以看成一个资源,作为客户端肯定不是所有资源都能访问。
- authorizedGrantTypes():定义认证中心支持的授权类型,总共支持五种
- 授权码模式:authorization_code
- 密码模式:password
- 客户端模式:client_credentials
- 简化模式:implicit
- 令牌刷新:refresh_token,这并不是OAuth2的模式,定义这个表示认证中心支持令牌刷新
- scopes():定义客户端的权限,这里只是一个标识,资源服务可以根据这个权限进行鉴权。
- autoApprove:是否需要授权,设置为true则不需要用户点击确认授权直接返回授权码
- redirectUris:跳转的uri
授权码服务配置
使用授权码模式必须配置一个授权码服务,用来办不和删除授权码。当然授权码也支持多种方式存储,如:内存、数据库
/**
* 授权码模式的service,必须注入authorization_code
*/
@Bean
public AuthorizationServices authorizationCodeServices() {
// todo 授权码暂存内存
return new InMemoryAuthorizationCodeServices();
}
令牌服务的配置
除了令牌的存储策略需配置,还需配置令牌服务AuthorizationServerTokenServices用来创建、获取、刷新令牌
/**
* 令牌管理服务的配置
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
// 客户端配置策略
services.setClientDetailsService(clientDetailsService);
// 支持令牌的刷新
services.setSupportRefreshToken(true);
// 令牌服务
services.setTokenStore(tokenStore);
// access_token过期时间
services.setAccessTokenValiditySeconds(60*60*2);
//refresh_token的过期时间
services.setRefreshTokenValiditySeconds(60*60*24*3);
return services;
}
令牌访问端点的配置
- 配置了授权码模式所需要的服务,AuthorizationCodeServices
- 配置了密码模式所需要的AuthenticationManager
- 配置了令牌管理服务,AuthorizationServerTokenServices
- 配置/oauth/token申请令牌的uri只允许POST提交。
/**
* 配置令牌访问的端点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
//授权码模式所需的authorizationCodeServices
.authorizationCodeServices(authorizationCodeServices())
//密码模式所需的authenticationManager
.authenticationManager(authenticationManager)
// 令牌管理服务,无论哪种模式都需要
.tokenServices(tokenServices())
//只允许POST提交访问令牌,uri:/oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
spring Security框架默认的访问端点有如下6个:
- /oauth/authorize:获取授权码的端点
- /oauth/token:获取令牌端点。
- /oauth/confifirm_access:用户确认授权提交端点。
- /oauth/error:授权服务错误信息端点。
- /oauth/check_token:用于资源服务访问的令牌解析端点。
- /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。
当然如果业务要求需要改变这些默认的端点的url,也是可以修改的,AuthorizationServerEndpointsConfigurer
令牌访问安全约束配置
/**
* 配置令牌访问的安全约束
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
// 开启/oauth/token_key验证端口权限访问
.tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("permitAll()")
// 表示支持client_id 和 client_secret 做登录认证
.allowFormAuthenticationForClients();
}
参考文档: