内容简介
- 微服务下常见认证解决方案;
- OAuth认证与授权;
- JWT认证介绍;
- Spring Cloud的OAuth2实现;
单体应用转变为分布式应用
单体应用转变为分布式应用在架构方式上存在较大区别,单体应用下的简单架构方式如下:
分布式应用的安全认证相对更加复杂,既要考虑它的安全性,一致性,还要考虑它的性能问题,开发成本等问题。分布式应用下的简单架构方式如下:
常见的授权认证解决方案
单点登录SSO
统一的认证服务器(CAS)
缺点:每一个用户请求都要经过认证服务器,容易形成瓶颈Session共享
Session共享存储,共享用户信息(Spring Session)
缺点:不同平台、不同架构中实现难度较高Token认证
认证服务器颁发包含用户身份信息的Token,资源服务器验证合法性(OAuth)
缺点:资源服务器想注销用户Token较困难Token+Gateway
所有的请求通过网关,可在网关中完成认证或注销Token
缺点:Gateway必须保证高可用
OAuth认证介绍
OAuth是一个开放的协议,提供了标准授权方式去访问受保护的资源。
常见的使用者如:Twitter、微信、QQ、GitHub
OAuth角色定义
- 客户端:发起请求的应用
- 资源拥有者: 用户
- 资源服务器: 资源所在的服务器
- 授权服务器:提供Token相关操作的服务器
OAuth认证流程
客户端的授权模式
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
授权码模式
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
授权码模式是功能最完整、流程最严密的授权模式。
简化模式
简化模式跳过了”授权码”这个步骤,使用较少
密码模式
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
密码模式通常使用在所有的服务都是由同一家公司提供的情况下。
请求示例:1
http://localhost:9061/oauth/token?username=user_1&password=123456&grant_type=password&scope=all&client_id=client_2&client_secret=123456
响应示例:1
2
3
4
5
6
7{
"access_token": "8c95f119-2e2a-45c9-a70a-660e55205d6f",
"token_type": "bearer",
"refresh_token": "8033f033-488b-42d2-8bed-4623ab5f66a4",
"expires_in": 43199,
"scope": "all"
}
客户端模式
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
为了保证安全,该模式一般会配合数字证书,需要使用客户端证书进行认证。
请求示例:1
http://localhost:9061/oauth/token?grant_type=client_credentials&scope=all&client_id=client_1&client_secret=123456
响应示例:1
2
3
4
5
6{
"access_token": "47bdce37-d570-439b-9d63-48ca736dbd8c",
"token_type": "bearer",
"expires_in": 43199,
"scope": "all"
}
JWT协议介绍
JSON Web Token(JWT)是一种用于传递认证信息的基于JSON的开放标准。
JWT可单独用于请求认证,也可以与其它认证协议配合使用,如OAuth2.0。
JWT认证流程
A)客户端传入用户密码请求JWT
B)认证中心验证通过后创建JWT并返回
C)客户端使用JWT访问资源服务器
D)资源服务器验证JWT,通过后返回资源内容
JWT结构
JWT 由三段信息构成,每一段内容都是一个 JSON 对象,将每一段 JSON 对象采用 BASE64 编码后,中间用.
连接:
- 第一段为头部(Header): 基本信息,例如其类型以及签名所用的算法等
- 第二段为载荷(Payload) :存放各种声明信息
- 第三段为签名(Signature): header 和 payload加密结果,可使用对称加密或非对称加密
客户端模式请求示例:1
http://localhost:9062/oauth/token?grant_type=client_credentials&scope=all&client_id=client_1&client_secret=123456
生成的JWT:
1 | { |
密码模式请求示例:
1 | http://localhost:9062/oauth/token?username=user_1&password=123456&grant_type=password&scope=all&client_id=client_2&client_secret=123456 |
生成JWT:1
2
3
4
5
6
7
8{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZXRlcm1jbG91ZHMiXSwidXNlcl9uYW1lIjoidXNlcl8xIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTUyMDg4ODUxNCwiYXV0aG9yaXRpZXMiOlsiVFdUOlBFS1k3NzcwMjMsRVhUUklQOjEzNTg5OTkwMCJdLCJqdGkiOiJjY2E0YWFlYy1hNmVlLTQ4ZDktODBkYS1jYzgxN2E2ODM4NDQiLCJjbGllbnRfaWQiOiJjbGllbnRfMiJ9.lvVfQ5H23gIU_i2cMItdpVfdVkGUL7zI1Tbs59wZa74",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZXRlcm1jbG91ZHMiXSwidXNlcl9uYW1lIjoidXNlcl8xIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImNjYTRhYWVjLWE2ZWUtNDhkOS04MGRhLWNjODE3YTY4Mzg0NCIsImV4cCI6MTUyMTQ1MDExNCwiYXV0aG9yaXRpZXMiOlsiVFdUOlBFS1k3NzcwMjMsRVhUUklQOjEzNTg5OTkwMCJdLCJqdGkiOiJlMzcxMjdhZC0yMzRiLTRkMDctODBiNC1lYzgzMmY2MzQ5YzciLCJjbGllbnRfaWQiOiJjbGllbnRfMiJ9.BswJy_3hyv_XqtM10sex1XRteJcDoqAwvogL3agt1KY",
"expires_in": 43199,
"scope": "all",
"jti": "cca4aaec-a6ee-48d9-80da-cc817a683844"
}
JWT优点
定义了标准格式,传递与解析简单
JWT缺点
JWT更多的是一种开放TOKEN传递协议,对于多种业务场景(授权模式)的实现支持只停留在理伦层面。
OAuth与JWT结合
OAuth方案中,获得了认证服务器颁发的Token后,资源服务器如何验证与鉴权呢?有几种方案:
资源服务器请求认证服务器验证Token
缺点:对认证服务器依赖过高,容易出现认证服务器性能问题共享Token存储信息,如Redis共享
缺点:无法解决外部应用验证问题,如使用github认证服务器资源服务器自验证,如JWT协议
缺点:Token注销是个难点,可以与Gateway结合控制。
Spring Cloud集成OAuth2.0
实际由spring-security-oauth2提供实现。实现较为复杂。
Shiro实现OAuth2.0相对简单,但与spring cloud集成不是很好。
认证服务器主要需要配置:
- AuthorizationServer配置
资源服务器主要需要配置:
- ResourceServer配置
1 |
|
配置示例:1
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
67
68
69
70
71
72
73
74
75/**
* AuthorizationServerConfig.java
* 功能:授权服务器配置
* @author kavenran 2018年2月23日
*/
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager authenticationManager;
private DataSource dataSource;
//自定义用户服务覆盖默认User
private UserDetailsServiceImpl userDetailsService;
//token存储数据库
// @Bean
// public JdbcTokenStore jdbcTokenStore() {
// return new JdbcTokenStore(dataSource);
// }
//JWT存储Token
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
//配置OAuth2的客户端相关信息,client信息包括:clientId、secret、scope、authorizedGrantTypes
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(new JdbcClientDetailsService(dataSource));//基于oauth_client_token表的操作
}
//配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore())
.accessTokenConverter(jwtAccessTokenConverter())
.tokenServices(defaultTokenServices())
.authenticationManager(authenticationManager);
}
//Token校验方式采用JWT协议
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); //CustomTokenEnhancer() //自定义Token信息
converter.setSigningKey("123");
return converter;
}
//按默认规则生成Token
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(jwtTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(new JdbcClientDetailsService(dataSource));
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // token有效期自定义设置,默认12小时
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天,这里修改为7天
tokenServices.setTokenEnhancer(jwtAccessTokenConverter());//使用JWT生成密钥
return tokenServices;
}
//对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security.checkTokenAccess("isAuthenticated()");//allow check token
security.allowFormAuthenticationForClients();
}
}
ResourceServer配置
主要配置以下两个:1
2
3
4
5
6
7
8
9
10
public void configure(ResourceServerSecurityConfigurer resources) {
//配置资源信息
}
public void configure(HttpSecurity http) throws Exception {
//配置安全策略信息
}
配置示例:1
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/**
* ResourceServerConfig.java
* 功能:资源服务器配置
* @author kavenran
* 2018年2月23日
*/
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
//自定义的JWT
JwtTokenStore jwtTokenStore;//自定义的tokenStore
//自定义的converter
JwtAccessTokenConverter jwtAccessTokenConverter;
//设置资源ID,经过授权访问此资源ID的Token才能访问此资源
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("order-service").tokenStore(jwtTokenStore);
}
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
.antMatchers("/order/**").authenticated();//配置order访问控制,必须认证过后才可以访问
}
}
OAuth自带的认证endpoint:1
2
3
4
5
6
7
8/oauth/authorize:验证
/oauth/token:获取token
/oauth/confirm_access:用户授权
/oauth/error:认证失败
/oauth/check_token:资源服务器用来校验token
/oauth/token_key:如果jwt模式则可以用此来从认证服务器获取公钥
刷新token是通过`oauth/token?grant_type=refresh_token`实现的
参考文档
理解OAuth 2.0
认证鉴权与API权限控制在微服务架构中的设计与实现
从零开始的Spring Security OAuth2