OAuth2.0 详解

news2024/10/6 10:35:19

OAuth2.0介绍

OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方 应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他 们数据的所有内容。OAuth在全世界得到广泛应用,目前的版本是2.0版。

OAuth协议:https://tools.ietf.org/html/rfc6749

协议特点:

  • 简单:不管是OAuth服务提供者还是应用开发者,都很易于理解与使用;
  • 安全:没有涉及到用户密钥等信息,更安全更灵活;
  • 开放:任何服务提供商都可以实现OAuth,任何软件开发商都可以使用OAuth;

应用场景

  • 原生app授权:app登录请求后台接口,为了安全认证,所有请求都带token信息,如果登录验证、 请求后台数据。
  • 前后端分离单页面应用:前后端分离框架,前端请求后台数据,需要进行oauth2安全认证
  • 第三方应用授权登录,比如QQ,微博,微信的授权登录。

基本概念

  • Third-party application:第三方应用程序,又称"客户端"(client),即例子中的"豆瓣"。
  • HTTP service:HTTP服务提供商,简称"服务提供商",即例子中的qq。
  • Resource Owner:资源所有者,又称"用户"(user)。
  • User Agent:用户代理,比如浏览器。
  • Authorization server:授权服务器,即服务提供商专门用来处理认证授权的服务器。
  • Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与授权服务器,可以是同一台服务器,也可以是不同的服务器。

OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务提供商"进行交互。

优缺点

优点:

  • 更安全,客户端不接触用户密码,服务器端更易集中保护
  • 广泛传播并被持续采用
  • 短寿命和封装的token
  • 资源服务器和授权服务器解耦
  • 集中式授权,简化客户端
  • HTTP/JSON友好,易于请求和传递token
  • 考虑多种客户端架构场景
  • 客户可以具有不同的信任级别

缺点:

  • 协议框架太宽泛,造成各种实现的兼容性和互操作性差
  • 不是一个认证协议,本身并不能告诉你任何用户信息。

Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization)。Spring Security在架构上将认证与授权分离,并提供了扩展点。

认证(Authentication) :用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。

授权(Authorization): 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

将OAuth2和Spring Security集成,就可以得到一套完整的安全解决方案。我们可以通过Spring Security OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个access_token来从资源服务器请求数据。

Spring Security + Oauth2.0整体架构

  • Authorize Endpoint :授权端点,进行授权
  • Token Endpoint :令牌端点,经过授权拿到对应的Token
  • Introspection Endpoint :校验端点,校验Token的合法性
  • Revocation Endpoint :撤销端点,撤销授权

  1. 用户访问,此时没有Token。Oauth2RestTemplate会报错,这个报错信息会被Oauth2ClientContextFilter捕获并重定向到授权服务器。
  2. 授权服务器通过Authorization Endpoint进行授权,并通过AuthorizationServerTokenServices生成授权码并返回给客户端。
  3. 客户端拿到授权码去授权服务器通过Token Endpoint调用AuthorizationServerTokenServices生成Token并返回给客户端
  4. 客户端拿到Token去资源服务器访问资源,一般会通过Oauth2AuthenticationManager调用ResourceServerTokenServices进行校验。校验通过可以获取资源。

OAuth2.0模式

运行流程

OAuth 2.0的运行流程如下图,摘自RFC 6749。

(A)用户打开客户端以后,客户端要求用户给予授权。 (B)用户同意给予客户端授权。 (C)客户端使用上一步获得的授权,向认证服务器申请令牌。 (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。 (E)客户端使用令牌,向资源服务器申请获取资源。 (F)资源服务器确认令牌无误,同意向客户端开放资源。

引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>    


授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0一共分成四种授权类型(authorization grant)

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

授权码模式和密码模式比较常用。

第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

授权码模式

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

适用场景:目前主流的第三方验证都是采用这种模式

主要流程:

(A)用户访问客户端,后者将前者导向授权服务器。

(B)用户选择是否给予客户端授权。

(C)用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

配置授权服务器

注意:实际项目中clinet_id 和client_secret 是配置在数据库中,省略spring security相关配置,可以参考

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore)  //指定token存储到redis
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        // 基于内存模式,这里只是演示,实际生产中应该基于数据库
        clients.inMemory()
                //配置client_id
                .withClient("client")
                //配置client_secret
                .secret(passwordEncoder.encode("123123"))
                //配置访问token的有效期
                .accessTokenValiditySeconds(3600)
                //配置刷新token的有效期
                .refreshTokenValiditySeconds(864000)
                //配置redirect_uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                //配置申请的权限范围
                .scopes("all")
                //配置grant_type,表示授权类型  authorization_code: 授权码
                .authorizedGrantTypes("authorization_code");


    }
}

