背景
基本概念
-
什么是OAuth 2.0
OAuth 2.0 是一套关于授权的行业标准协议。
OAuth 2.0 允许用户授权第三方应用访问他们在另一个服务提供方上的数据,而无需分享他们的凭据(如用户名、密码)。
-
OAuth 2.0 应用场景
OAuth 2.0的应用场景非常广泛,包括但不限于:
-
第三方应用访问用户在其他服务上的信息,例如,一个应用通过OAuth 2.0访问用户在github.com上的数据。
-
第三方应用代表用户执行操作,例如,一个邮件客户端应用通过OAuth 2.0发送用户的电子邮件。
-
第三方应用使用OAuth 2.0实现用户的单点登录,例如,用户可以使用Github账号登录其他应用。
-
OAuth 2.0 的重要性
OAuth 2.0的重要性主要体现在它以简洁、易实现的解决方案,解决用户数据访问和分享的安全问题的。
-
在现代网络环境中,用户的数据通常分散在不同的网络服务中,如何安全、有效地进行数据访问和分享,是一个重要的问题。OAuth 2.0提供了一种标准的解决方案,使得用户可以控制哪些应用可以访问他们的哪些数据,而无需将用户名和密码提供给第三方应用。
OAuth2.0基本流程
-
(A)客户端(Client)向资源所有者(Resource Owner)请求资源授权。授权请求可以直接向资源所有者(Resource Owner)发起,不过最好是通过授权服务器(Authorization Server)间接发起。
-
(B) 客户端(Client)得到资源所有者(Resoure Owner)的授权,这通常是一个凭据;授权的形式和凭据可以有不同的类型。RFC 6749 定义了四种主要的授权类型(下文进一步介绍)
-
(C)客户端(Client)向授权服务器(Authorization Server)出示授权(来自Resource Owenr的)凭据进行身份认证;并申请用于访问资源授权的访问令牌(Access Token)
-
(D) 授权服务器(Authorization Server)对客户端(Client)进行身份验证并验证授权授予,如果通过验证,则颁发访问令牌(Access Token)。
-
(E)客户端(Client)通过向资源服务器(Resource Server)发起令牌(Access Token)验证,请求被保护的资源。
-
(F)资源服务器(Resource Server)验证访问令牌(Access Token);如果通过认证,则返回请求的资源。

四种授权模式
客户端必须得到用户的授权(前面的步骤B),才能获得访问令牌(Access Token)。
OAuth 2.0定义了四种授权方式。
-
授权码模式(Authorization Code)
-
隐式授权模式(Implicit)
-
密码模式(Resource Owner Password Credentials)
-
客户端模式(Client Credentials)
OAuth2.0授权实践
OAuth2.0 客户端模式
客户端模式主要用于没有用户参与的后端服务间访问授权。

