Spring Security Oauth2
# 1 分布式系统认证方案
User Account and Authentication (UAA) Server
承载了OAuth2.0接入方认证、登入用户的认证、授权以及生成令牌的职责,完成实际的用户认证、授权功能。
# 2 Spring Cloud Security OAuth2
# 2.1 UAA授权服务配置
# 2.1.1 创建AuthorizationServer
/**
* 认证中心的配置
* @EnableAuthorizationServer:这个注解标注这是一个认证中心
* 继承AuthorizationServerConfigurerAdapter
*/
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// ... ...
}
2
3
4
5
6
7
8
9
10
需重写 AuthorizationServerConfigurerAdapter
的几个方法。
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
public AuthorizationServerConfigurerAdapter() {}
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}
}
2
3
4
5
6
7
8
9
configure(AuthorizationServerSecurityConfigurer security)
配置令牌端点的安全约束。
configure(ClientDetailsServiceConfigurer clients)
配置客户端详情服务(ClientDetailsService),客户端详情信息在 这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
configure(AuthorizationServerEndpointsConfigurer endpoints)
配置令牌(token)的访问端点和令牌服务(token services)。
# 2.1.2 配置客户端详细信息
ClientDetailsServiceConfigurer
能够使用 内存
或者 JDBC
来实现客户端详情服务 ClientDetailsService
, ClientDetailsService
负责查找 ClientDetails
,而 ClientDetails
有几个重要的属性如下列表:
- clientId :
必须的
用来标识客户的Id。 - secret :
需要值得信任的客户端
客户端安全码,如果有的话。 - scope : 用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
- authorizedGrantTypes : 此客户端可以使用的授权类型,默认为空。
- authorities : 此客户端可以使用的权限
基于Spring Security authorities
。
内存方式
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService);
// 使用in‐memory存储
clients.inMemory()
// client_id
.withClient("c1")
.secret(new BCryptPasswordEncoder().encode("secret"))
.resourceIds("res1")
// 该client允许的授权类型
.authorizedGrantTypes(
"authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// 允许的授权范围 .autoApprove(false)
.scopes("all")
//加上验证回调地址
.redirectUris("http://www.baidu.com");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JDBC方式
/**
* === 1.客户端详情服务,并不是所有的客户端都能接入授权服务
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//使用JdbcClientDetailsService,从数据库中加载客户端的信息
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}
2
3
4
5
6
7
8
# 2.1.3 令牌访问端点配置
配置授权类型(Grant Types)
AuthorizationServerEndpointsConfigurer
通过设定以下属性决定支持的授权类型 Grant Types
:
- authenticationManager : 认证管理器,当你选择了资源所有者密码
password
授权类型的时候,设置这个属性注入一个AuthenticationManager
对象。 - authorizationCodeServices : 这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对 象),主要用于
authorization_code
授权码类型模式。 - implicitGrantService : 这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
- tokenGranter : 当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的 需求的时候,才会考虑使用这个。
配置授权端点的URL(Endpoint URLs)
AuthorizationServerEndpointsConfigurer
这个配置对象有一个叫做 pathMapping()
的方法用来配置端点URL链接,有两个参数:
- 第一个参数:String 类型的,这个端点URL的默认链接。
- 第二个参数:String 类型的,你要进行替代的URL链接。
以上的参数都将以 /
字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping()
方法的第一个参数:
- /oauth/authorize : 授权端点。
- /oauth/token : 令牌端点。
- /oauth/confirm_access : 用户确认授权提交端点。
- /oauth/error : 授权服务错误信息端点。
- /oauth/check_token : 用于资源服务访问的令牌解析端点。
- /oauth/token_key : 提供公有密匙的端点,如果你使用JWT令牌的话。
需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问。
/**
* 令牌存储策略
*/
@Autowired
private TokenStore tokenStore;
/**
* 客户端存储策略,这里存储在数据库
*/
@Autowired
private ClientDetailsService clientDetailsService;
/**
* Security的认证管理器,密码模式需要用到
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 令牌管理服务的配置
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
//客户端端配置策略
services.setClientDetailsService(clientDetailsService);
//支持令牌的刷新
services.setSupportRefreshToken(true);
//令牌服务
services.setTokenStore(tokenStore);
//access_token的过期时间
services.setAccessTokenValiditySeconds(60 * 60 * 24 * 3);
//refresh_token的过期时间
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
//设置令牌增强,使用JwtAccessTokenConverter进行转换
services.setTokenEnhancer(jwtAccessTokenConverter);
return services;
}
/**
* 授权码模式的service,使用授权码模式authorization_code必须注入
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* === 2.管理令牌,配置令牌访问的端点
*/
@Override
@SuppressWarnings("ALL")
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
//设置异常WebResponseExceptionTranslator,用于处理用户名,密码错误、授权类型不正确的异常
.exceptionTranslator(new AuthenticationWebResponseExceptionTranslator())
//授权码模式所需要的authorizationCodeServices
.authorizationCodeServices(authorizationCodeServices())
//密码模式所需要的authenticationManager
.authenticationManager(authenticationManager)
//令牌管理服务,无论哪种模式都需要
.tokenServices(tokenServices())
//只允许POST提交访问令牌,uri:/oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 2.1.4 令牌端点的安全约束
/**
* === 3.令牌访问端点安全策略
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
//自定义ClientCredentialsTokenEndpointFilter,用于处理客户端id,密码错误的异常
AuthorizationTokenEndpointFilter endpointFilter = new AuthorizationTokenEndpointFilter(security, authenticationEntryPoint);
endpointFilter.afterPropertiesSet();
security.addTokenEndpointAuthenticationFilter(endpointFilter);
security
.authenticationEntryPoint(authenticationEntryPoint)
//开启/oauth/token_key验证端口权限访问
.tokenKeyAccess("permitAll()")
//开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("permitAll()");
//一定不要添加allowFormAuthenticationForClients,否则自定义的OAuthServerClientCredentialsTokenEndpointFilter不生效
//.allowFormAuthenticationForClients();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个 endpoint完全公开。
checkToken这个endpoint完全公开。
授权服务配置总结
- 先配置客户端详情。
需先知道客户信息从哪里读取
- 管理令牌,配置令牌访问的端点。
- 配置令牌访问端点安全策略。
# 2.2 授权类型
# 2.2.1 授权码模式
- 客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息。如:
/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
- client_id : 客户端准入标识。
- response_type : 授权码模式固定为code。
- scope : 客户端权限。
- redirect_uri : 跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。
- 浏览器出现向授权服务器授权页面,之后用户同意授权。
- 授权服务器将授权码(AuthorizationCode)转经浏览器发送给client(通过redirect_uri)。
/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://w ww.baidu.com
- client_id : 客户端准入标识。
- client_secret : 客户端秘钥。
- grant_type : 授权类型,填写authorization_code,表示授权码模式。
- code : 授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
- redirect_uri : 申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
- 授权服务器返回令牌(access_token)。
# 2.2.2 密码模式
- 客户端拿着资源拥有者的用户名、密码向授权服务器请求令牌(access_token),请求如下:
/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123
- client_id : 客户端准入标识。
- client_secret : 客户端秘钥。
- grant_type : 授权类型,填写password,表示密码模式。
- username : 资源拥有者用户名。
- password : 资源拥有者密码。
- 授权服务器将令牌(access_token)发送给client。
# 2.2.3 客户端模式
- 客户端向授权服务器发送自己的身份信息,并请求令牌(access_token),请求如下:
/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials
- client_id : 客户端准入标识。
- client_secret : 客户端秘钥。
- grant_type : 授权类型,填写client_credentials,表示客户端模式模式。
提示
这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因 此这种模式一般用来提供给我们完全信任的服务器端服务。比如,合作方系统对接,拉取一组用户信息。
# 2.2.4 简化模式
- 客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息。如:
/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
- client_id : 客户端准入标识。
- response_type : 简化模式固定为token。
- scope : 客户端权限。
- redirect_uri : 跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。
- 浏览器出现向授权服务器授权页面,之后用户同意授权。
- 授权服务器将授权码将令牌(access_token)以Hash的形式存放在重定向uri的fargment中发送给浏览器。