OAuth2令牌存入Redis

OAuth2令牌存入Redis

首先我们需要安装redis:
目前有四种方式获取一个 Redis,这里简单介绍一下后期具体说

  1. 直接编译安装(推荐使用)
  2. 使用 Docker
  3. 也可以直接安装
  4. 还有一个在线体验的方式,通过在线体验,可以直接使用 Redis 的功能http://try.redis.io/

这里我们使用第一种
提前准备好 gcc 环境。

yum install gcc-c++

接下来下载并安装 Redis:

wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar -zxvf redis-5.0.7.tar.gz
cd redis-5.0.7/
make
make install

安装完成后,启动 Redis

redis-server redis.conf

启动成功后页面如下
在这里插入图片描述
至此安装成功,下面说说springboot中加入redis启动报错的问题
添加 Redis 依赖

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

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

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

写上自己的ip和密码
可以用jedis来测试一下是否连接上,测试类如下:

public class JedisTest {
    public static void main(String[] args) {
        //创建客户端
        Jedis jedis = new Jedis("自己的IP地址",6379);
        //设置密码
        jedis.auth("自己设置的密码");
        //设置值
        jedis.set("name","cphsw");
        //获取值
        String value = jedis.get("name");
        System.out.println(value);
        //释放连接资源
        jedis.close();
    }
}

当然了我们要在pom.xml引入以下配置

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

第一次连接的时候,肯定会有连接超时或者连接不上问题,就不截图,说方法:
我们配置下防火墙 开一个6379端口权限,防火墙是关闭的可以忽略

firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --reload

我们配置下 redis配置文件

vi /cp/redis/redis-5.0.7/redis.conf

在这里插入图片描述
这里绑定了本机,我们把这个备注掉;

# bind 127.0.0.1

配置完后要重启下redis服务

[root@instance-cpe31be2 redis-5.0.7]# redis-cli shutdown
[root@instance-cpe31be2 redis-5.0.7]# redis-server /cp/redis/redis-5.0.7/redis.conf

此时因为远程连接redis redis自我保护 拒绝访问
方法一:
编辑redis.conf,修改下面内容
在这里插入图片描述
改成 no即可,此时可以连接上了,但不安全,推荐方法二
方法二: 设置redis连接密码
首先我们服务器上连接上去
在这里插入图片描述

127.0.0.1:6379> config set requirepass 123456
设置密码 123456
127.0.0.1:6379> quit

然后重启一下

127.0.0.1:6379> auth 123456
OK

此时我们本地的测试也可以了,到此我们本地项目连接上远程redis服务

OAuth2常见的有四种模式
1.授权码模式
2.简易模式
3.账号密码模式
4.客户端模式
具体的在码云上,感兴趣可以联系获取

在我们配置授权码模式的时候,有两个东西当时存在了内存中:
InMemoryAuthorizationCodeServices 这个表授权码存在内存中。
InMemoryTokenStore 表示生成的令牌存在内存中。
授权码用过一次就会失效,存在内存中没什么问题,但是令牌,我们实际上还有其他的存储方案。

我们所使用的 InMemoryTokenStore 实现了 TokenStore 接口,看下 TokenStore 接口的实现类
可以看到,我们有多种方式来存储 access_token。

1.InMemoryTokenStore,这是我们之前使用的,也是系统默认的,就是将 access_token 存到内存中,单机应用这个没有问题,但是在分布式环境下不推荐。
2.JdbcTokenStore,看名字就知道,这种方式令牌会被保存到数据中,这样就可以方便的和其他应用共享令牌信息。
3.JwtTokenStore,这个其实不是存储,因为使用了 jwt 之后,在生成的 jwt 中就有用户的所有信息,服务端不需要保存,这也是无状态登录
4.RedisTokenStore,这个很明显就是将 access_token 存到 redis 中。
5.JwkTokenStore,将 access_token 保存到 JSON Web Key。

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

@Configuration
public class AccessTokenConfig {
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Bean
    TokenStore tokenStore(){
        //return new InMemoryTokenStore();
        return new RedisTokenStore(redisConnectionFactory);
    }
}

