写在前面
阅读本文最好具备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
认证服务器
基于内存的实现
内存实现方式建议只用于测试,学习;
- Ouath的包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
- 创建 Spring Boot 工程
-
配置类添加 @EnableAuthorizationServer 表名该服务是一个认证服务器
-
创建一个Spring Security 的标准配置文件

-
配置基于内存的客户端数据

-
启动服务
启动完成,我们看日志,会看到

相信用过Spring Security 的同学,都知道的,上面是内置用户user 的随机密码 -
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 -
登录成功时会转发到授权页

-
我们可以选择授权的范围,比如选择一个 app 授权,web拒绝,点击提交。可以看到服务器已经重定向将code和我们之前设置的state回传

-
state字段,我们可以在代码里面做合法性校验,然后就是拿code换取access_token了,由于这个端点是post请求,如果浏览器没插件的可以使用postman

这里已经拿到了 access_token,接下来可以开始创建资源服务器了
-
基于数据库的实现
在采用数据库方式实现之前,需要一点准备工作;
- 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;
- 业务表
基于 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); - 开始搭建开发环境
这里采用了 Mybatis Plus 插件,简化单表操作。- 手写了两个sql : 根据用户id查角色集合,根据用户id查权限集合,用户登录时写进权限集
- yaml

- 启动类

- 需要从内存转移到数据库的数据大概有 ClientDetailsService, TokenStore, JdbcAuthorizationCodeServices, ApprovalStore 这几项(别问从哪找的,问就是官方文档+源码),Security Oauth2.0 已经内置了对应的实现类,只需要将数据源注入其中即可
- 重点来了,这里需要新增一个配置类,继承自 AuthorizationServerConfigurerAdapter

重写 以下俩方法
第一个不用说了,一看就是加载客户端信息的,直接注入 ClientDetailsService 数据库实现即可
第二个用于配置 Oauth的行为,配置以下几项即可

另外一个:
- 启动,测试
都和内存方式一样,资源服务器直接不用动
基于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 = 认证服务器的加密密钥
这种方式就不需要去认证服务器校验这个密钥
资源服务器
- Ouath的包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
- 创建Spring Boot 应用 加注解 @EnableResourceServer 声明该应用为资源服务器
- yaml配置

- 一个待访问资源

- 启动应用,打开postman访问资源

可以看到,这里的资源权限已经被 Spring Security Oauth2.0体系接管
带上刚才获取到的 access_token 再访问一次

这里有点需要说明,这个 access_token 默认是 Bear Token, 我们打开请求头可以看到
也就是说,我们手动添加这个请求头也是可以的
还有一种方式
基于内存的资源服务器就此完成。
813

被折叠的 条评论
为什么被折叠?