客户端信息,和授权码都是存储在了内存中,一旦认证服务宕机,那客户端的认证信息也随之消失,而且客户端信息是在程序中写死的,维护起来及不方便,每次修改都需要重启服务,如果向上述信息都存于数据库中便可以解决上面的问题,其中数据我们可以自定义存到

noSql

或其他数据库中。

数据库存储模式

首先在数据库中新建存储客户端信息,及授权码的表:

#客户端信息
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('client', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all', 'authorization_code,password,refresh_token,client_credentials,implicit', 'http://www.baidu.com', NULL, 3600, 864000, NULL, NULL);

#授权码
CREATE TABLE `oauth_code`  (
  `code` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.2</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.6</version>
</dependency>

/**
 * 授权服务器
 *
 * 授权码模式基于数据库
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig1 extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Autowired
    @Qualifier("jdbcClientDetailsService")
    private ClientDetailsService clientDetailsService;

    @Bean("jdbcClientDetailsService")
    public ClientDetailsService clientDetailsService() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    //设置授权码模式的授权码如何存取
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }




    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore)  //指定token存储到redis
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求


    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单认证
        security
                .tokenKeyAccess("permitAll()")                    //oauth/token_key是公开
                .checkTokenAccess("permitAll()")                  //oauth/check_token公开
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //授权码模式
        //http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
        // 简化模式
//        http://localhost:8080/oauth/authorize?response_type=token&client_id=client&redirect_uri=http://www.baidu.com&scope=all

        clients.withClientDetails(clientDetailsService);


    }
}

配置资源服务器

@Configuration
@EnableResourceServer
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().requestMatchers().antMatchers("/user/**");

    }

}

配置 spring security

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().permitAll()
                .and().authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated()
                .and().logout().permitAll()
                .and().csrf().disable();
    }
}

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("test",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

1、A网站(客户端)提供一个链接,用户点击后就会跳转到 B (授权服务器)网站,授权用户数据给 A 网站使用。

 127.0.0.1:9999/oauth/authorize?   
 response_type=code&            # 表示授权类型,必选项,此处的值固定为"code"
 client_id=CLIENT_ID&           # 表示客户端的ID,必选项
 redirect_uri=CALLBACK_URL&     # redirect_uri:表示重定向URI,可选项
 scope=read                     # 表示申请的权限范围,可选项 read只读  

http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all

2、用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码

登录:

授权:

选择 authorize ,获取授权码,浏览器返回:https://www.baidu.com/?code=PVpEEw

https://a.com/callback?code=AUTHORIZATION_CODE    #code参数就是授权码                   

如果使用数据库模式:

3、A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。 用户不可见,服务端行为

127.0.0.1:8080/oauth/token? 
client_id=CLIENT_ID& 
client_secret=CLIENT_SECRET&     # client_id和client_secret用来让 B 确认 A 的身份,client_secret参数是保密的,因此只能在后端发请求 
grant_type=authorization_code&   # 采用的授权方式是授权码
code=AUTHORIZATION_CODE&         # 上一步拿到的授权码 
redirect_uri=CALLBACK_URL        # 令牌颁发后的回调网址         

4、B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,返回数据。

 {    
   "access_token":"ACCESS_TOKEN",     # 令牌
   "token_type":"bearer",
   "expires_in":2592000,
   "refresh_token":"REFRESH_TOKEN",
   "scope":"read",
   "uid":100101,
   "info":{...}
 }

此时redis中会存储token

密码模式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

适用场景:公司搭建的授权服务器

(A)用户向客户端提供用户名和密码。

(B)客户端将用户名和密码发给授权服务器,向后者请求令牌。

(C)授权服务器确认无误后,向客户端提供访问令牌。

配置 spring security

增加AuthenticationManager

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private UserService userService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 获取用户信息
        auth.userDetailsService(userService);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().permitAll()
                .and().authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .anyRequest()
                .authenticated()
                .and().logout().permitAll()
                .and().csrf().disable();
    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        // oauth2 密码模式需要拿到这个bean
        return super.authenticationManagerBean();
    }
}


@Service
public class UserService implements UserDetailsService {

    @Autowired
    @Lazy
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = passwordEncoder.encode("123456");
        return new User("jack",password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
}

配置授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig2 extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManagerBean;

    @Autowired
    private UserService userService;

    @Autowired
    private TokenStore tokenStore;


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
                .tokenStore(tokenStore)  //指定token存储到redis
                .reuseRefreshTokens(false)  //refresh_token是否重复使用
                .userDetailsService(userService) //刷新令牌授权包含对用户信息的检查
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单认证
        security.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
                //配置client_id
                .withClient("client")
                //配置client-secret
                .secret(passwordEncoder.encode("123123"))
                //配置访问token的有效期
                .accessTokenValiditySeconds(3600)
                //配置刷新token的有效期
                .refreshTokenValiditySeconds(864000)
                //配置redirect_uri,用于授权成功后跳转
                .redirectUris("http://www.baidu.com")
                //配置申请的权限范围
                .scopes("all")
                /**
                 * 配置grant_type,表示授权类型
                 * authorization_code: 授权码模式
                 * implicit: 简化模式
                 * password: 密码模式
                 * client_credentials: 客户端模式
                 * refresh_token: 更新令牌
                 */
                .authorizedGrantTypes("authorization_code","implicit","password","client_credentials","refresh_token");
    }


}

