SpringCloud(二) 微服务安全实战 OAuth2

微服务安全面临的挑战

    跨多个微服务的请求难以追踪

    容器化部署导致的证书和访问控制问题

    如何在微服务间共享用户的登录状态

    多语言架构要求每个团队都有一定的安全经验

整体架构

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);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值