Spring Security-23-实现标准的OAuth服务提供商

本文开始将构建一个 OAuth 服务提供商, 主要就是两大块, 认证服务器和资源服务器, 之后的代码都写在 app 的项目里,所以先把demo项目中的 web 的依赖改成 app 的依赖

1
2
3
4
5
<dependency>
<groupId>com.security</groupId>
<artifactId>security-example-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

先把项目启动一下, 修改一些启动过程中可能会出现的问题

问题一

1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************

Description:

Field authenticationFailureHandler in com.security.example.core.validate.filter.ValidateCodeFilter required a bean of type 'org.springframework.security.web.authentication.AuthenticationFailureHandler' that could not be found.


Action:

Consider defining a bean of type 'org.springframework.security.web.authentication.AuthenticationFailureHandler' in your configuration.

这个就是验证码的处理器 ValidateCodeFilter 需要一个 AuthenticationFailureHandler 就是认证失败处理器

我们之前的自定义的认证成功失败处理器都是放在 web 项目里面的,因为 app 和 web 的请求成功失败可能不一样,所以先复制一份到 app 项目中来,之后再做修改

复制完成之后再次启动

问题二

1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************

Description:

Field passwordEncoder in com.security.example.demo.controller.TestController required a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' that could not be found.


Action:

Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.

少了一个 PasswordEncoder, 这个之前也是写在了 web 项目里面,这个是通用的,所以挪到core项目里面 SecurityConfig 中 :

1
2
3
4
5
6
7
8
@Configuration
@EnableConfigurationProperties({SecurityProperties.class})
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}

再次启动就ok了

认证服务器实现

app 项目中,新建一个类 MyAuthorizationServerConfig, 代码如下:

1
2
3
4
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig {
}

这就ok了, 一个注解就实现了

重启项目, 这注意一下控制台的打印, 重点如下:
image

image

首先是分配了一个默认的 clientIdsecret ,因为我们没有配置,所以每次启动的时候会为我们自动生成一个

下面图中,就是加了一些关于认证的请求

这里的 clientIdsecret 可以自己做一下配置, 以免每次重启项目都要去修改, 在 application.yml 中,如下:

1
2
3
4
5
security:
oauth2:
client:
client-id: testAppid
client-secret: testSecret

几种授权模式的实现

Spring Security OAuth 是实现的标准的 OAuth2 协议,所以认证请求的参数什么的,在 OAuth2 的官网上可以找到, 传送门

授权码模式

最常用的授权模式,之前我们接QQ微信的时候就是授权码模式

第一步,获取 code 授权码

请求地址

1
http://localhost:8080/oauth/authorize?response_type=code&client_id=testAppid&redirect_uri=http://www.example.com&scope=all

这里有几个参数:

  • response_type: 固定是code
  • client_id: 上面配置的client-id
  • redirect_uri: 回调地址
  • scope: 作用域

请求,如下图:

image

这里可以看到,要输入用户名和密码,这里输入用户名和密码, 最终会交给我们的 MyUserDetailsService 去验证

输入用户名密码,点击登录,如下:

image

这里返回一个 403 ,没有权限,原因是 用户必须有一个 ROLE_USER 这样的角色,在 MyUserDetailsService 中,返回用户信息的时候加一下:

1
2
3
4
5
6
7
8
return new org.springframework.security.core.userdetails.User(
user.getId().toString(),
user.getPassword(),
user.getEnable(),
true,
true,
!user.getLocked(),
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));

重启项目,再次请求授权码的页面, 如下:

image

这里就是个用户授权页了, 有同意和拒绝,点击同意,如下:
image

这里会跳转到回调地址,然后携带了code参数, 之后就可以拿code去交换token了

换取 token 的请求地址是 POST /oauth/token ,这里用 postMan 或者其他工具去测试, 请求如下:
image
这里是需要填写我们配置的 client-idclient-secret
image

image

这里的参数也是固定的

  • grant_type: 固定 authorization_code ,表示授权码模式
  • client_id: 配置的clientId
  • code: 前面获取到的授权码
  • redirect_uri: 回调地址,和获取授权码时候传的地址是一样的
  • scope: 作用域

点击请求,返回如下:

1
2
3
4
5
6
7
{
"access_token": "8fac95e3-8f50-498d-9dd5-449517e5ff5c",
"token_type": "bearer",
"refresh_token": "182f9e9a-fabf-46f1-94a0-780519239045",
"expires_in": 43199,
"scope": "all"
}

密码模式

和授权码模式的请求地址是一样的, 参数有些不同, 如下:
image

这里的 grant_typepassword 表示是密码模式,然后传的参数是 usernamepassword, 就是服务提供商的用户名和密码,这里的返回值也和上面的是一样的

注意

看一下我这里的返回的token,两种模式的请求,返回token是一样的, 在固定的时间段内, 不管请求多少次,返回的token都是相同的

剩余的两种模式不常用,就不演示了

资源服务器的实现

和认证服务器一样, 也是一个注解就实现, 新建类 MyResourcesServerConfig

1
2
3
4
5
@Configuration
@EnableResourceServer
public class MyResourcesServerConfig {

}

重启项目,随便访问一个接口,效果如下:
image

这里是因为我们没有把token传过去

重新获取一下token, 然后放在请求头中, 格式是: Authorization : token_type access_token ,如图:
image

token 传过去之后就可以访问接口了

这里要注意,现在的token生成是放在内存中的, 所以每次重启项目之后都要重新请求一下

总结

熟悉常用两种授权模式的流程,以及传递的参数等, 获取到token之后,通过请求头传递

本文代码传送门