# Oauth2_demo
**Repository Path**: lxmscorpio/lxm
## Basic Information
- **Project Name**: Oauth2_demo
- **Description**: 支持五种认证模式的基本Oauth2系统
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: base_oauth2_master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 9
- **Forks**: 0
- **Created**: 2020-05-10
- **Last Updated**: 2022-05-30
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 支持五种基础认证模式的Oauth2Dome
1、认证模式:authorization_code(授权码模式)、implicit(隐式授权模式)、password:(密码授权模式)、client_credentials(客户端授权模式)、refresh_token(刷新令牌授权模式);
2、使用内存存储token;
3、用户信息和客户端信息初始化在内存中。
# 一、搭建Oauth2服务器
本次搭建oauth2服务器全程使用IntelliJ IDEA。
## 1、创建springboot工程
1、初始相关依赖如下:
```yaml
org.springframework.boot
spring-boot-starter-web
org.springframework.security.oauth
spring-security-oauth2
2.3.3.RELEASE
```
## 2、创建`Authorication`认证中心
1、在`config`包下创建`AuthoricationServerConfig`配置类,继承`AuthorizationServerConfigurerAdapter`类。
2、加上`@Configuration`注解 使其成为一个配置类
3、加上`@EnableAuthorizationServer`注解 使其作为认证中心
```java
@Configuration
@EnableAuthorizationServer
public class AuthoricationServerConfig extends AuthorizationServerConfigurerAdapter {}
```
在该类中主要重写
```java
//配置客户端(应用)信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
}
//配置授权服务器端点的属性和增强的功能。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
}
//配置授权服务器安全信息
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
```
三个方法配置授权服务器认证中心
### 2.1、配置客户端信息
这里配置哪些客户端可以获得授权、授权范围、授权方式、令牌有效期及刷新令牌有效期等信息,详细代码实现及相关解释如下
```java
/**
* 客户端相关配置
* 2、配置客户端(应用)信息 非用户信息
* 客户端详细信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//super.configure(clients);
// 在内存中存储客户端信息
clients.inMemory()
// 配置客户端Id
.withClient("client")
// 客户端密钥 “{}”为加密方式 noop为不加密
// 现如今Spring Security中密钥的存储格式是“{id}…………” 所以“{}”必要 否则报错:There is no PasswordEncoder mapped for the id “null”
.secret("{noop}123456")
// 配置授权范围 read、write、all
.scopes("all")
// 配置该客户端支持的授权方式
.authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token","implicit")
// token令牌有效期 单位s
.accessTokenValiditySeconds(1800)
// refreshtoken刷新令牌有效期 单位s 一般来说刷新令牌比令牌有效期长 便于使用刷新令牌换取令牌
.refreshTokenValiditySeconds(3600);
// 基本上来说 到这一步就完成了一个客户端的配置了
// 如果想要配置多个客户端 往下使用.and()连接
//.and()
//.withClient()
//.secret()
//.scopes()
//.authorizedGrantTypes()
//.accessTokenValiditySeconds()
//.refreshTokenValiditySeconds();
}
```
### 2.2、配置`AuthenticationManager`
(第一次搭建忽略这一步,后续说明原因)
## 3、创建`webSecurity`配置中心
1、在`config`包下创建`WebSecurityConfig`配置类,继承`WebSecurityConfigurerAdapter`类。
2、加上`@Configuration`注解 使其成为一个配置类
3、加上`@EnableWebSecurity`注解 使其作为websecurity配置中心
```java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {}
```
在该类中主要重写
```java
//配置用户源
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
//配置网络安全
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
```
两个方法配置授权服务器安全中心
### 3.1、配置用户源
在这里配置用户信息的来源及管理,这次我将一个用户信息初始化在内存中,详细代码实现及相关解释如下:
```java
/**
* 用户相关配置
* 2、配置用户数据
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
// 此处我将一个简单的用户初始化在内存中
auth.inMemoryAuthentication()
// 密码编码
.passwordEncoder(new BCryptPasswordEncoder())
// 用户名
.withUser("user")
// 密码
.password(new BCryptPasswordEncoder().encode("123456"))
// 用户角色
.roles("USER");
}
```
此时项目结构如下:

## 4、启动验证
到这里,基本的Oauth服务器已经搭建起来了。下面我们进行每个授权方式的验证
客户端信息:
```pro
clientId = client
clientSecret = 123456
```
用户信息:
```pro
username = user
password = 123456
```
### 4.1、启动Oauth服务器
```
Initializing Spring embedded WebApplicationContext
Root WebApplicationContext: initialization completed in 1607 ms
Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]],
Tomcat started on port(s): 8080 (http) with context path ''
Started Oauth2serverApplication in 2.952 seconds (JVM running for 6.204)
```
由此我们看到服务器已经在`8080`端口启动起来,并暴露了`/oauth/token`、`/oauth/token_key`、`/oauth/check_token`
路径,并无报错
### 4.2、使用`postman`验证授权方式
#### 4.2.1、密码认证
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`password`
`username`和`password`分别对应`3.1`中的`user`和`password`