OAuth2.0鉴权实践
核心表结构
1、应用注册明细表:用于存储客户端client_id和client_secret,以及客户端的资源resource_ids
CREATE TABLE `oauth_client_details` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`client_id` varchar(48) NOT NULL DEFAULT '' COMMENT '应用id',
`client_secret` varchar(256) NOT NULL DEFAULT '' COMMENT '应用secret',
`client_name` varchar(255) NOT NULL DEFAULT '' COMMENT '应用名称',
`client_type` int(11) NOT NULL DEFAULT '0' COMMENT '应用类型 0 企业内应用 1 企业外应用',
`client_desc` varchar(1024) NOT NULL DEFAULT '' COMMENT '应用描述',
`client_status` int(11) NOT NULL DEFAULT '0' COMMENT '应用状态 0 活跃 1 冻结',
`resource_ids` varchar(256) NOT NULL DEFAULT '' COMMENT '应用资源id,多个资源逗号分隔',
`scope` varchar(256) NOT NULL DEFAULT '' COMMENT '应用权限范围',
`authorized_grant_types` varchar(256) NOT NULL DEFAULT '' COMMENT '支持的授权模式(如密码模式、授权码模式等)',
`web_server_redirect_uri` varchar(256) NOT NULL DEFAULT '' COMMENT '回调 URL,当用户授权后,服务将重定向到此地址',
`authorities` varchar(256) NOT NULL DEFAULT '' COMMENT '客户端拥有的权限',
`access_token_validity` int(11) NOT NULL DEFAULT '7200' COMMENT '访问令牌的有效期(秒)',
`refresh_token_validity` int(11) NOT NULL DEFAULT '14400' COMMENT '刷新令牌的有效期(秒)',
`additional_information` varchar(1024) NOT NULL DEFAULT '{}' COMMENT '其他附加信息,以 JSON 格式存储',
`autoapprove` varchar(256) NOT NULL DEFAULT '' COMMENT '用于指定是否自动批准(即跳过用户授权步骤)某些范围(scope)的请求',
`is_del` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_client_id` (`client_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_modify_time` (`modify_time`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='openapi应用注册明细表'
2、权限token记录表:存储客户端的权限token数据
CREATE TABLE `oauth_access_token` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`client_id` varchar(256) NOT NULL DEFAULT '' COMMENT '应用id',
`token_id` varchar(256) NOT NULL DEFAULT '' COMMENT '从服务器端获取到的access_token的值',
`token` blob COMMENT '二进制的字段, 存储的数据是AccessToken对象序列化后的二进制数据',
`refresh_token` varchar(256) NOT NULL DEFAULT '' COMMENT '刷新令牌,AccessToken过期时获取新的Token',
`user_name` varchar(256) NOT NULL DEFAULT '' COMMENT '颁发 Token 的用户名称(可用于标识用户)',
`authentication_id` varchar(512) NOT NULL DEFAULT '' COMMENT '认证 ID,表示与此 token 相关的具体认证请求',
`authentication` blob COMMENT '认证信息,包含有关用户和客户端的详细信息',
`is_del` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_app_access_token_id` (`client_id`,`token_id`),
KEY `idx_token_id` (`token_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_modify_time` (`modify_time`)
) ENGINE=InnoDB AUTO_INCREMENT=6733 DEFAULT CHARSET=utf8mb4 COMMENT='openapi权限token记录表'
3、刷新权限token记录表:存储客户端的权限refresh_token数据
CREATE TABLE `oauth_refresh_token` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`token_id` varchar(256) NOT NULL DEFAULT '' COMMENT '从服务器端获取到的refresh_token的值',
`token` blob COMMENT '二进制的字段, 存储的数据是RefreshToken对象序列化后的二进制数据',
`authentication` blob COMMENT '认证信息,包含有关用户和客户端的详细信息',
`is_del` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_token_id` (`token_id`),
KEY `idx_create_time` (`create_time`),
KEY `idx_modify_time` (`modify_time`)
) ENGINE=InnoDB AUTO_INCREMENT=148 DEFAULT CHARSET=utf8mb4 COMMENT='openapi刷新权限token记录表'
4、资源表:客户端存储资源数据(本文的资源是接口权限)
CREATE TABLE `wtm_openapi_app_resource` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`app_id` varchar(255) NOT NULL DEFAULT '' COMMENT '应用id',
`path` varchar(255) NOT NULL DEFAULT '' COMMENT '接口路径',
`type` varchar(255) NOT NULL DEFAULT '' COMMENT '接口类型 GET、POST、PUT、PATCH、DELETE',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '状态 0有效 1冻结',
`is_del` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`create_by` varchar(255) NOT NULL DEFAULT 'system' COMMENT '创建人',
`modify_by` varchar(255) NOT NULL DEFAULT 'system' COMMENT '修改人',
PRIMARY KEY (`id`),
KEY `idx_app_path_id` (`app_id`,`path`),
KEY `idx_create_time` (`create_time`),
KEY `idx_modify_time` (`modify_time`)
) ENGINE=InnoDB AUTO_INCREMENT=60 DEFAULT CHARSET=utf8mb4 COMMENT='openapi接口资源'
maven引用
<!-- spring security OAuth2 相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
核心配置类
1、鉴权相关核心配置
对鉴权方式和类型进行配置,核心配置和作用如下:
1、ClientDetailsServiceConfigurer
作用:配置客户端核心类,包括client-id、client-secret、资源id列表、令牌失效时长等。
2、AuthorizationServerEndpointsConfigurer (核心配置)
作用:令牌访问端点配置,配置鉴权核心类、token令牌管理核心类、获取token接口地址、自定义异常处理器、允许访问的http接口类型
3、AuthorizationServerSecurityConfigurer
作用:配置令牌访问的安全约束,配置是否开启权限校验
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 配置认证客户端
* authorization_code(授权码模式)、paassword(密码模式)、client_credentials(客户端模式)、implicit(简化模式) 、refresh_token(令牌刷新)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(wtmClientDetailsService);
/*clients.inMemory() //内存存储
.withClient("client-id") //app_id
.secret(passwordEncoder.encode("client-secret")) //app_secret
.resourceIds("wtm_api_1")
.scopes("read", "write") //授权范围,定义客户端的权限,这里只是一个标识,资源服务可以根据这个权限进行鉴权
.accessTokenValiditySeconds(3600)//访问令牌失效
.refreshTokenValiditySeconds(7200)//刷新令牌失效
.authorizedGrantTypes("authorization_code", "refresh_token", "password", "client_credentials")//授权类型
;*/
}
/**
* 令牌访问端点配置,token存储到数据库
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(wtmAuthenticationManager)
.tokenStore(tokenStore)
.tokenServices(tokenServices())//令牌管理服务
.pathMapping("/oauth/token","/oauth/getAccessToken")//获取token接口url
.exceptionTranslator(wtmWebResponseExceptionTranslator)//自定义异常处理器
.allowedTokenEndpointRequestMethods(HttpMethod.POST);//只允许POST提交访问令牌
}
/**
* 配置令牌访问的安全约束
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")//开启/oauth/token_key验证端口权限访问
.checkTokenAccess("permitAll()")//开启/oauth/check_token验证端口认证权限访问
.allowFormAuthenticationForClients();//支持app_id和app_secret做登录认证
}
}
2、资源相关核心配置
1、ResourceServerSecurityConfigurer
作用:配置资源的鉴权核心类、token核心处理类
2、HttpSecurity
作用:配置哪些接口需要进行OAuth2.0鉴权,哪些接口不需要
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//设置异常处理器
OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
authenticationEntryPoint.setExceptionTranslator(wtmWebResponseExceptionTranslator);
//获得所有WTM资源
resources.stateless(true)//资源ID,仅允许基于令牌的身份验证
.tokenServices(wtmTokenService)
.authenticationManager(wtmAuthenticationManager)
.authenticationEntryPoint(authenticationEntryPoint);
;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//关闭CSRF保护,因为这是一个资源服务器
.authorizeRequests()
.antMatchers("/openapi/**").authenticated()//需要认证的接口
//.antMatchers("/workforce/openapi/**").permitAll()
//.anyRequest().authenticated()//其它请求都需要认证
.anyRequest().permitAll()//公开接口,无需认证
/*.and()
.addFilterBefore(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)*/
;
}
}
3、令牌管理配置
作用:配置令牌生成和管理的方式,OAuth2.0有默认的缓存存储方式、数据库存储方式、也可以重新实现TokenStore方法,从而在数据库存储的同时,也进行缓存,比较符合实际生产过程中的要求。
@Configuration
public class AccessTokenConfig {
/**
* 令牌存储策略
*
* @return
*/
@Bean
public TokenStore tokenStore() {
return wtmTokenStore;
//return new InMemoryTokenStore();
}
}
实战过程中常见问题实现方式
1、如何改变自定义获取token的接口
答:可在AuthorizationServerEndpointsConfigurer中进行接口配置
2、鉴权不通过如何返回自定义的错误码
步骤1: 重写DefaultWebResponseExceptionTranslator异常处理类,对异常进行转换
代码示例
@Service
@Slf4j
public class WTMWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {
/**
* 自定义异常处理翻译器
*
* @param e
* @return
* @throws Exception
*/
@Override
public ResponseEntity translate(Exception e) throws Exception {
log.error("OAuth认证异常:{}", e.getMessage());
ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
Throwable throwable = e.getCause();
int oauthErrorCode = -1;
int httpErrorCode = HttpStatus.BAD_REQUEST.value();
String errorMsg = e.getMessage();
if (throwable instanceof OAuthException) {
OAuthException exc = (OAuthException) throwable;
httpErrorCode = exc.getHttpErrorCode();
oauthErrorCode = Integer.parseInt(exc.getOAuth2ErrorCode());
errorMsg = exc.getMessage();
}
return ResponseEntity.status(httpErrorCode)
.contentType(MediaType.APPLICATION_JSON)
.body(Result.of(oauthErrorCode, errorMsg));
}
}
步骤2: 在AuthorizationServerEndpointsConfigurer中设置自定义的异常处理类wtmWebResponseExceptionTranslator
/**
* 令牌访问端点配置,token存储到数据库
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(wtmAuthenticationManager)
.tokenStore(tokenStore)
.tokenServices(tokenServices())//令牌管理服务
.pathMapping("/oauth/token","/oauth/getAccessToken")//获取token接口url
.exceptionTranslator(wtmWebResponseExceptionTranslator)//自定义异常处理器
.allowedTokenEndpointRequestMethods(HttpMethod.POST);//只允许POST提交访问令牌
}
步骤3:在资源配置类中设置自定义异常处理器
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//设置异常处理器
OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
authenticationEntryPoint.setExceptionTranslator(wtmWebResponseExceptionTranslator);
//获得所有WTM资源
resources.stateless(true)//资源ID,仅允许基于令牌的身份验证
.tokenServices(wtmTokenService)
.authenticationManager(wtmAuthenticationManager)
.authenticationEntryPoint(authenticationEntryPoint);
;
}
彩蛋:授权码模式第三方登录
流程:

举例:

221

被折叠的 条评论
为什么被折叠?



