Spring Security OAuth2.0(三)-----基于Redis存储和JDBC存储

问题

令牌往哪里存?
客户端信息入库
第三方应用优化

1.令牌往哪里存?

在我们配置授权码模式的时候,有两个东西当时存在了内存中:

  • InMemoryAuthorizationCodeServices 这个表授权码存在内存中。
  • InMemoryTokenStore 表示生成的令牌存在内存中。
    授权码用过一次就会失效,存在内存中没什么问题,但是令牌,我们实际上还有其他的存储方案。

我们所使用的 InMemoryTokenStore 实现了 TokenStore 接口,我们来看下 TokenStore 接口的实现类:
在这里插入图片描述

可以看到,我们有多种方式来存储 access_token。

  1. InMemoryTokenStore,这是我们之前使用的,也是系统默认的,就是将 access_token 存到内存中,单机应用这个没有问题,但是在分布式环境下不推荐。
  2. JdbcTokenStore,看名字就知道,这种方式令牌会被保存到数据中,这样就可以方便的和其他应用共享令牌信息。
  3. JwtTokenStore,这个其实不是存储,因为使用了 jwt 之后,在生成的 jwt 中就有用户的所有信息,服务端不需要保存,这也是无状态登录,关于 OAuth2 结合 JWT 的用法,松哥本系列未来的文章中,也会详细介绍,这里就不再多说。
  4. RedisTokenStore,这个很明显就是将 access_token 存到 redis 中。
    JwkTokenStore,将 access_token 保存到 JSON Web Key。
    虽然这里支持的方案比较多,但是我们常用的实际上主要是两个,RedisTokenStore 和 JwtTokenStore,JwtTokenStore 的比较复杂,我会在后面专门写文章来单独介绍,这里先来跟大家演示存入 RedisTokenStore。

首先我们启动一个 Redis 服务,然后给 auth-server 添加 Redis 依赖:

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

依赖添加成功后,在 application.properties 中添加 redis 配置:

spring.redis.host=
spring.redis.port=6379
spring.redis.password=

配置完成后,我们修改 TokenStore 的实例,如下:

package com.xql.authorization_server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
public class AccessTokenConfig {

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Bean
    TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}

然后分别启动项目,走一遍第三方登录流程,然后我们发现,派发的 access_token 在 redis 中也有一份:
Spring Security OAuth2.0(三)-----基于Redis存储和JDBC存储_第1张图片

2.客户端信息入库

所以我们要将客户端信息存入数据库中。

客户端信息入库涉及到的接口主要是 ClientDetailsService,这个接口主要有两个实现类,如下:
Spring Security OAuth2.0(三)-----基于Redis存储和JDBC存储_第2张图片
InMemoryClientDetailsService 就不多说了,这是存在内存中的。如果要存入数据库,很明显是 JdbcClientDetailsService,我们来大概看下 JdbcClientDetailsService 的源码,就能分析出数据库的结构了:

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ID,唯一标识',
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端访问秘钥,BCryptPasswordEncoder加密算法加密',
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '可访问资源id(英文逗号分隔)',
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权范围(英文逗号分隔)',
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权类型(英文逗号分隔)',
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '重定向uri',
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '@PreAuthorize("hasAuthority(''admin'')")可以在方法上标志 用户或者说client 需要说明样的权限\r\n\n\n指定客户端所拥有的Spring Security的权限值\r\n(英文逗号分隔)',
  `access_token_validity` int NOT NULL COMMENT '令牌有效期(单位:秒)',
  `refresh_token_validity` int NOT NULL COMMENT '刷新令牌有效期(单位:秒)',
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '预留字段,在Oauth的流程中没有实际的使用(JSON格式数据)',
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '设置用户是否自动Approval操作, 默认值为 ''false''\r\n可选值包括 ''true'',''false'', ''read'',''write''.\r\n该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为''true''或支持的scope值,则会跳过用户Approve的页面, 直接授权',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;

接下来我们将一开始定义的客户端的关键信息存入数据库中,如下:

在这里插入图片描述

xql	$2a$10$SMP8P9hRmdTFzMfZBXksuuDNBm7AV9q1SFomvqc9FR38e/MMR7XiC	res1	all	authorization_code,password, client_credentials, implicit,refresh_token	http://localhost:8089/goods/index		7200	259200		false	2023-05-06 01:14:19	2023-05-06 06:14:30

既然用到了数据库,依赖当然也要提供相应的支持,我们给 authorization_server 添加如下依赖:

        
            org.springframework.boot
            spring-boot-starter-jdbc
        

        
            mysql
            mysql-connector-java
        

然后在 application.properties 中配置一下数据库的连接信息:

spring.datasource.river-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://xxxx:3307/oauth?useUnicode=true&characterEncoding=utf8
spring.datasource.username= root
spring.datasource.password= xxxx

这里的配置多了最后一条。这是因为我们一会要创建自己的 ClientDetailsService,而系统已经创建了 ClientDetailsService,加了最后一条就允许我们自己的实例覆盖系统默认的实例。

    @Resource
    private DataSource dataSource;

    @Bean
    public ClientDetailsService detailsService(){
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(detailsService());
        //内存配置的方式配置用户信息
        //内存方式
//        clients.inMemory()
//                //内存模拟client_id
//                .withClient("xql")
//                //客户端 秘钥 以及加密方式BCryptPasswordEncoder
//                .secret(new BCryptPasswordEncoder().encode("xql123"))
//                //客户端拥有的资源列表
//                .resourceIds("res1")
//                //该client允许的授权类型
//                .authorizedGrantTypes("authorization_code",
//                        "password", "client_credentials", "implicit",
//                        "refresh_token")
//                //允许的授权范围
//                .scopes("all")
//                //跳转到授权页面
//                .autoApprove(false)
//                //回调地址
//                .redirectUris("http://localhost:8089/goods/index");
//                .redirectUris("http://localhost:8089/goods/implicit.jsp");
//                .redirectUris("http://localhost:8089/goods/login");
        //继续注册其他客户端
        // .and()
        // .withClient()
        // 加载自定义的客户端管理服务
//         clients.withClientDetails(clientDetailsService);
    }

修改后的 AuthorizationServerTokenServices 实例如下:

    @Autowired
    private ClientDetailsService clientDetailsService;
    /**
     * 这个 Bean 主要用来配置 Token 的一些基本信息,
     * 例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。
     * Token 有效期这个好理解,刷新 Token 的有效期我说一下,当 Token 快要过期的时候,
     * 我们需要获取一个新的 Token,在获取新的 Token 时候,需要有一个凭证信息,
     * 这个凭证信息不是旧的 Token,而是另外一个 refresh_token,这个 refresh_token 也是有有效期的。
     */
    @Bean
    public AuthorizationServerTokenServices authorizationServerTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        //客户端详情服务
        services.setClientDetailsService(clientDetailsService);
        //允许令牌自动刷新
        services.setSupportRefreshToken(true);
        //令牌存储策略-内存
        services.setTokenStore(tokenStore);
        // 令牌默认有效期2小时
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        // 刷新令牌默认有效期3天
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        return services;
    }

你可能感兴趣的:(redis,spring,java)