3、校验
发送请求后发现并不能获得授权

而是提示`Unsupported grant type: password`
此时不要慌,记得我们上面`2.2`的小惊吓吗,先不管它,我们接着验证其他授权方式先
#### 4.2.2、授权码认证
授权码认证需要先获得授权码
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=code`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
这次请求的路径不`/toekn`了,而是`/authorize`,所以请求授权码的过程就是认证过程,那现在能不能认证呢?请求一下不就知道了。
此时会告诉你授权错误,错误如下:

稳住!不要慌!并不是我们搭建的服务器不能用!这个意思是`注册客户端是至少要指定一个重定向地址`,很明显我们上面并没有做这一步,所以现在我们把这个授权方式也暂时略过,验证其他授权方式。
#### 4.2.3、客户端认证
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`client_credentials`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`

3、校验
发送请求后发现成功获得授权,证明此时客户端认证已经可以使用了。惊不惊喜!?意不意外?!

#### 4.2.4、刷新令牌认证
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`client_credentials`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
`refresh_token`是?嗯。。。是不是有什么不对

喂!醒醒!我们拿到refresh_token了吗?虽然客户端认证通过了,但是它并不提供refresh_token,所以这个认证方式也先放一放吧,我们看最后一个授权方式去。
#### 4.2.5、简化认证
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=token`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
是不是有点熟悉的味道?对的,这次请求的路径不`/toekn`了,而是`/authorize`,所以简化认证不是请求token,而是直接认证,那现在行不行呢?请求一下不就知道了。

哦嗬!熟悉的感觉再次来袭!很明显,简化认证和授权码认证都需要`注册客户端是至少要指定一个重定向地址`
## 5、初次总结
这个最基础版本Oauth授权服务器只支持`客户端认证`,革命尚未成功,同志仍需努力啊,下面简单总结一下这次不能使用的认证模式的原因。
密码认证模式:缺少`authenticationManager` 实例,为什么需要这个实例?授权中心在对第三方,携带`access token`的请求做出回应时,需要验证资源所有者的证书。
授权码认证模式:注册客户端时没有至少指定一个重定向地址。
刷新令牌认证模式:缺少刷新令牌。
简化认证模式:注册客户端时没有至少指定一个重定向地址。
现在我们知道原因了,下面一步步把他们都解决了。
# 二、深入配置Oauth2服务器
## 1、配置`AuthenticationManager`
因为授权中心在对第三方,携带`access token`的请求做出回应时,需要验证资源所有者的证书,所以授权服务器必须配置`AuthenticationManager`
### 1.1、重写`authenticationManager()`方法
**为什么要重写`AuthenticationManager authenticationManager()`方法?不重写`AuthoricationServerConfig`认证中心中的`AuthenticationManager`无法注入**
在`WebSecurityConfig`配置类(`webSecurity`配置中心)中重写`AuthenticationManager authenticationManager()`方法,代码如下:
```java
/**
* 3、重写authenticationManager 不重写AuthoricationServerConfig认证中心中的AuthenticationManager无法注入
* @return
* @throws Exception
*/
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
```
### 1.2、注入`AuthenticationManager`
在`AuthoricationServerConfig`配置类(`AuthoricationServerConfig`授权认证中心)中注入`AuthenticationManager`,代码如下:
```java
// 4、注入AuthenticationManager
@Resource
private AuthenticationManager authenticationManager;
```
### 1.3、将`authenticationManager`交给服务器端点
在`AuthoricationServerConfig`配置类(`AuthoricationServerConfig`授权认证中心)重写的`configure(AuthorizationServerEndpointsConfigurer endpoints)`方法中将`AuthenticationManager`交给服务器端点,代码如下:
```java
/**
* 配置授权服务器端点的属性和增强的功能。
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//super.configure(endpoints);
endpoints.authenticationManager(authenticationManager); //5、将authenticationManager交给服务器端点
}
```
## 2、验证`密码认证模式`和`刷新令牌认证模式`
现在我们已经把`AuthenticationManager`实例注入了,再去验证一下`密码认证模式`是否可以使用,同时拿到`refresh_token`后我们再验证一下`刷新令牌认证模式`
### 2.1、密码认证
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`password`
`username`和`password`分别对应`3.1`中的`user`和`password`

3、校验
可以看到,密码认证模式已经可以使用了。

### 2.2、刷新令牌认证
我们通过`密码认证模式`已经拿到了`refresh_token`了,现在可以验证`刷新令牌认证模式`了
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`client_credentials`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
`refresh_token`是上一步密码认证模式中拿到的`12f29223-7522-47e7-9469-95944d9f9286`

3、校验
发起请求后,发现并不能获取授权,并且我们发现这是一个服务器错误。

这是因为需要在`AuthoricationServerConfig`授权认证中心的重写的`configure(AuthorizationServerEndpointsConfigurer endpoints)`方法中引入实现的`userDeatils`接口的实现类,而我们并没有实现这一步,先放一边,先把我们之前的问题处理完。
## 3、注册客户端时指定一个重定向地址
由于`授权码认证模式`和`简化认证模式`都提示我们指定一个重定向地址,我们将重定向地址配置后再检查这两个模式
在`AuthoricationServerConfig`授权认证中心的重写的`configure(ClientDetailsServiceConfigurer clients)`方法中增加重定向信息
```java
clients.inMemory().redirectUris("http://www.baidu.com");
```
完整代码如下:
```java
/**
* 重写客户端相关配置
* 2、配置客户端(应用)信息 非用户信息
* 客户端详细信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//super.configure(clients);
// 在内存中存储客户端信息
clients.inMemory()
// 配置客户端Id
.withClient("client")
// 客户端密钥 “{}”为加密方式 noop为不加密
// 现如今Spring Security中密钥的存储格式是“{id}…………” 所以“{}”必要 否则报错:There is no PasswordEncoder mapped for the id “null”
.secret("{noop}123456")
// 配置授权范围 read、write、all
.scopes("all")
// 配置该客户端支持的授权方式
.authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token","implicit")
// token令牌有效期 单位s
.accessTokenValiditySeconds(1800)
// refreshtoken刷新令牌有效期 单位s 一般来说刷新令牌比令牌有效期长 便于使用刷新令牌换取令牌
.refreshTokenValiditySeconds(3600)
// 6、指定重定向地址 授权码认证模式和简化认证模式中必须
.redirectUris("http://www.baidu.com");
// 基本上来说 到这一步就完成了一个客户端的配置了
// 如果想要配置多个客户端 往下使用.and()连接
//.and()
//.withClient()
//.secret()
//.scopes()
//.authorizedGrantTypes()
//.accessTokenValiditySeconds()
//.refreshTokenValiditySeconds();
}
```
## 4、验证`授权码认证模式`和`简化认证模式`
现在我们已经把客户端重定向信息配置上了,再去验证一下`授权码认证模式`和`简化认证模式`是否可以使用。
### 4.1、授权码认证
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=code&redirect_uri=http://www.baidu.com`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
然而:


其实并不是我的步骤有错,把你40米的大长刀放下!
**发生这个的主要原因是:进行`oauth2`认证前,必须先经过`Spring Security`的认证。**
镇静!我们先继续把之前的问题一一解决,回头再说这个。
### 4.2、简化认证
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=token`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
结果如下:

果不其然,简化认证也会有这个问题。
## 5、二次总结
经过我们对第一步搭建Oauth2服务器中出现的问题进行深一步的配置,`密码认证模式`已经可以正常使用了,到现在为止,我们能使用的认证模式有`客户端认证模式`、`密码认证模式`,在第二步的深入配置中,我们有收获,也出现了新的问题,现在总结一下剩下的三个认证模式失败的原因。
刷新令牌认证模式:引入实现的`userDeatils`接口的实现类
授权码认证模式:未经过`Spring Security`的认证
简化认证模式:未经过`Spring Security`的认证
现在在把这些问题解决。
# 三、百尺竿头更进一步
## 1、引入实现的`userDeatils`接口的实现类
### 1.1、实现的`userDeatils`接口的实现类
在`config`包中创建`service`包,并创建`MyUserdDetailsService`类实现`UserDetailsService`接口,重写`loadUserByUsername`方法(也可以按你的习惯在其他地方创建,需要`springboot`能扫描到)。初始代码如下:
```java
/**
* @ClassName: MyUserdDetailsService
* @Description: 实现的`userDeatils`接口的实现类
* @author: liangxingming
* @date: 2020年05月07日 10:27
*/
@Service("myUserdDetailsService")
public class MyUserdDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
```
此时项目结构如下:

重写`loadUserByUsername`方法,代码如下:
```java
/**
* 此处加载数据库用户信息并在各种信息的校验
* @param s
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//为了方便,我直接创建了一个用户
//现如今Spring Security中密钥的存储格式是“{id}…………” 所以“{}”中的内容必要 详情移步官方文档
return new User("user", "{noop}123456", AuthorityUtils.createAuthorityList("ROLE_USER"));
}
```
### 1.2、注入`MyUserdDetailsService`
在`AuthoricationServerConfig`配置类(`Authorication`认证中心)中注入`MyUserdDetailsService`,代码如下:
```java
// 8、注入MyUserdDetailsService
@Resource
private MyUserdDetailsService myUserdDetailsService;
```
### 1.3、修改`configure(AuthenticationManagerBuilder auth)`方法
将之前使用的用户信息注释掉,因为此时用户信息已经由实现的`userDeatils`接口的实现类的`loadUserByUsername`方法提供,此时该方法调用父类方法即可,代码如下:
```java
/**
* 重写用户相关配置
* 2、主要配置用户源的一些配置
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
// 此处我将一个简单的用户初始化在内存中
/*auth.inMemoryAuthentication()
// 密码编码
.passwordEncoder(new BCryptPasswordEncoder())
// 用户名
.withUser("user")
// 密码
.password(new BCryptPasswordEncoder().encode("123456"))
// 用户角色
.roles("USER");*/
}
```
### 1.4、将`MyUserdDetailsService`交给服务器端点
在`AuthoricationServerConfig`配置类(`Authorication`认证中心)的`configure(AuthorizationServerEndpointsConfigurer endpoints)`方法中将`MyUserdDetailsService`交给服务器端点,代码如下:
```java
/**
* 配置授权服务器端点的属性和增强的功能。
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//super.configure(endpoints);
endpoints.authenticationManager(authenticationManager); //5、将authenticationManager交给服务器端点
endpoints.userDetailsService(myUserdDetailsService); // 9、将myUserdDetailsService交给服务器端点
}
```
## 2、验证`刷新令牌认证模式`
使用`刷新令牌认证模式`前需要先获得`refresh_token`,我们经过`密码认证模式`获得`refresh_token`为`f6f1edca-3417-4797-8862-f319aa9fa38d`
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`当然是`client_credentials`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
`refresh_token`是`密码认证模式中`拿到的`f6f1edca-3417-4797-8862-f319aa9fa38d`

3、校验
可以看到,发起请求后顺利获得了授权。

至此,我们已经可以使用`客户端认证模式`、`密码认证模式`、`刷新令牌认证模式`三种认证模式了。
## 3、配置`Spring Security`的认证
对`Spring Security`的认证的配置,在`WebSecurityConfig`配置类中的`configure(HttpSecurity http)`方法中配置,代码如下:
```java
/**
* 网络安全配置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//super.configure(http);
http.authorizeRequests()
.antMatchers("/**") // 拦截路径
.permitAll() // 处理方式
.and()
.httpBasic();// 9、通过httpBasic进行认证
}
```
## 4、验证`授权码认证模式`和`简化认证模式`
现在我们已经把配置了对`Spring Security`的认证,再去验证一下`授权码认证模式`和`简化认证模式`是否可以使用。
### 4.1、授权码认证
#### 4.1.1、获取授权码
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=code&redirect_uri=http://www.baidu.com`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
可以看到现在已经出现用户名、密码输入框了

现在将用户名`user`、密码`123456`输入,点击登录,可以看到询问对话,我们当然选`允许approve`,然后点击`授权authorize`

点击授权后,可以看到地址已重定向到`百度`,并且在地址后拼接了`授权码`,此时`授权码`就是code等号后的内容

#### 4.1.2、授权码认证
1、配置`HTTP Basic`认证
`Authorization TYPE` 选择 `Basic Auth`
用户名和密码分别对应`2.1`中的`client-id`和`secret`

2、配置参数
`grant_type`是`authorization_code`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
`code`是上一步获取的授权码
`redirect_uri`是重定向地址

3、校验
发送请求后成功获得授权,证明此时授权码认证已经可以使用了。

### 4.2、简化认证
使用`浏览器`访问`http://localhost:8080/oauth/authorize?client_id=client&client_secret=123456&response_type=token`
`client_id`和`client_secret`分别对应`2.1`中的`clientId`和`secret`
回车后,可以直接看到询问对话,不需要输入用户名和密码,选`允许approve`,然后点击`授权authorize`

点击授权后,可以看到地址已重定向到`百度`,并且在地址后拼接了`token`等信息,此时就是获得授权成功了

由此可以看到,`简化认证模式`安全性比较低,需谨慎使用
# 四、总结
到此刻,我们已经将一个具有五种认证模式的授权服务器搭建起来了,希望可以帮助到大家。我也是一个半路程序员,有讲的不对的地方望大家海涵,不对的地方也希望大家帮忙指出,有更好的优化也可以分享给大家。当然这里面还有很多其他东西需要讲,比如token的存储、数据库用户信息的获取、自定义授权认证类型等,我需要再整理一下再发出来。