一、分布式系统认证方案
1.1 什么是分布式系统
具有分布式架构的系统叫分布式系统,分布式系统的运行通常依赖网络,它将单体结构的系统分为若干服务,服务之间通过网络交互来完成用户的业务处理,当前流行的微服务架构就是分布式系统架构,如下图:
分布式系统具体如下基本特点:
1、分布性:每个部分都可以独立部署,服务之间交互通过网络进行通信,比如:订单服务、商品服务。
2、伸缩性:每个部分都可以集群方式部署,并可针对部分结点进行硬件及软件扩容,具有一定的伸缩能力。
3、共享性:每个部分都可以作为共享资源对外提供服务,多个部分可能有操作共享资源的情况。
4、开放性:每个部分根据需求都可以对外发布共享资源的访问接口,并可允许第三方系统访问。
1.2 分布式认证需求
分布式系统的每个服务都会有认证、授权的需求,如果每个服务都实现一套认证授权逻辑会非常冗余,考虑分布式系统共享性的特点,需要由独立的认证服务处理系统认证授权的请求;考虑分布式系统开放性的特点,不仅对系统内部服务提供认证,对第三方系统也要提供认证。分布式认证的需求总结如下:
统一认证授权
提供独立的认证服务,统一处理认证授权。
无论是不同类型的用户,还是不同种类的客户端(web端,H5、APP),均采用一致的认证、权限、会话机制,实现统一认证授权。
要实现统一则认证方式必须可扩展,支持各种认证需求,比如:用户名密码认证、短信验证码、二维码、人脸识别等认证方式,并可以非常灵活的切换。
应用接入认证
开放部分API给接入第三方使用,一方应用(内部 系统服务)和三方应用(第三方应用)均采用统一机制接入。
1.3 分布式认证方案
1.3.1 选型分析
1、基于session的认证方式
在分布式的环境下,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认证。
这个时候,通常的做法有下面几种:
Session复制:多台应用服务器之间同步session,使session保持一致,对外透明。
Session黏贴:当用户访问集群中某台服务器后,强制指定后续所有请求均落到此机器上。
Session集中存储:将Session存入分布式缓存中,所有服务器应用实例统一从分布式缓存中存取Session。
总体来讲,基于session认证的认证方式,可以更好的在服务端对会话进行控制,且安全性较高。但是,session机制方式基于cookie,在复杂多样的移动客户端上不能有效的使用,并且无法跨域,另外随着系统的扩展需提高session的复制、黏贴及存储的容错性。
2、基于token的认证方式
基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可以实现web和app统一认证机制。其缺点也很明显,token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担。
1.3.2 技术方案
根据 选型的分析,决定采用基于token的认证方式,它的优点是:
1、适合统一认证的机制,客户端、一方应用、三方应用都遵循一致的认证机制。
2、token认证方式对第三方应用接入更适合,因为它更开放,可使用当前有流行的开放协议Oauth2.0、JWT等。
3、一般情况服务端无需存储会话信息,减轻了服务端的压力。
分布式系统认证技术方案见下图:
流程描述:
(1)用户通过接入方(应用)登录,接入方采取OAuth2.0方式在统一认证服务(UAA)中认证。
(2)认证服务(UAA)调用验证该用户的身份是否合法,并获取用户权限信息。
(3)认证服务(UAA)获取接入方权限信息,并验证接入方是否合法。
(4)若登录用户以及接入方都合法,认证服务生成jwt令牌返回给接入方,其中jwt中包含了用户权限及接入方权限。
(5)后续,接入方携带jwt令牌对API网关内的微服务资源进行访问。
(6)API网关对令牌解析、并验证接入方的权限是否能够访问本次请求的微服务。
(7)如果接入方的权限没问题,API网关将原请求header中附加解析后的明文Token,并将请求转发至微服务。
(8)微服务收到请求,明文token中包含登录用户的身份和权限信息。因此后续微服务自己可以干两件事:1,用户授权拦截(看当前用户是否有权访问该资源)2,将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)流程所涉及到UAA服务、API网关这三个组件职责如下:
1)统一认证服务(UAA)
它承载了OAuth2.0接入方认证、登入用户的认证、授权以及生成令牌的职责,完成实际的用户认证、授权功能。
2)API网关
作为系统的唯一入口,API网关为接入方提供定制的API集合,它可能还具有其它职责,如身份验证、监控、负载均衡、缓存等。API网关方式的核心要点是,所有的接入方和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。
二、OAuth2.0
2.1 OAuth2.0介绍
OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用,从而用户不需要注册就可以登录系统了。
1、客户端请求第三方授权
2、资源拥有者同意给客户端授权
3、客户端获取到授权码,请求认证服务器申请令牌
4、认证服务器向客户端响应令牌
5、客户端请求资源服务器的资源
6、资源服务器返回受保护资源
资源服务器校验令牌的合法性,如果合法则向用户响应资源信息内容。
OAauth2.0包括以下角色:
1、客户端
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源
2、资源拥有者
通常为用户,也可以是应用程序,即该资源的拥有者。
3、授权服务器(也称认证服务器)
用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌
(access_token),作为客户端访问资源服务器的凭据。
4、资源服务器
存储资源的服务器
注意:
client_id:客户端标识
client_secret:客户端秘钥
授权服务器对两种OAuth2.0中的两个角色进行认证授权,分别是资源拥有者、客户端。
2.2 Spring Cloud Security OAuth2
OAuth2.0的服务提供两个服务,即授权服务 (也叫认证服务) 和资源服务 ,使用 Spring Security OAuth2 的时候你可以选择把它们在同一个应用程序中实现,也可以选择建立使用同一个授权服务的多个资源服务。
授权服务 (Authorization Server)配置一个认证服务必须要实现的endpoints:
AuthorizationEndpoint 服务于认证请求。默认 URL: /oauth/authorize 。
TokenEndpoint 服务于访问令牌的请求。默认 URL: /oauth/token 。
资源服务 (Resource Server),应包含对资源的保护功能,对非法请求进行拦截,对请求中token进行解析鉴权,下面的过滤器用于实现 OAuth 2.0 资源服务:OAuth2AuthenticationProcessingFilter用来对请求给出的身份令牌解析鉴权。
认证流程如下:
1、客户端请求UAA授权服务进行认证。
2、认证通过后由UAA颁发令牌。
3、客户端携带令牌Token请求资源服务。
4、资源服务校验令牌的合法性,合法即返回资源信息。
2.2.1 环境搭建
搭建父工程、uaa(统一认证)工程、Order资源服务工程、discovery注册中心
2.2.2 授权服务器配置
配置注册中心:需要配置application.yml和启动类
配置uaa:配置客户端详情服务、配置令牌管理服务、配置令牌访问端点、配置令牌访问的安全策略
配置令牌管理服务时候需要配置令牌存储策略(TokenStore),可以配置为内存存储、jdbc储存、jwt存储
配置令牌访问端点时候需要配置认证管理器、userDetailsService、授权码
WebSecurityConfig配置拦截器和密码编码器
2.2.3 授权码模式
授权码访问地址:/uaa/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参数(授权码)。
获取令牌地址:/uaa/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)不会经过浏览器或移动端的App,而是直接从服务端去交换,这样就最大限度的减小了令牌泄漏的风险。
2.2.4 简化模式
访问地址:/uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
参数描述同授权码模式 ,注意response_type=token,说明是简化模式。
简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法接收授权码。
2.2.5 密码模式
访问地址:/uaa/oauth/token?
client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123
参数解释:
client_id:客户端准入标识。
client_secret:客户端秘钥。
grant_type:授权类型,填写password表示密码模式
username:资源拥有者用户名。
password:资源拥有者密码。
密码模式一般用于我们自己开发的
2.2.6 客户端模式
访问地址:/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials
参数解释:
client_id:客户端准入标识。
client_secret:客户端秘钥。
grant_type:授权类型,填写client_credentials表示客户端模式
这种模式一般用来提供给我们完全信任的服务器端服务
2.2.7 资源服务测试
- 配置资源服务器ResouceServerConfig,在ResouceServerConfig配置资源服务令牌解析服务,RemoteTokenServices是通过 HTTP 请求来解码令牌
- 添加安全访问控制WebSecurityConfig
- 生成令牌token
- 测试在post中加入地址:http://localhost:53021/order/session,在headers中加键:Authorization
值:Bearer token值
2.3 JWT令牌
2.3.1 JWT令牌介绍
普通的令牌需要每次请求授权服务,JWT令牌不需要每次都请求认证服务完成授权,用户认证通过会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT令牌访问资源服务,资源服务根据事先约定的算法自行完成令牌校验
JWT令牌用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
JWT令牌的优点:
1)jwt基于json,非常方便解析。
2)可以在令牌中自定义丰富的内容,易扩展。
3)通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4)资源服务使用JWT可不依赖认证服务即可完成授权。
缺点:
1)JWT令牌较长,占存储空间比较大,不过长度也在可接收范围内。
2.3.2 JWT令牌结构
JWT令牌由三部分组成,每部分中间使用点(.)分隔
- Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
- Payload
第二部分是负载,内容也是一个json对象它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
{
"sub": "1234567890",
"name": "456",
"admin": true
}
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
- Signature
第三部分是签名,此部分用于防止jwt内容被篡改。这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
2.3.3 配置JWT令牌服务
在uaa中配置jwt令牌服务,即可实现生成jwt格式的令牌。
- 在TokenConfig配置生成JWT令牌
//JWT令牌存储方案
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
//JWT使用秘钥生成令牌
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(“123321qwert”); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
- 在授权服务器AuthorizationServer配置义JWT令牌服务
//JWT使用秘钥生成令牌
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
//令牌管理服务
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);//客户端详情服务
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
//令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
用postman测试生成令牌
2.3.4 校验jwt令牌
- 将授权服务中UAA的TokenConfig类拷贝到order资源 服务中
- 效验令牌服务,把tokenServices改为tokenStore
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
// .tokenServices(tokenService())//验证令牌的服务
.tokenStore(tokenStore)
.stateless(true);
}
2.4 完善环境配置
创建oauth_client_details和oauth_code表
2.4.1 配置授权服务
AuthorizationServer中ClientDetailsService和AuthorizationCodeServices从数据库读取数据,
@Autowired
PasswordEncoder passwordEncoder;
//将客户端信息存储到数据库
@Bean
public ClientDetailsService clientDetailsService(DataSource dataSource) {
ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
//配置客户端详情服务
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService);
/*//暂时使用内存方式
clients.inMemory()// 使用in‐memory存储
.withClient("c1")// client_id客户端id
.secret(new BCryptPasswordEncoder().encode("secret"))//客户端秘钥
.resourceIds("res1")//资源列表 服务id
// 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials
.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//加上验证回调地址
.redirectUris("http://www.baidu.com");*/
}
配置授权码从数据库中获取
//授权码使用数据库用jdbc获取
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
}
转载请注明:Spring Cloud整合 Spring Security OAuth2.0认证授权 | 胖虎的工具箱-编程导航