然后分别启动 auth-server、client-app 以及 user-server,走一遍第三方登录流程(http://localhost:8082/index.html),然后我们发现,派发的 access_token 在 redis 中也有一份
在这里插入图片描述
可以看到,数据都存到 Redis 中了,access_token 这个 key 在 Redis 中的有效期就是授权码的有效期。正是因为 Redis 中的这种过期机制,让它在存储 access_token 时具有天然的优势

客户端信息之前我们也是存到内存的,后期如果用户量太大,是不合理的
客户端信息入库涉及到的接口主要是 ClientDetailsService,这个接口主要有两个实现类
InMemoryClientDetailsService 就不多说了,这是存在内存中的。如果要存入数据库,很明显是 JdbcClientDetailsService,从JdbcClientDetailsService源码中我们可以看到表的结构

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
  `client_id` varchar(48) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

接下来我们将一开始定义的客户端的关键信息存入数据库中
在这里插入图片描述
既然用到了数据库,依赖当然也要提供相应的支持,我们给 auth-server 添加如下依赖

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

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

spring.datasource.url=jdbc:mysql:///oauth2?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=Asia/Shanghai
spring.datasource.password=root
spring.datasource.username=root

spring.main.allow-bean-definition-overriding=true

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

接下来,我们来提供自己的实例即可

@Autowired
DataSource dataSource;
@Bean
ClientDetailsService clientDetailsService() {
    return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.withClientDetails(clientDetailsService());
}

我们也可以将令牌有效期配置在数据库中,这样就不用在代码中配置了,修改后的数据库如下:
修改后的 AuthorizationServerTokenServices 实例如下:

@Bean
AuthorizationServerTokenServices tokenServices() {
    DefaultTokenServices services = new DefaultTokenServices();
    services.setClientDetailsService(clientDetailsService());
    services.setSupportRefreshToken(true);
    services.setTokenStore(tokenStore);
    return services;
}

第三方应用优化
优化第三方登录中的Controller
首先我们来定义一个专门的类 TokenTask 用来解决 Token 的管理问题

@Component @SessionScope
public class TokenTask {
    @Autowired
    RestTemplate restTemplate;
    public String access_token = "";
    public String refresh_token = "";

    public String getData(String code) {
        if ("".equals(access_token) && code != null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("client_id", "javaboy");
            map.add("client_secret", "123");
            map.add("redirect_uri", "http://localhost:8082/index.html");
            map.add("grant_type", "authorization_code");
            Map<String, String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
            access_token = resp.get("access_token");
            refresh_token = resp.get("refresh_token");
            return loadDataFromResServer();
        } else {
            return loadDataFromResServer();
        }
    }

    private String loadDataFromResServer() {
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Bearer " + access_token);
            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
            ResponseEntity<String> entity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, httpEntity, String.class);
            return entity.getBody();
        } catch (RestClientException e) {
            return "未加载";
        }
    }

    @Scheduled(cron = "0 55 0/1 * * ?")
    public void tokenTask() {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("client_id", "javaboy");
        map.add("client_secret", "123");
        map.add("refresh_token", refresh_token);
        map.add("grant_type", "refresh_token");
        Map<String, String> resp = restTemplate.postForObject("http://localhost:8080/oauth/token", map, Map.class);
        access_token = resp.get("access_token");
        refresh_token = resp.get("refresh_token");
    }
}

1.首先在 getData 方法中,如果 access_token 为空字符串,并且 code 不为 null,表示这是刚刚拿到授权码的时候,准备去申请令牌了,令牌拿到之后,将 access_token 和 refresh_token 分别赋值给变量,然后调用 loadDataFromResServer 方法去资源服务器加载数据。
2.另外有一个 tokenTask 方法,这是一个定时任务,每隔 115 分钟去刷新一下 access_token(access_token 有效期是 120 分钟)。

HelloController 中略作调整

@Controller
public class HelloController {
    @Autowired
    TokenTask tokenTask;
    @GetMapping("/index.html")
    public String hello(String code, Model model) {
        model.addAttribute("msg", tokenTask.getData(code));
        return "index";
    }
}

这样就 OK 了,当我们再去下图这个页面按 F5 刷新就不会出错了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值