OAuth2优点在于,它允许开发人员与第三方服务提供商集成,使用这些服务进行用户验证授权,而无须不断的将用户凭据信息传递给第三方服务。
OAuth2简介
OAuth2是一个基于令牌的安全验证授权框架。它将安全性分解为一下4部分:
- 受保护资源:例如微服务,确保通过验证并具有适当授权的用户才能访问。
- 资源所有者:定义那些应用程序可以调用其服务,那些用户可以访问该服务,以及可以使用服务完成哪些事情。
- 应用程序:代表用户调用服务的应用程序
- OAuth2验证服务器:验证服务器是应用程序和正在使用的服务之间的中间人。
这4个组成部分互相作用对用户进行验证
OAuth2规范具有以下4种授权类型:
- 密码
- 客户端凭据
- 授权码
- 隐式
建立OAuth2的验证和授权功能
以下将以密码授权类型开展后续的介绍:
- 构建Spring Cloud OAuth2验证服务
- 注册一个已授权的应用程序,通过OAuth2服务验证和授权用户身份
- 使用postman对OAuth2服务进行验证
- 保护许可证服务和组织服务,使他们只能被已通过的验证的用户调用
构建Spring Cloud OAuth2验证服务
OAuth2验证服务使用一个新的Spring Boot 服务。
验证服务将验证用户凭据并颁发令牌,每当用户尝试访问由验证服务保护的服务时,验证服务将确认OAuth2令牌是否已由其颁发并尚未过期。
完成以下两件事:
- 添加引导类所需的MAVEN依赖
- 添加一个将作为服务的入口点的引导类
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
使用OAuth2服务注册客户端应用程序
OAuth2Config类定义OAuth2验证服务注册哪些应用程序。
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
/*AuthorizationServerConfigurerAdapter 类是Spinrg Security核心部分,
提供了执行关键验证和授权功能的基本机制*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
//定义哪些客户端将注册到服务
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//支持两种不同的存储:内存和JDBC
clients.inMemory()
//应用程序名称
.withClient("eagleeye")
//密钥
.secret("thisissecret")
//授权类型列表
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
//OAuth2服务器获取访问令牌时可以操作的作用域
.scopes("webclient", "mobileclient");
}
//提供的默认验证管理器和用户详细信息服务
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
配置用户
配置OAuth2服务器已验证用户ID,必须创建一个新类WebSecurityConfigurerAdapter
//扩展 Spring Security 核心 WebSecurityConfigurerAdapter
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
@Bean
//AuthenticationManager 用来处理验证
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
@Bean
//UserDetailsService 处理返回的用户信息,用户信息由Spring Security返回
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
@Override
//定义用户,密码,角色的地方
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("john.carnell").password("password1").roles("USER")
.and()
.withUser("william.woodward").password("password2").roles("USER", "ADMIN");
}
}
验证用户
获取令牌:
Http传递的表单参数:
- grant_type:执行Oauth2授权类型。此处使用密码授权。
- scope:应用程序作用于。此处定义了两个 webclient和mobileclient。
- username:登录名
- password:登录密码
从oauth/token调用返回的参数:
- access_token:OAuth2令牌,它将随用户对受保护资源的每个服务调用一起出示。
- token_type:令牌类型,最常用的令牌类型是不记名类型(bearer token)。
- refresh_token :包含一个可以提交回OAuth2服务器的令牌,以便在访问令牌过期后重新颁发一个访问令牌。
- expires_in:这是OAuth2访问令牌过期前的秒数。Sping中,授权令牌过期的默认值是12h。
- scope:此OAuth2令牌的有效作用域。
使用令牌关联检索用户:
调用受保护资源 oauth/user
@SpringBootApplication
@RestController
@EnableResourceServer
@EnableAuthorizationServer //代表该服务用作OAuth2服务
public class Application {
//当试图访问由OAuth2保护的服务时,将会用到这个断点
//此断点由受保护服务调用,以确认OAuth2访问令牌,检索受保护服务的用户所分配的角色
@RequestMapping(value = { "/user" }, produces = "application/json")
public Map<String, Object> user(OAuth2Authentication user) {
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("user", user.getUserAuthentication().getPrincipal());
userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
return userInfo;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
此处 ROLE_USER 是一个拼接 ROLE_XXXX XXXX代表之前分配给此用户的角色 USER。
使用OAuth2保护组织服务
建立受保护资源,需要执行以下操作:
- 将相应的Spring Security 和 OAuth2.jar 添加到要保护的服务中;
- 配置服务以指向OAuth2验证服务;
- 定义谁可以访问服务
将相应的Spring Security 和 OAuth2.jar 添加到要保护的服务中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
配置服务以指向OAuth2验证服务:
在application.yml文件中定义以下回调URL
security:
oauth2:
resource:
userInfoUri: http://localhost:8901/auth/user
告诉微服务,他是一个受保护资源:
@EnableResourceServer :强制执行一个过滤器,该过滤器会拦截对该服务的所有传入的调用,检查HTTP中是否存在OAuth2令牌,然后调用上述的userInfoUri中定义的回调URL来查看令牌是否有效。
定义谁可以访问服务
定义访问控制规则:
需要扩展 ResourceServerConfigurerAdapter 类并覆盖 configure()方法。
此处包含一些常见例子:
- 只有已通过验证的用户才能访问服务URL
- 只有具有特定角色的用户才能访问服务URL
用过验证用户保护服务:
由于没有传OAuth2访问令牌所以报一下错误
加上令牌信息后:
通过特定角色保护服务:
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
//antMatchers() 限制受保护的url 以及 delete请求
.antMatchers(HttpMethod.DELETE, "/v1/organizations/**")
//hasRole()允许访问角色列表,由逗号分隔
.hasRole("ADMIN")
.anyRequest()
.authenticated();
}
}
传播OAuth2访问令牌
如何将OAuth2令牌从一个服务传播到另一个服务:
实现这些流程需要做两件事:
1.需要修改zuul服务网关,以将OAuth2令牌传播到许可证服务。
组了不会传播Http首部如Cookie,Authorization 等 转发到下游服务,需要传播的话,需要在zuul网关的application.xml 或 sping cloud config数据存储中设置以下配置,里面没有包含Authorization:
zuul.sensitiveHeaders:Cookie,Set-Cookie
Zuul可以自动传播下游的OAuth2访问令牌,并通过使用@EnableOAuth2Sso注解来针对OAuth2服务的传入请求进行授权。
2.将许可证服务配置为OAuth2资源服务,建立所需的服务授权规则。
如果没有使用Spring Security ,开发人员需要编写一个servlet过滤器以从传入的许可证服务调用中获取Http首部,然后说动将他添加到许可证服务中的每个出站服务调用中。
Sping OAuth2 提供了一个支持OAuth2调用的新Rest模板类 OAuth2RestTemplate。需要先将他公开为一个可以被自动装配到调用另一个受OAuth2 抱负的服务的bean。
public class Application {
@Autowired
private ServiceConfig serviceConfig;
private static final Logger logger = LoggerFactory.getLogger(Application.class);
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
调用服务的方式与RestTemplate相同
@Component
public class OrganizationRestTemplateClient {
@Autowired
OAuth2RestTemplate restTemplate;
private static final Logger logger = LoggerFactory.getLogger(OrganizationRestTemplateClient.class);
public Organization getOrganization(String organizationId){
logger.debug("In Licensing Service.getOrganization: {}", UserContext.getCorrelationId());
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
"http://zuulserver:5555/api/organization/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}
JSON WEB TOKEN 与 OAuth2
OAuth2是一个令牌框架,但是它并没有为如何定义规范中的令牌提供任何标准。
为了矫正这个缺陷, JWT 是因特网工程任务组(IETF)提出的开放标准,为OAuth2提供令牌标准。
JWT特点:
- 小巧:令牌编码Base64,可以通过URL,HTTP首部或HTTP POST参数轻松传递。
- 密码签名:令牌由颁发它的验证服务器签名。意味着可以保证令牌没有被篡改。
- 自包含:由于令牌是密码签名的,接收该服务的微服务可以保证令牌内容是有效地,因此,不需要调用验证服务来确认令牌内容,因为令牌的签名可以被接收微服务确认,并且内容(如令牌和用户信息的过期时间)可以被接收微服务检查
- 可扩展:当验证服务生成一个令牌时,他可以在令牌被密封之前在令牌中放置额外的信息。接收服务可以解密令牌净荷,并从它里面检索额外的上下文。
修改验证服务以颁发JWT令牌
添加JWT依赖
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency>
告诉验证服务如何生成和翻译JWT令牌
@Configuration public class JWTTokenStoreConfig { //JWT @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } //JWT @Bean @Primary //如果有多个特定类型的bean,那么久使用被@Primary标注的bean类型进行自动注入 public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; } //JWT @Bean //在JWT和OAuth2服务器之间充当翻译 public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); //定义将用于签署令牌的签名秘钥 converter.setSigningKey("345345fsdfsf5345"); return converter; } }
本例中将使用一个对称秘钥,意味着验证服务和受验证服务保护的服务必须要在所有服务之间共享相同的秘钥。
Spring Cloud Security 支持对称秘钥加密和使用公钥/私钥的不对称加密。
JWTTokenStoreConfig定义了如何创建和签名JWT令牌。
JWTOAuth2Config类定义OAuth2服务的配置。
@Configuration public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private TokenStore tokenStore; @Autowired private DefaultTokenServices tokenServices; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private TokenEnhancer jwtTokenEnhancer; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter)); endpoints.tokenStore(tokenStore) //JWT 定义的令牌存储将在这里注入 .accessTokenConverter(jwtAccessTokenConverter) //JWT 这是钩子,高速Spring Security OAuth2 代码使用JWT .tokenEnhancer(tokenEnhancerChain) //JWT .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("eagleeye") .secret("thisissecret") .authorizedGrantTypes("refresh_token", "password", "client_credentials") .scopes("webclient", "mobileclient"); } }
现在重启服务将会生成一个JWT构建的令牌。令牌并不是直接作为JSON返回,而是使用了BASE64进行了编码。
JWT允许开发人员扩展令牌,并想令牌添加额外信息,不要暴露敏感信息或个人身份信息