OAuth2.0客户端模式鉴权核心配置实践

背景

基本概念

  1. 什么是OAuth 2.0

OAuth 2.0 是一套关于授权的行业标准协议。

OAuth 2.0 允许用户授权第三方应用访问他们在另一个服务提供方上的数据,而无需分享他们的凭据(如用户名、密码)。

  1. OAuth 2.0 应用场景

OAuth 2.0的应用场景非常广泛,包括但不限于:

  • 第三方应用访问用户在其他服务上的信息,例如,一个应用通过OAuth 2.0访问用户在github.com上的数据。

  • 第三方应用代表用户执行操作,例如,一个邮件客户端应用通过OAuth 2.0发送用户的电子邮件。

  • 第三方应用使用OAuth 2.0实现用户的单点登录,例如,用户可以使用Github账号登录其他应用。

  1. 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);
    ;
}

彩蛋:授权码模式第三方登录

流程:

举例:

参考:https://blog.51cto.com/u_13540373/10826160

https://zhuanlan.zhihu.com/p/717770658

### 使用OAuth 2.0与JWT进行的方法 #### OAuth 2.0与JWT概述 OAuth 2.0提供了一种安全、高效、可扩展的认证和授框架,能够支持多种不同的授模式,满足不同应用场景的需求。从资源拥有者、客户端到授服务器和资源服务器,每个角色的分工清晰,保证了数据访问的安全性与隐私保护[^2]。 JSON Web Token (JWT) 是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式用于在网络应用程序之间传输声明信息。这些声明可以是关于用户的身份验证和其他必要的限信息。JWT由三部分组成:头部、载荷和签名[^4]。 #### 流程说明 当使用OAuth 2.0结合JWT作为令牌时,整个过程如下: 1. **获取授码** 客户端向授服务器发起请求以获得用户的同意来访问某些资源。如果用户批准,则返回给客户端一个临时性的授码。 2. **交换访问令牌** 接收到授码之后,客户端会将其发送回授服务器换取实际可用的访问令牌(即JWT形式)。此时可能会同时发放刷新令牌以便后续更新失效后的访问令牌。 3. **携带令牌访问API** 获得有效的JWT后,每次调用受保护的服务接口时都需要附带此令牌。服务端会对传入的JWT进行解析并校验其合法性,确认无误后再处理业务逻辑。 4. **令牌续期** 当接近过期时间前,利用之前得到的刷新令牌再次向授服务器申请新的访问令牌,保持持续登录状态而不必频繁重新输入凭证。 ```python import jwt from datetime import datetime, timedelta def create_jwt_token(secret_key, user_id): payload = { 'user_id': user_id, 'exp': datetime.utcnow() + timedelta(hours=1), # 设置token有效期为1小时 } token = jwt.encode(payload, secret_key, algorithm='HS256') return token.decode('utf-8') secret_key = "your_secret_key" user_id = 12345 print(create_jwt_token(secret_key, user_id)) ``` 上述代码展示了如何创建一个简单的JWT实例。请注意这只是一个基础示例,在真实环境中还需要考虑更多因素如错误处理等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值