务架构时,安全将会更加复杂。在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.1 搭建认证服务器首先,我们会搭建一个认证服务器(Auth Server)。该服务器也是一个标准的Spring Boot框架应用。
2.1.1 编写Maven文件4.0.0 auth-server twostepsfromjava.cloud twostepsfromjava-cloud-parent1.0.0-SNAPSHOT ../parent MS Blog Projects(Security): Auth Server org.springframework.boot spring-boot-starter-weborg.springframework.cloud spring-cloud-securityorg.springframework.security.oauth spring-security-oauth2org.springframework.boot spring-boot-maven-plugin
在该配置文件中我们需要引入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;@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;@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/i_access: 用户确认授权提交端点;
/oauth/error: 认证服务器错误信息获取端点;
/oauth/check_token: 用于资源服务访问的令牌解析端点;
/oauth/token_key: 如果使用JWT令牌,则公开用于令牌验证的公钥。
对于认证服务来说我们还需要提供一个用户信息加载端点,这样其它微服务就可以使用令牌从认证服务器获取认证用户的信息,从而能够实现用户认证及鉴权处理。具体实现代码如下:
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;@RestControllerpublic class AuthEndpoint { protected Logger logger = LoggerFactory.getLogger(AuthEndpoint.class); @RequestMapping(value = { "/auth/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;
}
}
作者:CD826
链接:https://www.jianshu.com/p/664c71b6681a