如需支持数据库模式,只需要把授权服务器在授权码模式的基础上增加AuthenticationManager,关键代码如下:

//AuthorizationServerConfig1
@Autowired
private AuthenticationManager authenticationManagerBean;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
    .tokenStore(tokenStore)  //指定token存储到redis
    .authorizationCodeServices(authorizationCodeServices)//授权码服务
    .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求


}

获取token:

https://oauth.b.com/oauth/token?
  grant_type=password&       # 授权方式是"密码式"
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID&
  client_secret=123123&
  scope=all

客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行 授权。

适用于没有前端的命令行应用,即在命令行下请求令牌。一般用来提供给我们完全信任的服务器端服务。

它的步骤如下:

(A)客户端向授权服务器进行身份认证,并要求一个访问令牌。

(B)授权服务器确认无误后,向客户端提供访问令牌。

A 应用在命令行向 B 发出请求。

https://oauth.b.com/token? 
grant_type=client_credentials& 
client_id=CLIENT_ID& 
client_secret=CLIENT_SECRET              

配置授权服务器

在grant_type增加client_credentials来支持客户端模式。

clients.inMemory()
        //配置client_id
        .withClient("client")
        //配置client-secret
        .secret(passwordEncoder.encode("123123"))
        //配置访问token的有效期
        .accessTokenValiditySeconds(3600)
        //配置刷新token的有效期
        .refreshTokenValiditySeconds(864000)
        //配置redirect_uri,用于授权成功后跳转
        .redirectUris("http://www.baidu.com")
        //配置申请的权限范围
        .scopes("all")
        /**
         * 配置grant_type,表示授权类型
         * authorization_code: 授权码模式
         * implicit: 简化模式
         * password: 密码模式
         * client_credentials: 客户端模式
         * refresh_token: 更新令牌
         */
        .authorizedGrantTypes("authorization_code","implicit","password","client_credentials","refresh_token");

获取令牌:

简化(隐式)模式

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌,这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)

简化模式不通过第三方应用程序的服务器,直接在浏览器中向授权服务器申请令牌,跳过了"授权码"这个步骤,所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

它的步骤如下:

(A)客户端将用户导向授权服务器。

(B)用户决定是否给于客户端授权。

(C)假设用户给予授权,授权服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。

