源码:https://github.com/GiraffePeng/spring-cloud-scaffolding
授权服务器
- 客户端信息以及用户信息按照生产环境模拟,将其保存在了数据库中。
- 自定义了Token增强器,在其负荷部分加入自定义内容
- 使用JWT来实现token的生成
- 自定义根据手机号验证码和手机号密码的形式授权的grant_type,可基本满足于移动端的开发
1、oauth2介绍
OAuth2.0是一套授权体系的开放标准,定义了四大角色:
- 资源拥有者,也就是用户,由用于授予三方应用权限
- 客户端,也就是三方应用程序,在访问用户资源之前需要用户授权
- 资源提供者,或者说资源服务器,提供资源,需要实现Token和ClientID的校验,以及做好相应的权限控制
- 授权服务器,验证用户身份,为客户端颁发Token,并且维护管理ClientID、Token以及用户
其中后三项都可以是独立的程序。OAuth2.0标准同时定义了四种授权模式,这里介绍常用的三种(授权码、密码模式、客户端模式)
1、不管是哪种模式,通用流程如下:
- 三方网站(或者说客户端)需要先向授权服务器去申请一套接入的ClientID+ClientSecret
- 用任意一种模式拿到访问Token(流程见下)
- 拿着访问Token去资源服务器请求资源
- 资源服务器根据Token查询到Token对应的权限进行权限控制
2、授权码模式,最标准最安全的模式,适合和外部交互,流程是:
- 三方网站客户端转到授权服务器,上送ClientID,授权范围Scope、重定向地址RedirectUri等信息
- 用户在授权服务器进行登录并且进行授权批准(授权批准这步可以配置为自动完成)
- 授权完成后重定向回到之前客户端提供的重定向地址,附上授权码
- 三方网站服务端通过授权码+ClientID+ClientSecret去授权服务器换取Token(Token含访问Token和刷新Token,访问Token过去后用刷新Token去获得新的访问Token)
- 你可能会问这个模式为什么这么复杂,为什么安全呢?因为我们不会对外暴露ClientSecret,不会对外暴露访问Token,使用授权码换取Token的过程是服务端进行,客户端拿到的只是一次性的授权码
3、密码凭证模式,适合内部系统之间使用的模式(客户端是自己人,客户端需要拿到用户帐号密码),流程是:
- 用户提供帐号密码给客户端
- 客户端凭着用户的帐号密码,以及客户端自己的ClientID+ClientSecret去授权服务器换取Token
4、客户端模式,适合内部服务端之间使用的模式:
- 和用户没有关系,不是基于用户的授权
- 客户端凭着自己的ClientID+ClientSecret去授权服务器换取Token
2、JWT
通过 JWT 配合 Spring Security OAuth2 使用的方式,可以避免每次请求都远程调度认证授权服务。资源服务器只需要从授权服务器 验证一次,返回 JWT。返回的 JWT 包含了 用户 的所有信息,包括 权限信息
2.1、什么是JWT
JSON Web Token(JWT)是一种开放的标准(RFC 7519),JWT 定义了一种 紧凑 且 自包含 的标准,旨在将各个主体的信息包装为 JSON 对象。主体信息 是通过 数字签名 进行 加密 和 验证 的。经常使用 HMAC 算法或 RSA(公钥/私钥 的 非对称性加密)算法对 JWT 进行签名,安全性很高。
- 紧凑型:数据体积小,可通过 POST 请求参数 或 HTTP 请求头 发送。
- 自包含:JWT 包含了主体的所有信息,避免了 每个请求 都需要向 Uaa 服务验证身份,降低了 服务器的负载。
2.2、JWT结构
JWT 的结构由三部分组成:Header(头)、Payload(有效负荷)和 Signature(签名)。因此 JWT 通常的格式是 xxxxx.yyyyy.zzzzz
2.2.1、Header
Header通常是由两部分组成:令牌的类型(即 JWT)和使用的 算法类型,如 HMAC、SHA256 和 RSA。例如:
{
"typ": "JWT",
"alg": "HS256"
}
将 Header 用 Base64 编码作为 JWT的第一部分,不建议在 JWT 的 Header 中放置 敏感信息。
2.2.2、Payload
第二部分 Payload 是 JWT 的 主体内容部分,它包含 声明 信息。声明是关于 用户 和 其他数据 的声明。
声明有三种类型: registered、public 和 private。
- Registered claimsJWT 提供了一组 预定义 的声明,它们不是 强制的,但是推荐使用。JWT 指定 七个默认 字段供选择:
注册声明 | 字段含义 |
---|---|
iss | 发行人 |
exp | 到期时间 |
sub | 主题 |
aud | 用户 |
nbf | 在此之前不可用 |
iat | 发布时间 |
jti | 用于标识JWT的ID |
- Public claims:可以随意定义
- Private claims:用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
下面是 Payload 部分的一个示例:
{
"sub": "123456789",
"name": "John Doe",
"admin": true
}
将 Payload 用 Base64 编码作为 JWT 的 第二部分,不建议在 JWT 的 Payload 中放置 敏感信息。
2.2.3、Signature
要创建签名部分,需要利用 秘钥 对 Base64 编码后的 Header 和 Payload 进行 加密,加密算法的公式如下:
HMACSHA256(
base64UrlEncode(header) + '.' +
base64UrlEncode(payload),
secret
)
签名可以用于验证消息在传递过程中有没有被更改。对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
2.3、JWT的工作方式
客户端 获取 JWT 后,对于以后的 每次请求,都不需要再通过 授权服务 来判断该请求的 用户 以及该 用户的权限。在微服务系统中,可以利用 JWT 实现 单点登录。认证流程图如下:
3、授权服务器的搭建
父级pom.xml这里省略,基于父级工程spring-cloud-scaffolding即可。
3.1、授权服务器引入依赖:
<dependencies>
<!-- oauth2.0依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- web相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--reids -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jpa数据访问 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 断路器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 健康监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 链路跟踪-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- 链路跟踪-->
<dependency>
<groupId>com.github.gavlyukovskiy</groupId>
<artifactId>p6spy-spring-boot-starter</artifactId>
<version>1.4.3</version>
</dependency>
<!-- 注册中心注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.2、建立application.yml文件:
spring:
application:
name: auth-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ceshi?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
username: huaxin
password: Koreyoshih527
jpa:
hibernate:
ddl-auto: update
show-sql: true
redis:
host: localhost
database: 0
port: 6379
hystrix:
command:
default:
execution:
isolation:
thread:
timeout-in-milliseconds: 3000
server:
port: 8599
eureka:
client:
service-url:
defaultZone: http://localhost:8865/eureka/
会使用到mysql数据库,授权服务器的端口是8599。
3.3、建立表
因为授权服务器的客户端信息以及用户信息要放入数据库中,我们需要初始化一些表:
- user_auth表用于oauth2的用户信息记录。
- member_auth表用于移动端oauth2的会员信息记录。
- role_auth表,存放了用户的权限信息
- oauth_approvals授权批准表,存放了用户授权第三方服务器的批准情况
- oauth_client_details,客户端信息表,存放客户端的ID、密码、权限、允许访问的资源服务器ID以及允许使用的授权模式等信息
- oauth_code授权码表,存放了授权码。
表结构如下:
CREATE TABLE `user_auth` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';
CREATE TABLE `member_auth` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`password` varchar(255) DEFAULT NULL,
`username` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8 COMMENT='会员表';
CREATE TABLE `role_auth` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`authority` varchar(255) NOT NULL,
`user_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_sb8bbouer5wak8vyiiy4pf2bx` (`authority`)
) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8 COMMENT='用户角色表';
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL,
`clientId` varchar(256) DEFAULT NULL,
`partnerKey` varchar(32) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
`expiresAt` datetime DEFAULT NULL,
`lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(64) NOT NULL,
`resource_ids` varchar(255) DEFAULT NULL,
`client_secret` varchar(255) DEFAULT NULL,
`scope` varchar(255) DEFAULT NULL,
`authorized_grant_types` varchar(255