Spring Cloud入门教程(十一):微服务安全(Security)
Spring Cloud入门教程系列:
- Spring Cloud入门教程(一):服务治理(Eureka)
- Spring Cloud入门教程(二):用户端负载均衡(Ribbon)
- Spring Cloud入门教程(三):公告式服务调用(Feign)
- Spring Cloud入门教程(四):微服务容错保护(Hystrix)
- Spring Cloud入门教程(五):API服务网关(Zuul) 上
- Spring Cloud入门教程(六):API服务网关(Zuul) 下
- Spring Cloud入门教程(七):分布式链路跟踪(Sleuth)
- Spring Cloud入门教程(八):统一配置中心(Config)
- Spring Cloud入门教程(九):基于消息驱动开发(Stream)
- Spring Cloud入门教程(十):消息总线(Bus)
本人和同事撰写的《Spring Cloud微服务架构开发实战》一书也在京东、当当等书店上架,大家可以点击这里前往购买,多谢大家支持和捧场!
Spring Cloud微服务架构开发实战.png
安全,几乎在任何应用开发中都是绕不过去的一个基础功能。当我们将应用转移到微服务架构时,安全将会更加复杂。在2016年David Borsos在伦敦的微服务大会上提出了以下四种方案:
单点登录(SSO): 每个微服务都需要和认证服务交互,但这将产生大量非常琐碎的网络流量和重复的工作,当动在应用中存在数十个或者更多微服务时,该方案的弊端就非常显著;
分布式会话(Session)方案: 该方案将客户认证的信息存储在共享存储中(如:Redis),并使用客户会话的ID作为key来实现的简单分布式哈希映射。当客户访问微服务时,可以通过会话的ID从从共享存储中获取客户认证信息。该方案在大部分时候非常不错,但其主要缺点在于共享存储需要肯定保护机制,此时相应的实现就会相对复杂;
用户端令牌(Token)方案: 令牌在用户端生成,并由认证服务器进行签名,令牌中包含足够的信息,以便各微服务可以使用。令牌会附加到每个请求上,为微服务提供客户身份验证。该处理方案的安全性相对较好,但因为令牌由用户端生成并保存,因而身份验证注销非常麻烦,一个折衷处理方案就是通过短期令牌和频繁检查认证服务来验证令牌能否有效等。对于用户端令牌JSON Web Tokens(JWT)是一个非常好的选择;
用户端令牌与API网关结合: 使用该方案意味着所有请求都通过网关,从而有效地隐藏了微服务。在请求时,网关将原始客户令牌转换为内部会话。这样也即可以网关对令牌进行注销,从而处理上一种方案存在的问题。
在本文中我们将着重详情基于令牌的处理方案,而基于令牌的处理方案最好的选择就是OAuth2.0。
1. OAuth2.0
关于OAuth2.0在维基百科中形容如下:
开放受权(OAuth)是一个开放标准,允许客户让第三方应用访问该客户在某一网站上存储的私密的资源(如照片,视频,联络人列表),而无需将客户名和密码提供给第三方应用。
OAuth允许客户提供一个令牌,而不是客户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌受权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让客户可以受权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。
OAuth 2.0是OAuth协议的下一版本,但不向下兼容OAuth 1.0。OAuth 2.0关注用户端开发者的简易性,同时为Web应用,桌面应用和手机,和起居室设施提供专门的认证流程。
对于让我们首先理解一下OAuth2.0中的几个关键术语:
- Resource Owner: 资源所有者,我们可以直接了解为:客户(User);
- User Agent: 客户代理商,对于Web应用可以直接了解为浏览器;
- Authorization server: 认证服务器,即提供客户认证和受权的服务器,可以是独立服务器;
- Resource server: 资源服务器,这里我们可以了解为需要保护的微服务。
而后,让我们看一下OAuth2.0的认证流程图(摘自RFC6749):
Security-OAuth2-010.png
认证流程步骤如下:
- (A)客户打开用户端以后,用户端请求客户给予受权;
- (B)客户同意受权给用户端;
- (C)用户端使用上一步取得的受权,向认证服务器申请令牌;
- (D)认证服务器对用户端进行认证以后,确认无误,同意发放令牌;
- (E)用户端使用令牌,向资源服务器申请获取资源;
- (F)资源服务器确认令牌无误,同意向用户端开放资源。
从流程上可以得知用户端必需在得到客户受权后才能够从认证服务中获取到令牌。OAuth2.0针对用户端受权提供了下面四种受权方式:
- 受权码模式(authorization code): 该种模式是功能最完整、流程最严密的受权模式;
- 简化模式(implicit): 该模式不需要通过第三方应用程序的服务器,跳过了”受权码”这个步骤,直接在浏览器中向认证服务器申请令牌,因而称为简化模式;
- 密码模式(password): 客户向用户端提供自己的客户名和密码,用户端通过这些信息直接向认证服务器获取受权;
- 用户端模式(client credentials): 指用户端以自己的名义,而不是以客户的名义向认证服务器获取认证,这种方式下认证服务器会将用户端作为一个客户来对待。
2. 实现微服务安全
下面让我们着手来实现示例项目的安全管控。
2.1 搭建认证服务器
首先,我们会搭建一个认证服务器(Auth Server)。该服务器也是一个标准的Spring Boot框架应用。
2.1.1 编写Maven文件
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>twostepsfromjava.cloud</groupId> <artifactId>twostepsfromjava-cloud-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>../parent</relativePath> </parent> <artifactId>auth-server</artifactId> <name>MS Blog Projects(Security): Auth Server</name> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>在该配置文件中我们需要引入spring-cloud-security和spring-security-oauth2依赖。
2.1.2 实现用户端管理
对于OAuth2.0应用来说需要实现一个用户端认证管理,这里我们直接继承AuthorizationServerConfigurerAdapter,并通过内存管理的方式添加了一个用户端应用,代码如下:
package io.twostepsfromjava.cloud.auth.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;/** * OAuth2Server 配置 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@Configurationpublic class OAuthConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("malldemo") .secret("pgDBd99tOX8d") .authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials") .scopes("webmall"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); }}用户端ID我们设置为malldemo,secret则设置为:pgDBd99tOX8d,同时我们还为用户端受权了authorization_code, refresh_token, implicit, password, client_credentials认证模式。并且在接下来的示例中我们会使用受权码模式和密码模式来进行测试。
2.1.3 实现客户认证和受权的管理
对于使用过Spring Security的同学来说对Security中客户认证和受权的管理应该不会陌生,这里也不再细讲,不熟习的同学可以自行搜索来理解。示例中代码如下:
package io.twostepsfromjava.cloud.auth.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import org.springframework.http.HttpMethod;import org.springframework.security.authentication.AuthenticationManager;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;/** * OAuth2 安全配置 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@Configuration@Order(org.springframework.boot.autoconfigure.security.SecurityProperties.ACCESS_OVERRIDE_ORDER)public class OAuthWebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception{ return super.authenticationManagerBean(); } @Override @Bean public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user001") .password("pwd001") .roles("USER") .and() .withUser("admin") .password("pwdAdmin") .roles("USER", "ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests() .anyRequest() .authenticated() .and() .httpBasic(); }}在上面的代码中,我们仍然通过内存方式进行管理,并创立了两个客户:
- user001: 是一个普通客户,只有
USER角色; - admin: 是一个管理员客户,拥有
USER和ADMIN角色。
在上面的代码中我们还指定了所有访问都需要认真,并且开启了httpBasic认证,通过这个当一个未认证客户访问时即可以通过浏览器弹出一个认证对话框,可以让客户输入客户名和密码进行认证。
2.1.4 实现应用引导类
对于我们所要实现的认证服务器来说最重要的就是需要在应用引导类中添加@EnableAuthorizationServer注解,通过该注解即可以启动Spring Cloud Security,并且为我们提供一系列端点,从而实现OAuth2.0的认证。这些端点分别为:
/oauth/authorize: 受权端点;/oauth/token: 获取访问令牌端点;/oauth/confirm_access: 客户确认受权提交端点;/oauth/error: 认证服务器错误信息获取端点;/oauth/check_token: 用于资源服务访问的令牌解析端点;/oauth/token_key: 假如使用JWT令牌,则公开用于令牌验证的公钥。
2.1.5 实现客户信息加载端点
对于认证服务来说我们还需要提供一个客户信息加载端点,这样其它微服务即可以使用令牌从认证服务器获取认证客户的信息,从而能够实现客户认证及鉴权解决。具体实现代码如下:
package io.twostepsfromjava.cloud.auth.api;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.oauth2.provider.OAuth2Authentication;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;/** * 获取当前认证客户的信息 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@RestControllerpublic class AuthEndpoint { protected Logger logger = LoggerFactory.getLogger(AuthEndpoint.class); @RequestMapping(value = { "/auth/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; }}该段代码将从Spring Security中获取到当前客户信息,并转化成一个Map对象返回。
2.1.6 编写配置文件
# 定义应用端口server.port=8290# 定义应用名称spring.application.name=authserverlogging.level.org.springframework=INFOlogging.level.io.twostepsfromjava=DEBUG2.2 完善客户微服务
接下来我们将对客户微服务进行修改,添加安全解决功能。
2.2.1 引入Security依赖
对于客户微服务来说是一个Resource server,也就是资源服务器,当访问到某些资源时需要进行客户认证及鉴权,因而需要引入对Spring Cloud Security的依赖:
<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>2.2.2 完善应用引导类修改
在Spring Cloud Security中我们只要要对需要进行安全管理的应用添加@EnableResourceServer注解来开启安全管控:
package io.twostepsfromjava.cloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;/** * TwoStepsFromJava Cloud -- User Service 服务器 * * @author CD826(CD826Dong@gmail.com) * @since 1.0.0 */@EnableDiscoveryClient@EnableResourceServer@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}当引入了spring-cloud-security并在应用引导类中添加了@EnableResourceServer注解时,应用就会开启默认的安全管控解决,假如你想在自己的应用中添加更细的安全管控,那么需要继承WebSecurityConfigurerAdapter并实现安全相关配置,这个在这里就不细讲了。
2.2.3 完善应用配置
我们需要在应用配置文件中添加获取认证客户信息的端点,具体配置如下:
# 保持原来的配置不变# OAuth2security.oauth2.resource.user-info-uri=http://localhost:8290/auth/user所配置的端点也就是之前我们在认证服务器所实现的获取当前登录客户信息的端点。
2.2.4 添加获取当前客户信息的端点
为了进行测试,我们需要在客户微服务中添加一个获取当前已登录客户信息的端点,代码如下:
@RequestMapping(value = "/my", method = RequestMethod.GET)public UserDto myDetail() { Map curUser = (Map) SecurityContextHolder.getContext() .getAuthentication() .getPrincipal(); String userName = (String)curUser.get("username"); return new UserDto(userName, userName, "/avatar/default.png", "");}这里的代码非常简单,就是直接从SecurityContextHolder中获取到当前已登录客户的信息,并构造成一个UserDto,而后返回。
Ok,到此客户微服务的改造就完成了。假如你现在启动客户微服务并进行访问上面所提供的端点,就会看到如下返回:
未认证访问客户微服务
也就是说,现在需要权限才可以访问该端点。那么我们如何提供权限呢?
之前在讲OAuth2.0的时候我们提到用户端受权模式有四种,那么我们来看看如何使用这些受权模式来实现具体的客户认证解决。
2.3 通过受权码模式(authorization code)访问
首先,让我们来看看如何通过OAuth2.0所提供的最全面的受权流程受权码模式来实现客户认证。
我们需要依次启动:服务治理服务器(Eureak Server)、认证服务器(Auth Server)和客户微服务。
第一步,我们首先来构造获取访问令牌的Url:
http://localhost:8290/oauth/authorize?response_type=code&client_id=malldemo&redirect_uri=http://localhost:8260&scope=webmall&state=63879对于该Url说明如下:
/oauth/authorize: 这个是获取受权码的端点;client_id: 用户端的ID,在请求的Url中必选包含。请注意一下我们这里给的值为:malldemo,这个就是我们在认证服务器中配置用户端列表是所配置的;response_type: 表示受权类型,也是必选的,对于受权码模式,这里固定填写为:code;scope: 表示申请的权限范围,可以不填。这个是指当有不同的用户端时所受权能否复用;redirect_uri: 受权成功后重定向到的URI;state: 表示用户端的当前状态,可以指定任意值,不管受权能否成功认证服务器都会原封不动地返回这个值。
我们,接下来可以直接在浏览器中请求这个地址,而后返回的页面如下:
客户登录截图
这个一个浏览器自身的客户登录窗口。由于,我们没有登录过,所以认证服务器通过httpBasic开启浏览器登录模式,这样当客户尚未认证时就会弹出如上的登录窗口。
我们在登录窗口中输入user001和pwd001也就是之前认证服务器中所配置的客户列表中的一个,而后就会跳转到如下页面:
客户受权截图
这里就是客户能否受权的界面。在该界面中我们可以看到所传入的用户端应用的ID和受权范围都会显示出来。这里我们点击【Approve】,而后浏览器就会跳转到redirect_uri所指定的地址,这时候仔细观察所跳转回来的地址,如下:
http://localhost:8260/?code=tn8n8F&state=63879可以发现在地址包含了两个参数:
code: 为认证服务所发放的受权码。该码的有效期很短,默认为10分钟,并且用户端只能使用该码一次,否则会被认证服务器拒绝。还需要说明一点就是该码与用户端ID及重定向URI,是逐个对应关系;state: 这个就是上面我们请求时所传入的参数,认证服务器会原封不动的返回来。
Ok,第一步我们已经获取到了受权码。那么第二步就是根据这个受权码来获取访问令牌,在获取访问令牌时我们使用Postman来进行,界面截图如下:
获取访问令牌
这该截图中我们需要填写以下参数:
/oauth/token: 这个是获取访问令牌的端点;grant_type: 表示使用的受权模式,必需填写,对于受权码模式值固定为”authorization_code”;code: 也就是上一步我们所取得的受权码;redirect_uri: 获取成功后重定向到的URI,同样我们需要设置为之前所给的地址;client_id: 用户端ID,必需填写,而且需要与之前保持一致。
此外,在上面的截图中我们还需要填写受权认证信息,这个由于我们认证服务器开启了权限验证,这里填写用户端的ID和secret就可。此时服务器就会把用户端作为一个客户来对待,从而能够访问/oauth/token端点。
P.S. 这里这么做主要时简化测试方法,使得我们上一步访问时可以弹出认证对话框。但实际生产使用时不应这么做,需要自己定义客户登录页面、受权页面。
上面的请求,我们可以取得如下返回:
获取到访问令牌
返回的内容如下:
access_token: 这个是所获取访问令牌;token_type: 令牌类型,Spring Cloud OAuth默认返回的值为bearer;expires_in: 表示令牌过期时间,单位为秒,默认为12小时;refresh_token: 升级令牌,过期后可以用来获取下一次的访问令牌;scope: 权限范围,一般与用户端申请范围一致。
有了访问令牌下一步我即可以访问客户微服务了,访问方式如下:
通过访问令牌访问客户微服务
访问时我们需要在Header中指定Authorization,并且将值设置为上一步所获取到的访问令牌就可,这样我们即可以得到正确的数据返回了,如上图所示。
这样我们就完成了通过受权码模式实现客户认证的测试。
2.4 通过密码模式(password)访问
假如认证服务器是第三方的,那么使用上面的流程问题不大。假如认证服务器是我们自己搭建的,比方该示例,那么上面的受权显有点复杂。接下来让我们看看如何使用密码模式来简化。
我们按照下图方式来直接请求认证服务器获取访问令牌:
通过密码获取客户受权-1
通过密码获取客户受权-2
在上图中我们需要同时设置用户端的ID、secret及访问客户的客户名和密码,并将grant_type设置为password,这样即可以直接获取到访问令牌,如下图所示:
通过密码获取访问令牌
而后,我们根据所获取到的访问令牌再次访问客户微服务,可以获取如下界面:
访问客户微服务
这说明,客户认证也是成功的。
可见,通过密码模式可以大大简化客户认证流程,但是需要客户信任用户端的情况下才会提供,因而这种方式适合用户端与认证服务器是同一个应用的情况下。
对于其它用户端受权模式这里就不再逐个进行测试了。本文中所提到的示例你可以在这里下载。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Spring Cloud入门教程(十一):微服务安全(Security)