• OAuth + Security


    PS:此文章为系列文章,建议从第一篇开始阅读。

    在我们之前的文章中,我们当时获取到Token令牌时,此时的令牌时存储在内存中的,这样显然不利于我们程序的扩展,所以为了解决这个问题,官方给我们还提供了其它的方式来存储令牌,存储到数据库或者Redis中,下面我们就来看一看怎么实现。

    不使用Jwt令牌的实现

    • 存储到数据库中(JdbcTokenStore)

    使用数据库存储方式之前,我们需要先准备好对应的表。Spring Security OAuth仓库可以找到相应的脚本:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql。该脚本为HSQL,所以需要根据具体使用的数据库进行相应的修改,以MySQL为例,并且当前项目中,只需要使用到oauth_access_token和oauth_refresh_token数据表,所以将创建这两个库表的语句改为MySQL语句:

    CREATE TABLE oauth_access_token (
    	token_id VARCHAR ( 256 ),
    	token BLOB,
    	authentication_id VARCHAR ( 256 ),
    	user_name VARCHAR ( 256 ),
    	client_id VARCHAR ( 256 ),
    	authentication BLOB,
    	refresh_token VARCHAR ( 256 ) 
    );
    
    CREATE TABLE oauth_refresh_token ( 
    token_id VARCHAR ( 256 ), 
    token BLOB, authentication BLOB 
    );
    

    然后我们需要去配置对应的认证服务器,主要就是修改之前文章中TokenConfigure类中的tokenStore()方法:

    // 同时需要注入数据源
    @Autowired
    private DataSource dataSource;
        
    @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }
    

    同时需要添加jdbc的依赖:

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    

    认证服务器中的配置保持本系列第一章的配置不变,此处不再赘述。

    其中若有不理解的地方,请参考该系列的第一篇文章

    关于数据源的补充:

    在我们项目中配置的数据源,可能不一定是使用的官方提供的格式,比如我们自定义的格式,或者使用第三方的数据源,那么我们如何去配置呢?这里以mybatis-plus的多数据源为例:

    @Configuration
    @EnableAuthorizationServer
    public class FebsAuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {
    
        ......
        
        @Autowired
        private DynamicRoutingDataSource dynamicRoutingDataSource;
    
        @Bean
        public TokenStore tokenStore() {
            DataSource dimplesCloudBase = dynamicRoutingDataSource.getDataSource("dimples_cloud_base");
            return new JdbcTokenStore(febsCloudBase);
        }
        ......
    }
    

    然后启动项目,重新获取Token进行测试,能正确的获取到Token:

    image

    我们查看数据中,看是否已经存入相关信息到数据库中:

    image

    • 存储到redis(RedisTokenStore)

    令牌存储到redis中相比于存储到数据库中来说,存储redis中,首先在性能上有一定的提升,其次,令牌都有有效时间,超过这个时间,令牌将不可再用,而redis的可以给对应的key设置过期时间,完美切合需求,所有令牌存储到redis中也是一种值得使用的方法。

    首先,我们需要在项目中添加redis依赖,同时配置redis

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

    新建配置类RedisConfigure

    @Configuration
    public class RedisConfigure{
    
        @Bean
        @ConditionalOnClass(RedisOperations.class)
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
            ObjectMapper mapper = new ObjectMapper();
            mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(mapper);
    
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用 String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            // hash的 key也采用 String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用 jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的 value序列化方式采用 jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
    
            return template;
        }
        
    }
    

    配置redis的连接

    spring:
      redis:
        database: 0
        host: 127.0.0.1
        port: 6379
        lettuce:
          pool:
            min-idle: 8
            max-idle: 500
            max-active: 2000
            max-wait: 10000
        timeout: 5000
    

    这时我们启动项目测试,会发现项目将会报错:

    Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
    

    这是由于我们配置RedisConfigure时,使用到了一个ObjectMapper类,这个类需要我们引入Apache的一个工具包

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    

    最后,我们需要在认证服务器中配置token的存储方式,还是同jdbc的配置,在tokenStore()方法中配置,打开我们的TokenConfigure类,然后做如下配置:

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

    认证服务器中的配置还是跟之前的一样,保持不变,启动项目,获取Token测试:

    image

    我们查看Redis中,看是否已经存入相关信息到数据库中:

    image

    • 其它

    我们打开TokenStore接口的实现类,会发现,还有一种JwkTokenStore,这种实际上就是将我们UUID格式令牌变成可以带特殊含义的jwt格式的令牌,我们已经在第三篇文章中介绍过了,可以参考前面的文章。

    但是我们需要明白一点的是,这种令牌还是存储在内存中的,后期我们如何将其存储到redis中是我们研究的方向。

    image

    在上面的token存储到数据库和存储到redis中,我们会发现一个问题,那就是我们多次获取令牌,但是其值是固定不变的,为了解决这个问题,我们可以使用如下方式解决:

    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        // 解决每次生成的 token都一样的问题
        redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
        return redisTokenStore;
    }
    

    使用JWT令牌的实现

    在之前的所有使用方法中,我们要么是将Token存储在数据库或者redis中的时候,令牌的格式是UUID的无意义字符串,或者是使用JWT的有意义字符串,但是确是将令牌存储在内存中,那么,我们现在想使用JWT令牌的同时,也想将令牌存储到数据库或者Redis中。我们该怎么配置呢?下面我们以Redis存储为例,进行探索:

    首先,我们还是要使用到TokenConfigure中的JWT和Redis配置:

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //对称秘钥,资源服务器使用该秘钥来验证
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }
    
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
    

    接下来,是配置认证服务器,在认证服务器中,跟之前的配置有一点区别,如下:

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    
    @Autowired
    private TokenStore tokenStore;
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                // 配置密码模式管理器
                .authenticationManager(authenticationManager)
                // 配置授权码模式管理器
                .authorizationCodeServices(authorizationCodeServices())
                // 令牌管理
    //                .tokenServices(tokenServices());
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenStore(tokenStore);
    
    }
    

    启动项目,进行测试,获取token

    image

    image

    然后使用该令牌请求资源

    image

    至此,我们差不多完成了Token令牌的存储和获取

    源码传送门: https://gitee.com/dimples820/dimples-explore

  • 相关阅读:
    HDU 1698 Just a Hook(线段树区间替换)
    NBOJv2 1034 Salary Inequity(DFS序+线段树区间更新区间(最值)查询)
    NBOJv2 1004 蛤玮打扫教室(线段树区间更新区间最值查询)
    NBOJv2 1050 Just Go(线段树/树状数组区间更新单点查询)
    POJ 3468 A Simple Problem with Integers(线段树区间更新区间查询)
    HDU 1754 I Hate It(线段树单点更新区间最值查询)
    HDU 1166敌兵布阵+NOJv2 1025: Hkhv love spent money(线段树单点更新区间查询)
    GDUT——1169: Krito的讨伐(优先队列BFS)
    HDU——2444The Accomodation of Students(BFS判二分图+最大匹配裸题)
    HDU——1045Fire Net(最大匹配)
  • 原文地址:https://www.cnblogs.com/reroyalup/p/13062967.html
Copyright © 2020-2023  润新知