前言1. OAuth2 基础知识
1.1 安全性的 4 个组成部分1.2 OAuth2 的工作原理1.3 OAuth2 规范的 4 种类型的授权1.4 OAuth2 的优势1.5 OAuth2 核心原理1.6 JSON Web Token 2. 建立 OAuth2 服务器
2.1 引入 pom.xml 依赖文件2.2 主程序类上添加注解2.3 添加受保护对象的端点2.4 定义哪些应用程序可以使用服务
2.4.1 使用 JDBC 存储2.4.2 使用内存储存 2.5 为应用程序定义用户 ID、密码和角色2.6 通过发送 POST 请求验证用户 3. 使用 OAuth2 建立并保护服务资源
3.1 引入 pom.xml 依赖文件3.2 添加 bootstrap.yml 配置文件3.3 在主程序类上添加注解3.4 定义访问控制规则
3.4.1 通过验证用户保护服务3.4.2 通过特定角色保护服务 4. 在上下游服务中传播 OAuth2 访问令牌
4.1 配置服务网关的黑名单4.2 修改上游服务业务代码
4.2.1 下游服务4.2.2 在上游服务中公开 OAuth2RestTemplate 类4.2.3 在上游服务中用 OAuth2RestTemplate 来传播 OAuth2 访问令牌 最后
前言
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
OAuth2 是一个基于令牌的安全验证和授权框架。他允许用户使用第三方验证服务进行验证。 如果用户成功进行了验证, 则会出示一个令牌,该令牌必须与每个请求一起发送。然后,验证服务可以对令牌进行确认;
1. OAuth2 基础知识 1.1 安全性的 4 个组成部分
受保护资源:Resource Server,开发人员想要保护的资源(如一个微服务),需要确保只有已通过验证并且具有适当授权的用户才能访问它;资源所有者:Resource Owner,资源所有者定义哪些应用程序可以调用其服务,哪些用户可以访问该服务,以及他们可以使用该服务完成哪些事情。 资源所有者注册的每个应用程序都将获得一个应用程序名称,该应用程序名称与应用程序密钥一起标识应用程序。 应用程序名称和密钥的组合是在验证 OAuth2 令牌时传递的凭据的一部分;应用程序:Client,这是代表用户调用服务的应用程序。毕竟,用户很少直接调用服务 。相反,他们依赖应用程序为他们工作。OAuth2 验证服务器:Authorization Server,OAuth2 验证服务器是应用程序和正在使用的服务之间的中间人。 OAuth2 验证服务器允许用户对自己进行验证,而不必将用户凭据传递给由应用程序代表用户调用的每个服务; 1.2 OAuth2 的工作原理
第三方客户端向资源所有者(用户)申请认证请求;【关键】用户同意请求,返回一个许可;客户端根据许可向认证服务器申请认证令牌 Token;客户端根据认证令牌向资源服务器申请相关资源;
1.3 OAuth2 规范的 4 种类型的授权密码( password ) ;客户端凭据( client credential ) ;;授权码( authorization code) ;隐式( imp licit ); 1.4 OAuth2 的优势
允许开发人员轻松与第三方云服务提供商集成,并使用这些服务进行用户验证和授权,而无须不断地将用户的凭据传递给第三方服务; 1.5 OAuth2 核心原理
先有一个 OAuth2 认证服务器,用来创建和管理 OAuth2 访问令牌;接着在受保护资源主程序类上添加一个注解:@EnableResourceServer,该注解会强制执行一个过滤器,该过滤器会拦截对服务的所有传入调用,检查传入调用的 HTTP 首部中是否存在 OAuth2 访问令牌,然后调用 security.oauth2.resource.userInfoUri 中定义的回调 URL 告诉客户端与 OAuth2 认证服务器交互,查看令牌是否有效;一旦获悉令牌是有效的,@EnableResourceServer 注解也会应用任何访问控制规则,以控制什么人可以访问服务; 1.6 JSON Web Token
考虑到篇幅有限,JWT 相关令牌存储将在《微服务架构 | 7.2 构建使用 JWT 令牌存储的 OAuth2 安全认证》中讲解;
2. 建立 OAuth2 服务器
验证服务将验证用户凭据并颁发令牌;每当用户尝试访问由,如正服务保护的服务时,验证服务将确认 OAuth2 令牌是否已由其颁发并且尚未过期; 2.1 引入 pom.xml 依赖文件
2.2 主程序类上添加注解org.springframework.cloud spring-cloud-security org.springframework.cloud spring-cloud-starter-oauth2
@EnableAuthorizationServer:该服务将作为 OAuth2 服务;@EnableResourceServer:表示该服务是受保护资源;(该注解在 3.3 详解) 2.3 添加受保护对象的端点
在 controller 包下;
该端点将映射到 /auth/user 端点,当受保护的服务调用 /auth/user 时,将会确认 OAuth2 访问令牌,并检索发文手背欧虎服务所分配的角色;
@RequestMapping(value = { "/user" }, produces = "application/json")
public Map user(OAuth2Authentication user) {
Map userInfo = new HashMap<>();
userInfo.put("user", user.getUserAuthentication().getPrincipal());
userInfo.put("authorities", AuthorityUtils.authorityListToSet(user.getUserAuthentication().getAuthorities()));
return userInfo;
}
2.4 定义哪些应用程序可以使用服务
在 config 包下;
ClientDetailsServiceConfigurer 支持两种类型的储存:内存存储和JDBC存储,如下分点所示: 2.4.1 使用 JDBC 存储
OAuth2Config 类:
@Configuration
//继承 AuthorizationServerConfigurerAdapter 类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
//定义哪些客户端将注册到服务
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//JDBC存储:
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT); //设置我们的自定义的sql查找语句
clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT); //设置我们的自定义的sql查找语句
clients.withClientDetails(clientDetailsService); //从 jdbc 查出数据来存储
}
@Override
//使用 Spring 提供的默认验证管理器和用户详细信息服务
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
SecurityConstants 类:里面存放上述提到的 SQL 查询语句;
public interface SecurityConstants {
String CLIENT_FIELDS = "client_id, client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
String base_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
String DEFAULT_FIND_STATEMENT = base_FIND_STATEMENT + " order by client_id";
String DEFAULT_SELECT_STATEMENT = base_FIND_STATEMENT + " where client_id = ?";
}
2.4.2 使用内存储存
@Configuration
//继承 AuthorizationServerConfigurerAdapter 类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private 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"); //获取访问令牌时可以操作的范围
}
@Override
//使用 Spring 提供的默认验证管理器和用户详细信息服务
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
2.5 为应用程序定义用户 ID、密码和角色
在 config 包下:
可以从内存数据存储、支持 JDBC 的关系数据库或 LDAP 服务器中存储和检索用户信息;
@Configuration
@EnableWebSecurity
//扩展核心 Spring Security 的 WebSecurityConfigurerAdapter
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
//用来处理验证
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//处理返回用户信息
@Override
@Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
//configure() 方法定义用户、密码与角色
@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");
}
}
上述例子中 john.carnell 用户拥有 USER 用户;william.woodward 拥有 ADMIN 用户; 2.6 通过发送 POST 请求验证用户
发送:POST http://localhost:8901/auth/oauth/token;并在 POST 的请求体里带上应用程序名称、密钥、用户 ID 和密码,可以模拟用户获取 OAuth2 令牌;
3. 使用 OAuth2 建立并保护服务资源
创建和管理 OAuth2 访问令牌是 OAuth2 服务器的职责;定义哪些用户角色有权执行哪些操作在单个服务级别上的; 3.1 引入 pom.xml 依赖文件
3.2 添加 bootstrap.yml 配置文件org.springframework.cloud spring-cloud-security org.springframework.security.oauth spring-security-oauth2
security:
oauth2:
resource:
userInfoUri: http://localhost:8901/auth/user
这里添加回调 URL,客户端访问受保护服务时,受保护服务将调用 /auth/user 端点,向 OAuth2 服务器检查访问令牌是否生效; 3.3 在主程序类上添加注解
@EnableResourceServer:表示该服务是受保护资源;该注解会强制执行一个过滤器,该过滤器会拦截对服务的所有传入调用,检查传入调用的 HTTP 首部中是否存在 OAuth2 访问令牌,然后调用 security.oauth2.resource.userInfoUri 中定义的回调 URL 来查看令牌是否有效;一旦获悉令牌是有效的,@EnableResourceServer 注解也会应用任何访问控制规则,以控制什么人可以访问服务;
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //断路器
@EnableResourceServer //表示受保护资源
public class Application {
//注入一个过滤器,会拦截对服务的所有传入调用
@Bean
public Filter userContextFilter() {
UserContextFilter userContextFilter = new UserContextFilter();
return userContextFilter;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.4 定义访问控制规则
在 config 包或 security 包下;
要定义访问控制规则,需要扩展 ResourceServerConfigurerAdapter 类井覆盖 configure() 方法;有多种定义方法,这里给出常见的两种定义示例: 3.4.1 通过验证用户保护服务
即:只由已通过身份验证的用户访问;
//必须使用该注解,且需要扩展 ResourceServerConfigurerAdapter 类
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
//访问规则在 configure() 方法中定义,并且通过传入方法的 HttpSecurity 对象配置
@Override
public void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().anyRequest().authenticated();
}
}
anyRequest().authenticated() 表示需要由已通过验证的用户访问; 3.4.2 通过特定角色保护服务
限制只有 ADMIN 用户才能调用该服务的 DELETE 方法;
@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception{
http
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/v1/xxxservices/**") //运行部开发人员限制对受保护的 URL 和 HTTP DELETE 动词的调用
.hasRole("ADMIN") //允许访问的角色列表
.anyRequest()
.authenticated();
}
}
anyRequest().authenticated() 表示仍需要由已通过验证的用户访问;结合本篇《2.5 为应用程序定义用户 ID、密码和角色》的示例,这里使用 john.carnell USER 用户访问资源将被拒绝,而使用 william.woodward ADMIN 用户访问资源将被通过;
4. 在上下游服务中传播 OAuth2 访问令牌
用户已经向 OAuth2 服务器进行了验证,调用 EagleEye Web 客户端;EagleEye Web 应用程序( OAuth2 服务器)将通过 HTTP 首都 Authorization 添加 OAuth2 访问令牌;Zuul 将查找许可证服务端点,然后将调用转发到其中一个许可证服务的服务器;服务网关需要从传入的调用中复制 HTTP 首部 Authorization;受保护服务使用 OAuth2 服务器确认令牌; 4.1 配置服务网关的黑名单
在 Zuul 的 application.yml 的配置文件里;
因为在整个验证流程中,我们需要将 HTTP 首部 Authorization 传递上下游进行权限认证;
但在默认情况下,Zuul 不会将敏感的 HTTP 首部(如 cookie、Set-Cokkie 和 Authorization)转发到下游服务;
需要配置 Zuul 的黑名单放行 Authorization;
zuul: sensitiveHeaders: cookie , Set-cookie
上述配置表示拦截 cookie , Set-cookie 传递下游,而 Authorization 会放行;
4.2 修改上游服务业务代码业务代码需要保证将 HTTP 首部 Authorization 注入服务的上下游; 4.2.1 下游服务
这里的下游服务就是受保护的服务;其构建方法同本篇的《3. 使用 OAuth2 建立并保护服务资源》 4.2.2 在上游服务中公开 OAuth2RestTemplate 类
可以在主程序类上,也可以在主程序所在包及其子包里创建类;
使该类可以被自动装配到调用另一个受 OAuth2 保护的服务;
@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext, OAuth2ProtectedResourceDetails details) {
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
4.2.3 在上游服务中用 OAuth2RestTemplate 来传播 OAuth2 访问令牌
自动装配 OAuth2RestTemplate;
@Component
public class OrganizationRestTemplateClient {
//OAuth2RestTemplate 是标准的 RestTemplate 的增强式替代品,可处理 OAuth2 访问令牌
@Autowired
OAuth2RestTemplate restTemplate;
public Organization getOrganization(String organizationId){
//调用组织服务的方式与标准的 RestTemplate 完全相同
ResponseEntity restExchange =
restTemplate.exchange(
"http://zuulserver:5555/api/organization/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}
最后



