前言
Spring Security OAuth 2.0 是 OAuth 2.0 协议的 Java 实现。这篇章主要是记录如何基于 Spring Security OAuth 2.0 搭建授权服务器和资源服务器。
概述
OAuth 2.0 是一个授权协议,可以用于以下的一些场景:
1、使用授权码模式向第三方系统授权,授予有限的访问权限。A 系统想获取 B 系统的用户信息,B 不需要提供用户名、密码给 A 系统(如果直接将密码给 A,A 就会拥有所有的权限,不太合适),而是经过用户授权提供令牌给 A,A 通过令牌获得 B 系统有限得访问权限。
2、使用密码模式,向信任的第一方系统进行授权。
前期准备
主要使用的包如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.9.RELEASE</version>
</dependency>
授权服务器的配置
我们可以继承 AuthorizationServerConfigurerAdapter
并重写其中的方法来实现一些我们想要的功能,如:定义如何获取客户端信息等。
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final SysClientDetailsService sysClientDetailsService;
private final SysUserService sysUserService;
private final AuthenticationManager authenticationManager;
private final TokenStore tokenStore;
/**
* 安全策略配置
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 获取 token key 需要进行 basic 认证客户端信息
.tokenKeyAccess("isAuthenticated()")
// 获取 token 信息同样需要 basic 认证客户端信息
.checkTokenAccess("isAuthenticated()");
}
/**
* 配置如何读取客户端信息:从数据库当中读取
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(sysClientDetailsService);
}
/**
* 认证端点的配置,如:配置token的获取方式配置(从数据库当中获取、内存中获取、缓存中获取...)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(sysUserService)
.tokenStore(tokenStore);
}
}
使用 @EnableAuthorizationServer
注解后,系统为我们配置了以下端点(Endpoint):
-
AuthorizationEndpoint
:授权:/oauth/authorize
-
TokenEndpoint
:获取令牌:/oauth/token
-
CheckTokenEndpoint
:令牌校验:/oauth/check_token
-
WhitelabelErrorEndpoint
: 授权失败:/oauth/error
资源服务器的配置
资源服务器和授权服务器可以是单独的一个应用,也可以是分开的。
@Configuration
@EnableResourceServer
@AllArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final ResourceServerProperties resourceServerProperties;
private final TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore)
.resourceId(resourceServerProperties.getResourceId());
}
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
// 前后端分离下,可以关闭 csrf
http.csrf().disable();
}
}
在 application.yml 文件中配置授权服务器的相关信息
security:
oauth2:
resource:
token-info-uri: http://127.0.0.1:8080/oauth/check_token
id: test # 资源id,可能存在多个资源服务器,可以配置客户端拥有哪些资源的访问权限
client: #资源服务器作为客户向授权服务器申请令牌
client-id: test
client-secret: oauth2
access-token-uri: http://localhost:8080/oauth/token
scope: READ,WRITE
示例
密码模式
- 获取令牌
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic b2F1dGgyOm9hdXRoMg==' \
--data-urlencode 'password=123456' \
--data-urlencode 'username=test' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=READ WRITE'
- 通过令牌访问需要权限的接口
curl --location --request GET 'http://127.0.0.1:8081/read' \
--header 'Authorization: bearer 2ea0da70-cfb3-44a8-8487-4fdebd5d9993'
授权码模式
- 获取授权码
http://127.0.0.1:8080/oauth/authorize?response_type=code&client_id=example&redirect_uri=http://example.com&scope=READ
- 根据授权码获取令牌
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic ZXhhbXBsZTpvYXV0aDI=' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=nW4iqR' \
--data-urlencode 'redirect_uri=http://example.com' \
--data-urlencode 'client_id=example' \
--data-urlencode 'scope=READ'
- 刷新令牌
curl --location --request POST 'http://127.0.0.1:8080/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic ZXhhbXBsZTpvYXV0aDI=' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=f9182c66-697a-4ea2-99a4-2b282617fca8'