Spring Cloud Security与OAuth2
Spring Cloud Security
分布式系统的安全控制
Spring Security安全控制原理:Filter
启动的时候创建一个过滤器的代理,代理将请求转化到Spring定义的一系列Filter,最终实现针对Web层安全的控制。
Spring Cloud能帮助我们解决哪些问题

OAuth2
是一个标准,与编程语言无关;本质也是一个微服务;
Open Authentication
一个基于令牌的安全框架,允许用户使用第三方验证服务进行验证
4个组成部分
A protected resource,受保护资源
A resource owner,资源所有者
An application,应用程序
OAuth2 authentication server,OAuth2验证服务器(认证服务器)

认证与访问流程
- 用户向认证服务器请求登录
- 认证返回token给用户
- 用户携带token访问受保护资源
- 受保护资源拿着token向OAuth2验证合法性
- 返回用户所需资源
分布式系统关系图

开发OAuth2认证服务
添加依赖
1 2 3 4 5 6 7 8 9
| <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>
|
启动类加注解
创建配置类
OAuth2Config-哪些应用程序(Client)、凭据?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Configuration 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 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } }
|
个人用户凭据、所属角色定义
- 支持的存储:内存、JDBC、==LDAP服务器==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration 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(); }
@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"); } }
|
authentication vs. authorization
测试
以下URL都是自动实现的
注意/auth
是因为在配置文件中加上了:
1 2
| server: contextPath: /auth
|
验证用户
POST
:http://localhost:8901/auth/oauth/token
提供信息
(*
为必须)
Auth中(Basic Auth类型)(即头部Authorization)
body中(form-data类型)
- username
*
- password
*
- grant_type 授权类型
- scope 使用范围
返回json
如:
访问自定义端点:/auth/user(这里是定义在启动类中)
/auth
为配置文件的上下文
1 2 3 4 5 6 7
| @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; }
|
GET
:http://localhost:8901/auth/user
提供信息
携带上一步的token,并在Authorization中选择OAuth2.0
类型,Access Token选择Available Tokens
开发受保护资源(服务)1(licensing)
添加依赖
1 2 3 4 5 6 7 8
| <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>
|
配置security.oauth2.resource.userInfoUri,指向到认证服务的端点
这里是通过在run.sh中通过-Dsecurity.oauth2.resource.userInfoUri=$AUTHSERVER_URI
引入的
AUTHSERVER_URI
在docker-compose中,值为:http://authenticationservice:8901/auth/user
启动类加注解
@EnableResourceServer
,指定当前服务是一个受保护的资源,强制执行一个过滤器
权限控制
在业务服务中开发而不是OAuth2,是为了解耦
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{ @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/v1/organizations/**") .hasRole("ADMIN") .anyRequest() .authenticated(); } }
|
传递Authorization(OAuth2访问令牌)
网关(Zuul)的传递
在Zuul的配置文件(在confsvr中)配置:
1 2 3
|
zuul.sensitiveHeaders: Cookie,Set-Cookie
|
配置完成后,zuulserver在调用与访问服务时都会自动带上token了
licensing的传递
在调用organization-service的地方做出一些修改,使用OAuth2RestTemplate,调用远程服务(通过Zuul)
此后在licensing发送请求时就会自动带上token了
JWT
解决重复向OAuth2认证的问题
JSON Web Token,是IETF提出的开发标准,旨在为OAuth2令牌提供标准结构
优点
- JWT令牌编码为Base64,容易传递
- 认证服务器作了签名(不会被伪造)
- 自包含信息,不需要调用认证服务
- 可扩展
使用
添加依赖
认证服务和受保护资源的服务都要加
1 2 3 4
| <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency>
|
创建Bean(如何创建和签名JWT令牌)
authentication服务、licensing服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Configuration public class JWTTokenStoreConfig {
@Autowired private ServiceConfig serviceConfig;
@Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); }
@Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; }
@Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(serviceConfig.getJwtSigningKey()); return converter; }
}
|
authentication在其中多加一个可扩展接口的Bean(可选)
1 2 3 4
| @Bean public TokenEnhancer jwtTokenEnhancer() { return new JWTTokenEnhancer(); }
|
OAuth2认证服务:配置到OAuth2服务中
authentication服务中添加配置类:
主要是为了将一些bean注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @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) .accessTokenConverter(jwtAccessTokenConverter) .tokenEnhancer(tokenEnhancerChain) .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"); } }
|
可扩展接口(可选)
依赖:
1 2 3 4 5
| <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
|
authentication中创建类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class JWTTokenEnhancer implements TokenEnhancer { @Autowired private OrgUserRepository orgUserRepo;
private String getOrgId(String userName){ UserOrganization orgUser = orgUserRepo.findByUserName( userName ); return orgUser.getOrganizationId(); }
@Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); String orgId = getOrgId(authentication.getName()); additionalInfo.put("organizationId", orgId);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
|
可扩展接口的解析
zuul的前置过滤器中添加函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private String getOrganizationId(){ String result=""; if (filterUtils.getAuthToken()!=null){
String authToken = filterUtils.getAuthToken().replace("Bearer ",""); try { Claims claims = Jwts.parser() .setSigningKey(serviceConfig.getJwtSigningKey().getBytes("UTF-8")) .parseClaimsJws(authToken).getBody(); result = (String) claims.get("organizationId"); } catch (Exception e){ e.printStackTrace(); } } return result; }
|
与之前相比?
- 受保护服务中(licensing)不用再配置security.oauth2.resource.userInfoUri
- 认证服务、licensing、organization服务配置服务中都要加上signing.key,且保持一致
在线工具
jwt编解码工具:jwt.io