(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。

(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。

(F)浏览器执行上一步获得的脚本,提取出令牌。

(G)浏览器将令牌发给客户端。

A 网站提供一个链接,要求用户跳转到 B 网站,授权用户数据给 A 网站使用。

https://b.com/oauth/authorize?
>   response_type=token&          # response_type参数为token,表示要求直接返回令牌
>   client_id=CLIENT_ID&
>   redirect_uri=CALLBACK_URL&
>   scope=read

用户跳转到 B 网站,登录后同意给予 A 网站授权。这时,B 网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌作为 URL 参数,传给 A 网站。

https://a.com/callback#token=ACCESS_TOKEN     #token参数就是令牌,A 网站直接在前端拿到令牌。 

配置授权服务器

只需要在配置grant_type增加implicit

clients.inMemory()
        //配置client_id
        .withClient("client")
        //配置client-secret
        .secret(passwordEncoder.encode("123123"))
        //配置访问token的有效期
        .accessTokenValiditySeconds(3600)
        //配置刷新token的有效期
        .refreshTokenValiditySeconds(864000)
        //配置redirect_uri,用于授权成功后跳转
        .redirectUris("http://www.baidu.com")
        //配置申请的权限范围
        .scopes("all")
        //配置grant_type,表示授权类型 授权码:authorization_code implicit: 简化模式
        .authorizedGrantTypes("authorization_code","implicit");

http://localhost:8080/oauth/authorize?client_id=client&response_type=token&scope=all&redirect_uri=http://www.baidu.com

登录之后进入授权页面,确定授权后浏览器会重定向到指定路径,并以Hash的形式存放在重定向uri的fargment中:

如果想要支持数据库模式,配置同授权码模式一样,只需要在oauth_client_details表的authorized_grant_types配置上implicit即可。

INSERT INTO `oauth`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('gateway', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all', 'authorization_code,password,refresh_token,implicit', 'http://www.baidu.com', NULL, 3600, 864000, NULL, NULL);

令牌的使用

A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。

此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。

也可以添加请求参数access_token请求数据

localhost/user/getCurrentUser?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxx

更新令牌

令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。

具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。

https://b.com/oauth/token?
>   grant_type=refresh_token&    # grant_type参数为refresh_token表示要求更新令牌
>   client_id=CLIENT_ID&
>   client_secret=CLIENT_SECRET&
>   refresh_token=REFRESH_TOKEN    # 用于更新令牌的令牌

基于redis存储Token

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

redis配置类

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
}

在授权服务器配置中指定令牌的存储策略为Redis

@Autowired
private TokenStore tokenStore;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
        .tokenStore(tokenStore)  //指定token存储到redis
        .reuseRefreshTokens(false)  //refresh_token是否重复使用
        .userDetailsService(userService) //刷新令牌授权包含对用户信息的检查
        .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【日常系列】LeetCode《26·动态规划1》

数据规模->时间复杂度 <10^4 &#x1f62e;(n^2) <10^7:o(nlogn) <10^8:o(n) 10^8<:o(logn),o(1) lc 509【剑指 10-1】&#xff1a;斐波那契数列问题 - 动态规划入门 https://leetcode.cn/problems/fibonacci-number/ 提示&#xff1a; 0 < n < 30 #方案…

WebSocket概念及实现简易聊天室

WebSocket实现简易聊天室 1 WebSocket介绍 网络通信协议是HTML5开始提供的一个单个TCP连接上进行全双工通信协议 1.1 诞生原因&#xff08;http无状态、无连接&#xff09; ①HTTP协议&#xff1a; 由于HTTP协议是一种无状态、无连接、单向的应用层协议&#xff0c;通信请求只…

Java反射机制是什么?

Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前&#xff0c;大家应该先了解两个概念&#xff0c;编译期和运行期。 编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些…

Qt Creator添加自定义向导

文章目录一、前言二、前置说明三、wizard.json解析3.1、宏观结构3.2、微观解释3.2.1、向导信息3.2.2、定义变量3.2.3、页面定义3.2.4、文件生成四、实战一、前言 在Qt Creator中&#xff0c;当我们选择新建时&#xff0c;Qt自带了很多选项&#xff1b; 如果我们在开发过程中&a…

软件测试——使用mujava测试过程中

文章目录下载对应安装包步骤二、修改代码第二部分&#xff0c;修改对应测试文件下载对应安装包 分别下载junit.jar、mujava.jar和openjava.jar三个包&#xff0c;并设定对应系统路径。三个安装包的下载路径以mujava.jar为例子 设置系统环境变量&#xff0c;计算机》属性》高级…

golang实现一个linux命令ls命令(命令行工具构建)

希望2023可以听到这些话&#xff1a; 恭喜你得到了这份工作恭喜你的建议被采用了恭喜你被录取了恭喜你的考试顺利通过了恭喜你上岸了恭喜你升职了恭喜你加薪了恭喜你体检结果一切正常在这篇文章下面许个愿吧&#xff01; ls 命令 要实现ls&#xff0c;首先先我们复习一下ls命令…

FPGA知识汇集-ASIC向FPGA的移植

ASIC原型验证是整个验证环节中非常重要的步骤之一&#xff0c;也是将ASIC的代码移植到FPGA平台上最重要的原因&#xff0c;本文章的意义在于&#xff1a; 对于系统构架师&#xff0c;将帮助他们在选择商用模拟器还是自行设计方案之间做出更好的选择&#xff1b; 对于逻辑工程师…

一文读懂Go Http Server原理

hello大家好呀&#xff0c;我是小楼&#xff0c;这是系列文《Go底层原理剖析》的第二篇&#xff0c;依旧是分析 Http 模块&#xff0c;话不多说&#xff0c;开始。 从一个 Demo 入手 俗话说万事开头难&#xff0c;但用 Go 实现一个 Http Server 真不难&#xff0c;简单到什么程…

2、Eclipse安装与使用

目录 一、简介 二、下载 三、Eclipse安装 &#xff08;1&#xff09;找到Eclipse安装包&#xff0c;右键【以管理员身份运行】 2.因为是写Java程序&#xff0c;所以安装第一个喽 3.安装位置设置&#xff08;不建议C盘安装哦&#xff09;&#xff0c;点击【INSTALL】 3.点…

day02 数组 | 977、有序数组的平方 209、长度最小的子数组 59、螺旋矩阵II

1、题目 977、有序数组的平方 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 示例 1&#xff1a; 输入&#xff1a;nums [-4,-1,0,3,10] 输出&#xff1a;[0,1,9,16,100] 解释&#xff1…

github Page博客速度优化+Cloudflare_https两端配置+解决重定向次数过多问题

网站加速调优 自从加了CDN之后我的博客偶尔会报错”重定向次数过多“ 症状&#xff1a; XXX.XXX.XXX 将您重定向的次数过多。 尝试清除 Cookie. ERR_TOO_MANY_REDIRECTS 可能原因 参考阿里云cdn解决方案https://help.aliyun.com/document_detail/451418.html但是cloudflar…

【UCIe】UCIe Stall 介绍

&#x1f525;点击查看精选 UCIe 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0…

【SAP Hana】SAP Hana存储过程开发

SAP Hana存储过程开发Hana 存储过程1、创建&更新语法2、删除语法3、调用语法4、实例演示&#xff08;1&#xff09;存储过程入门&#xff08;2&#xff09;指定 DEFAULT SCHEMA&#xff08;3&#xff09;定义内部变量&#xff0c;执行多个查询&#xff08;4&#xff09;定义…

【Javascript】高阶函数,JSON,forEach,map,filter,reduce等高阶函数,函数绑定

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录高阶函数箭头函数apply函数JSONfilter函数map函数总结reduce函数find/findIndex函数every/some函…

【Android安全】frida-gum教程

frida-gum教程 frida-gum概述 frida-gum是基于inline-hook实现的 提供的功能&#xff1a;代码跟踪&#xff08;Stalker&#xff09;、内存访问监控&#xff08;MemoryAccessMonitor&#xff09;、符号查找、栈回溯实现、内存扫描、动态代码生成和重定位 inline Hook 原理 .…

MySQL中常见的数值函数

第一个 ceil(x)&#xff1a;向上取整 取整&#xff0c;顾名思义就是取整数&#xff1b; 向上取整是只要小数位不是 0&#xff0c;都会向上进 1 位整数。 案例 1&#xff1a; select ceil(9.2); 解析&#xff1a; 9.2 向上取一位整数&#xff0c;就是 10。 查询结果&#xff…

全屋智能品牌很多坑!选华为还是卡特加特数字家庭?技术角度分析亮了

市面上的智能家居品牌有很多&#xff0c;但拥有成熟全屋智能系统、完善产品体系&#xff0c;以及线下线上闭环销售渠道的则没几个。细数下来只有手机厂商华为、小米&#xff0c;以及科创型企业欧瑞博、摩根、UIOT和卡特加特&#xff0c;而其中以华为和卡特加特为代表的&#xf…

自定义类型:结构体,枚举,联合(3)

TIPS 1. 2. 枚举 1. 枚举顾名思义就是一一列举可能的取值&#xff0c;比如一周的星期一到星球天是有限的七天&#xff0c;可以一一列举。有比如性别&#xff0c;月份。 2. 像这种容易并且可以被一一列举的数据我们就可以定义为枚举类型。 枚举类型 1. 枚举的关键字为e…

【2】成功安装部署K8s集群

目录 1、安装方式 2、环境初始化 【1】主机名解析 【2】时间同步 【3】禁用iptables和firewalld服务&#xff08;三台都要设置&#xff09; 【4】禁用selinux&#xff08;三台都要设置&#xff09; 【5】禁用swap分区 【6】修改linux的内核参数 3、安装docker 【1】安…

自定义类型:结构体,枚举,联合(详解版)

&#x1f40b;自定义类型&#xff1a;结构体&#xff0c;枚举&#xff0c;联合&#x1f996;结构体&#x1f414;1.结构体的声明&#x1f424;1.1 结构的基础知识&#x1f424;1.2 结构的声明&#x1f424;1.3 特殊的声明&#x1f424;1.4 结构的自引用&#x1f424;1.5 结构体…