微服务安全面临的挑战
跨多个微服务的请求难以追踪
容器化部署导致的证书和访问控制问题
如何在微服务间共享用户的登录状态
多语言架构要求每个团队都有一定的安全经验
整体架构
Demo订单服务代码
OrderController:
/**
* @author aric
* @create 2021-04-06-9:20
* @fun
*/
@RestController
@RequestMapping("/orders")
@Slf4j
public class OrderController {
private RestTemplate restTemplate = new RestTemplate();
@PostMapping
public OrderInfo create(@RequestBody OrderInfo info){
PriceInfo price = restTemplate.getForObject("http://localhost:9060/prices/" + info.getProductId(), PriceInfo.class);
log.info("price is "+ price.getPrice());
return info;
}
}
OrderInfo:
@Data
public class OrderInfo {
private Long ProductId;
}
PriceInfo:
@Data
public class PriceInfo {
private Long id;
private BigDecimal price;
}
application.yml:
server:
port: 9080
Demo价格服务代码:
PriceController:
/**
* @author aric
* @create 2021-04-06-9:27
* @fun
*/
@RestController
@RequestMapping("/prices")
@Slf4j
public class PriceController {
@GetMapping("/{id}")
public PriceInfo getPrice(@PathVariable Long id){
log.info("productId is "+id);
PriceInfo priceInfo = new PriceInfo();
priceInfo.setId(id);
priceInfo.setPrice(new BigDecimal(100));
return priceInfo;
}
}
PriceInfo:
@Data
public class PriceInfo {
private Long id;
private BigDecimal price;
}
application.yml:
server:
port: 9060
OAuth2的角色和流程:
角色:用户-认证服务器-资源服务器
Demo认证服务器代码:
pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
auth认证模块:
/**
* @author aric
* @create 2021-04-06-10:54
* @fun 认证服务器配置 OAuth2AuthServerConfig
*/
@Configuration
@EnableAuthorizationServer //作为授权服务器存在注解
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private PasswordEncoder passwordEncoder;
//让认证服务器知道哪些应用会来找它要令牌
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//先注册到内存(不持久,重启消失),后面到数据库操作,详见最下面
clients.inMemory()
.withClient("orderApp")
.secret(passwordEncoder.encode("123456")) //应用的名字和密码
.scopes("read","write") //访问资源服务器 -> 做权限控制
.accessTokenValiditySeconds(3600) //令牌有效期
.resourceIds("order-server") //可以访问的资源服务器
.authorizedGrantTypes("password") //授权方式
.and()
.withClient("orderService")
.secret(passwordEncoder.encode("123456")) //应用的名字和密码
.scopes("read") //访问资源服务器 -> 做权限控制
.accessTokenValiditySeconds(3600) //令牌有效期
.resourceIds("order-server") //可以访问的资源服务器
.authorizedGrantTypes("password"); //授权方式
}
//让认证服务器知道哪些人会来访问
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints){
endpoints.authenticationManager(authenticationManager); //托管给authenticationManager做校验用户信息是否合法
}
//订单服务(谁)找认证服务器验证
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
security.checkTokenAccess("isAuthenticated()");
}
}
/**
* @author aric
* @create 2021-04-06-11:13
* @fun Web应用安全服务适配器 OAuth2WebSecurityConfig
*/
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService; //用来获取用户信息
//加密算法
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//构建AuthManagerBuilder
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
//暴露AuthManager成为Spring的Bean
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
/**
* @author aric
* @create 2021-04-06-11:28
* @fun 根据用户名获取用户详细信息 UserDetailsServiceImpl
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//返回用户名,密码,权限信息
return User.withUsername(username)
.password(passwordEncoder.encode("123456"))
.authorities("ROLE_ADMIN")
.build();
}
}
配置项:
server:
port: 9090
请求附带需认证资源信息:
返回信息:
Demo订单服务加token验证:
resource:
OAuth2ResourceServerConfig :
/**
* @author aric
* @create 2021-04-06-16:25
* @fun 配置资源服务器
*/
@Configuration
@EnableResourceServer //发往orderApi的所有请求都必须携带token
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
//资源服务器相关配置
@Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.resourceId("order-server"); //在server-auth中配置的orderApp的令牌只能访问order-server
}
//权限控制,哪些请求需要令牌,哪些不需要
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/haha").permitAll() //除了/haha请求不需要验证令牌,别的都需要
.antMatchers(HttpMethod.POST).access("#oauth2.hasScope('write')") //只有write Scope时才能调用POST请求
.antMatchers(HttpMethod.GET).access("#oauth2.hasScope('read')") //只有write Scope时才能调用GET请求
.anyRequest().authenticated();
}
}
OAuth2WebSecurityConfig:
/**
* @author aric
* @create 2021-04-06-17:44
* @fun 令牌怎么验证
*/
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public ResourceServerTokenServices tokenServices(){
//server-auth认的客户端
RemoteTokenServices tokenServices = new RemoteTokenServices(); //调用远程服务的token
tokenServices.setClientId("orderService");
tokenServices.setClientSecret("123456");
tokenServices.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token"); //校验token的地址
return tokenServices;
}
//验证token , 把authManager注册到Bean中
@Bean
@Override
public AuthenticationManager authenticationManagerBean(){
OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
authenticationManager.setTokenServices(tokenServices());
return authenticationManager;
}
}
OrderController:
/**
* @author aric
* @create 2021-04-06-9:20
* @fun
*/
@RestController
@RequestMapping("/orders")
@Slf4j
public class OrderController {
//带有token令牌的的处理逻辑
@PostMapping
public OrderInfo create(@RequestBody OrderInfo info, @AuthenticationPrincipal String username){
log.info("user is "+ username);
return info;
}
}
请求验证:
注意Oauth2请求头为Bearer,http为Basic
优化OrderController代码中用户信息获取方式:
OrderController:
/**
* @author aric
* @create 2021-04-06-9:20
* @fun
*/
@RestController
@RequestMapping("/orders")
@Slf4j
public class OrderController {
//带有token令牌的的处理逻辑
@PostMapping
public OrderInfo create2(@RequestBody OrderInfo info, @AuthenticationPrincipal User user){
log.info("user is "+ user.getUsername());
return info;
}
}
优化resource中User信息获取:
/**
* @author aric
* @create 2021-04-06-17:44
* @fun 令牌怎么验证
*/
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public ResourceServerTokenServices tokenServices(){
//server-auth认的客户端
RemoteTokenServices tokenServices = new RemoteTokenServices(); //调用远程服务的token
tokenServices.setClientId("orderService");
tokenServices.setClientSecret("123456");
tokenServices.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token"); //校验token的地址
tokenServices.setAccessTokenConverter(getAccessTokenConverter()); //把令牌转换成用户信息
return tokenServices;
}
private AccessTokenConverter getAccessTokenConverter() {
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userDetailsService);
accessTokenConverter.setUserTokenConverter(userTokenConverter);
return accessTokenConverter;
}
//验证token , 把authManager注册到Bean中
@Bean
@Override
public AuthenticationManager authenticationManagerBean(){
OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
authenticationManager.setTokenServices(tokenServices());
return authenticationManager;
}
}
/**
* @author aric
* @create 2021-04-06-18:47
* @fun
*/
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private static final long serialVersionUID = 1L;
//将字符串转换成相应的角色
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
public String getPassword() {
return null;
}
public String getUsername() {
return null;
}
public boolean isAccountNonExpired() {
return false;
}
public boolean isAccountNonLocked() {
return false;
}
public boolean isCredentialsNonExpired() {
return false;
}
public boolean isEnabled() {
return false;
}
}
/**
* @author aric
* @create 2021-04-06-18:47
* @fun
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = new User();
user.setUsername(username);
user.setId(1L);
return user;
}
}
优化server-auth中ServerConfig令牌存储数据库的补充:
主要表:
主要用access-token和client-details表
/**
* @author aric
* @create 2021-04-06-10:54
* @fun 认证服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DateSource dateSource;
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dateSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints){
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dateSource);
}
}