Spring Boot Security Oauth2.0 整合

写在前面

阅读本文最好具备Spring Boot , Oauth2.0, Maven等等技能
接触oauth其实已经有很多年了,但是由于现实中的业务大多数都是接入第三方,对于搭建Oauth服务的需求i相对较少,无意看了下Spring Security Oauth的代码,发现变动蛮大,就想着搭建一套试试,但是网上的大部分教程都比较零散,要么就是抄来抄去。于是自己动手搞一套。
额外提一嘴,最好的教程往往都在官方文档,建议大家没事多看看Spring官方文档,英文差可以用翻译,多看看对应的源码会发现可以学到很多东西,以上。
源码已上传码云/github

依赖

本次整合使用的依赖版本

Spring Boot : 2.3.10.RELEASE
Spring Cloud: Hoxton.SR11
Mybatis-plus: 3.4.2

认证服务器

基于内存的实现

内存实现方式建议只用于测试,学习;

  1. Ouath的包
	<dependency>
    	<groupId>org.springframework.cloud</groupId>
     	<artifactId>spring-cloud-starter-oauth2</artifactId>
	</dependency>
  1. 创建 Spring Boot 工程
    1. 配置类添加 @EnableAuthorizationServer 表名该服务是一个认证服务器

    2. 创建一个Spring Security 的标准配置文件
      在这里插入图片描述

    3. 配置基于内存的客户端数据
      在这里插入图片描述

    4. 启动服务
      启动完成,我们看日志,会看到
      在这里插入图片描述
      相信用过Spring Security 的同学,都知道的,上面是内置用户user 的随机密码

    5. Spring Security Oauth2.0 内置授权端点 /oauth/authorize
      打开浏览器访问

       http://localhost:9010/oauth/authorize?client_id=client_id&response_type=code&redirect_uri=https://www.baidu.com&state=xzy
      

      这个url参数需要说一下:二者都是可选
      redirect_uri:要么不加,要么必须和配置文件里面的一样
      state:是一个随机值,在服务器通过redirect_uri回传code时,会原值返回,可用于校验本次回调是否合法 .
      scope:用于指定本次申请授权的范围,这里没有指定,授权页会将该客户端所拥有的授权列表列出来

      不出意外会出现
      在这里插入图片描述
      这里使用Spring Security 内置的用户进行登录
      user : cb4c7867-a0ef-4a30-80e4-878031913e3b

    6. 登录成功时会转发到授权页 在这里插入图片描述

    7. 我们可以选择授权的范围,比如选择一个 app 授权,web拒绝,点击提交。可以看到服务器已经重定向将code和我们之前设置的state回传
      在这里插入图片描述

    8. state字段,我们可以在代码里面做合法性校验,然后就是拿code换取access_token了,由于这个端点是post请求,如果浏览器没插件的可以使用postman
      在这里插入图片描述
      这里已经拿到了 access_token,接下来可以开始创建资源服务器了

基于数据库的实现

在采用数据库方式实现之前,需要一点准备工作;

  1. Oauth2.0 系统表
    系统脚本官方已提供,部分字段类型需根据数据库调整,这里仅提供Mysql版本,其他数据库请自行修改
CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` timestamp NULL DEFAULT NULL,
  `lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) 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;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 业务表
    基于 RBAC 的权限模型需要一些基础的表结构,即 用户,角色,权限之间多对多的关系;这里直接从网上扒了一套
    CREATE TABLE `tb_permission`
    (
        `id`          bigint(20)   NOT NULL AUTO_INCREMENT,
        `parent_id`   bigint(20)   DEFAULT NULL COMMENT '父权限',
        `name`        varchar(64)  NOT NULL COMMENT '权限名称',
        `enname`      varchar(64)  NOT NULL COMMENT '权限英文名称',
        `url`         varchar(255) NOT NULL COMMENT '授权路径',
        `description` varchar(200) DEFAULT NULL COMMENT '备注',
        `created`     datetime     NOT NULL,
        `updated`     datetime     NOT NULL,
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 44
      DEFAULT CHARSET = utf8 COMMENT ='权限表';
    insert into `tb_permission`(`id`, `parent_id`, `name`, `enname`, `url`, `description`, `created`, `updated`)
    values (37, 0, '系统管理', 'System', '/', NULL, '2019-04-04 23:22:54', '2019-04-04 23:22:56'),
           (38, 37, '用户管理', 'SystemUser', '/users/', NULL, '2019-04-04 23:25:31', '2019-04-04 23:25:33'),
           (39, 38, '查看用户', 'SystemUserView', '', NULL, '2019-04-04 15:30:30', '2019-04-04 15:30:43'),
           (40, 38, '新增用户', 'SystemUserInsert', '', NULL, '2019-04-04 15:30:31', '2019-04-04 15:30:44'),
           (41, 38, '编辑用户', 'SystemUserUpdate', '', NULL, '2019-04-04 15:30:32', '2019-04-04 15:30:45'),
           (42, 38, '删除用户', 'SystemUserDelete', '', NULL, '2019-04-04 15:30:48', '2019-04-04 15:30:45');
    
    CREATE TABLE `tb_role`
    (
        `id`          bigint(20)  NOT NULL AUTO_INCREMENT,
        `parent_id`   bigint(20)   DEFAULT NULL COMMENT '父角色',
        `name`        varchar(64) NOT NULL COMMENT '角色名称',
        `enname`      varchar(64) NOT NULL COMMENT '角色英文名称',
        `description` varchar(200) DEFAULT NULL COMMENT '备注',
        `created`     datetime    NOT NULL,
        `updated`     datetime    NOT NULL,
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 38
      DEFAULT CHARSET = utf8 COMMENT ='角色表';
    insert into `tb_role`(`id`, `parent_id`, `name`, `enname`, `description`, `created`, `updated`)
    values (37, 0, '超级管理员', 'admin', NULL, '2019-04-04 23:22:03', '2019-04-04 23:22:05');
    
    CREATE TABLE `tb_role_permission`
    (
        `id`            bigint(20) NOT NULL AUTO_INCREMENT,
        `role_id`       bigint(20) NOT NULL COMMENT '角色 ID',
        `permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 43
      DEFAULT CHARSET = utf8 COMMENT ='角色权限表';
    insert into `tb_role_permission`(`id`, `role_id`, `permission_id`)
    values (37, 37, 37),
           (38, 37, 38),
           (39, 37, 39),
           (40, 37, 40),
           (41, 37, 41),
           (42, 37, 42);
    
    CREATE TABLE `tb_user`
    (
        `id`       bigint(20)  NOT NULL AUTO_INCREMENT,
        `username` varchar(50) NOT NULL COMMENT '用户名',
        `password` varchar(64) NOT NULL COMMENT '密码,加密存储',
        `phone`    varchar(20) DEFAULT NULL COMMENT '注册手机号',
        `email`    varchar(50) DEFAULT NULL COMMENT '注册邮箱',
        `created`  datetime    NOT NULL,
        `updated`  datetime    NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `username` (`username`) USING BTREE,
        UNIQUE KEY `phone` (`phone`) USING BTREE,
        UNIQUE KEY `email` (`email`) USING BTREE
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 38
      DEFAULT CHARSET = utf8 COMMENT ='用户表';
    insert into `tb_user`(`id`, `username`, `password`, `phone`, `email`, `created`, `updated`)
    values (37, 'admin', '$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi', '15888888888',
            'lee.lusifer@gmail.com', '2019-04-04 23:21:27', '2019-04-04 23:21:29');
    
    CREATE TABLE `tb_user_role`
    (
        `id`      bigint(20) NOT NULL AUTO_INCREMENT,
        `user_id` bigint(20) NOT NULL COMMENT '用户 ID',
        `role_id` bigint(20) NOT NULL COMMENT '角色 ID',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 38
      DEFAULT CHARSET = utf8 COMMENT ='用户角色表';
    insert into `tb_user_role`(`id`, `user_id`, `role_id`)
    values (37, 37, 37);
    
  2. 开始搭建开发环境
    这里采用了 Mybatis Plus 插件,简化单表操作。
    1. 手写了两个sql : 根据用户id查角色集合,根据用户id查权限集合,用户登录时写进权限集
    2. yaml
      在这里插入图片描述
    3. 启动类
      在这里插入图片描述
    4. 需要从内存转移到数据库的数据大概有 ClientDetailsService, TokenStore, JdbcAuthorizationCodeServices, ApprovalStore 这几项(别问从哪找的,问就是官方文档+源码),Security Oauth2.0 已经内置了对应的实现类,只需要将数据源注入其中即可
    5. 重点来了,这里需要新增一个配置类,继承自 AuthorizationServerConfigurerAdapter
      在这里插入图片描述
      重写 以下俩方法在这里插入图片描述
      第一个不用说了,一看就是加载客户端信息的,直接注入 ClientDetailsService 数据库实现即可
      第二个用于配置 Oauth的行为,配置以下几项即可
      在这里插入图片描述
      另外一个:在这里插入图片描述
    6. 启动,测试
      都和内存方式一样,资源服务器直接不用动

基于JWT实现

(这里采用的是对称加密,采用RSA加密时,认证服务器不再是配置key-value 而是key-store,源码参考 AuthorizationServerTokenServicesConfiguration 这个类)

从普通token转到jwt,很简单,网上好多都是复制
实际上只需要以下两步即可

1. 认证服务器增加配置
	security.oauth2.authorization.jwt.key-value = jwt加密密钥	
2. 资源服务增加配置
   	如果资源服务器拿不到密钥时,可以配置
   	security.oauth2.resource.jwt.key-uri =  认证服务器/oauth/token_key 获取认证服务器当前的 密钥。
  
	采用这种动态获取的方式时需要注意:
	
	1.认证服务器必须开启对应端口
   	security.oauth2.authorization.token-key-access: "permitAll()或者isAuthenticated()"
   	
   	2.Security 必须开启http Basic 认证(这个还是看源码看到的)
   	如果资源服务器能拿到这个密钥,而且双方约定不轻易改动时,可以采用以下方式配置
	security.oauth2.resource.jwt.key-value = 认证服务器的加密密钥
	这种方式就不需要去认证服务器校验这个密钥

资源服务器

  1. Ouath的包
	<dependency>
    	<groupId>org.springframework.cloud</groupId>
     	<artifactId>spring-cloud-starter-oauth2</artifactId>
	</dependency>
  1. 创建Spring Boot 应用 加注解 @EnableResourceServer 声明该应用为资源服务器
  2. yaml配置
    在这里插入图片描述
  3. 一个待访问资源
    在这里插入图片描述
  4. 启动应用,打开postman访问资源
    在这里插入图片描述
    可以看到,这里的资源权限已经被 Spring Security Oauth2.0体系接管
    带上刚才获取到的 access_token 再访问一次
    在这里插入图片描述
    这里有点需要说明,这个 access_token 默认是 Bear Token, 我们打开请求头可以看到 在这里插入图片描述
    也就是说,我们手动添加这个请求头也是可以的
    还有一种方式 在这里插入图片描述
    基于内存的资源服务器就此完成。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值