zoukankan      html  css  js  c++  java
  • 网关安全(四)-重构代码以适应真实环境

    1、使用scope来实现权限控制

      认证服务器,可以根据不同的应用发出不同scope的令牌,同样,资源服务器也可以通过scope来控制访问权限。

      场景:对于订单服务器,GET请求要求令牌的scope必须有read,POST请求必须有write。

      1.1、订单服务提供两个请求一个POST,一个GET

    /**
     * 订单控制层
     *
     * @author caofanqi
     * @date 2020/1/31 14:26
     */
    @Slf4j
    @RestController
    @RequestMapping("/orders")
    public class OrderController {
    
        private RestTemplate restTemplate = new RestTemplate();
    
        @PostMapping
        public OrderDTO create(@RequestBody OrderDTO orderDTO, @AuthenticationPrincipal String username) {
            log.info("username is :{}",username);
            PriceDTO price = restTemplate.getForObject("http://127.0.0.1:9070/prices/" + orderDTO.getProductId(), PriceDTO.class);
            log.info("price is : {}", price.getPrice());
            return orderDTO;
        }
    
    
        @GetMapping("/{productId}")
        public OrderDTO get(@PathVariable Long productId) {
            OrderDTO orderDTO = new OrderDTO();
            orderDTO.setProductId(productId);
            return orderDTO;
        }
    
    }

      1.2、资源服务器配置类重写public void configure(HttpSecurity http)方法。

    /**
     * 资源服务器配置
     *
     * @author caofanqi
     * @date 2020/2/1 20:10
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            //该资源服务器id
            resources.resourceId("order-server");
        }
    
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers(HttpMethod.GET).access("#oauth2.hasScope('read')")
                    .antMatchers(HttpMethod.POST).access("#oauth2.hasScope('write')")
                    .anyRequest().authenticated();
        }
    
    }

      1.3、测试访问权限

        1.3.1、申请scope为read,write的令牌,既可以访问POST请求,又可以访问GET请求。

        

        1.3.2、申请scope为read的令牌,只能访问GET请求,POST请求没有权限

        

        1.3.3、同样,申请scope为write的令牌,只能访问POST请求,GET请求没有权限(图略)。

    2、通过@AuthenticationPrincipal获取用户信息

      在前面我们通过@AuthenticationPrincipal可以直接获得用户的username,现在我们想要获得整个user对象。

      2.1、WebSecurityConfig配置类中,为ResourceServerTokenServices添加token转换器

        /**
         *  远程校验令牌相关配置
         */
        @Bean
        public ResourceServerTokenServices tokenServices(){
            RemoteTokenServices tokenServices = new RemoteTokenServices();
            tokenServices.setClientId("orderService");
            tokenServices.setClientSecret("123456");
            tokenServices.setCheckTokenEndpointUrl("http://127.0.0.1:9020/oauth/check_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;
        }

      2.2、实现UserDetailsService接口

    /**
     * 查找用户信息
     *
     * @author caofanqi
     * @date 2020/2/2 0:34
     */
    @Component
    public class UserDetailsServiceImpl implements UserDetailsService {
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserDO userDO = new UserDO();
            userDO.setId(1234L);
            userDO.setUsername(username);
            userDO.setPassword(new BCryptPasswordEncoder().encode("123456"));
            return userDO;
        }
    }

      2.3、@AuthenticationPrincipal后直接跟loadUserByUsername方法返回的对象即可

        @PostMapping
        public OrderDTO create(@RequestBody OrderDTO orderDTO, @AuthenticationPrincipal UserDO userDO) {
            log.info("userId is :{}", userDO.getId());
            PriceDTO price = restTemplate.getForObject("http://127.0.0.1:9070/prices/" + orderDTO.getProductId(), PriceDTO.class);
            log.info("price is : {}", price.getPrice());
            return orderDTO;
        }

      2.4、测试,控制台打印结果如下

      2.5、@AuthenticationPrincipal支持Spel表达式

        @PostMapping
        public OrderDTO create(@RequestBody OrderDTO orderDTO, @AuthenticationPrincipal(expression = "#this.id") Long userId) {
            log.info("userId is :{}",userId);
            PriceDTO price = restTemplate.getForObject("http://127.0.0.1:9070/prices/" + orderDTO.getProductId(), PriceDTO.class);
            log.info("price is : {}", price.getPrice());
            return orderDTO;
        }

    3、将认证服务器中的客户端信息与token存放到数据库中

      前面我们在认证服务器配置的客户端信息是直接写在代码里的,存在内存中,这样新增或删除一个客户端应用,都要改代码,重启认证服务器。token信息也都是存在内存里的,重启认证服务器后,token就没了。而且如果认证服务器是集群的话,发令牌的是A机器,验令牌的可能是B机器,这样也是不行的,需要将token持久化到数据库或者redis。我们选择将数据存放到MySQL数据库。

      3.1、创建认证服务器需要的表,其中oauth_client_details存放客户端应用信息,oauth_access_token存放发放的令牌信息

    create table oauth_client_details (
      client_id VARCHAR(256) PRIMARY KEY,
      resource_ids VARCHAR(256),
      client_secret VARCHAR(256),
      scope VARCHAR(256),
      authorized_grant_types VARCHAR(256),
      web_server_redirect_uri VARCHAR(256),
      authorities VARCHAR(256),
      access_token_validity INTEGER,
      refresh_token_validity INTEGER,
      additional_information VARCHAR(4096),
      autoapprove VARCHAR(256)
    );
    
    create table oauth_client_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication_id VARCHAR(256) PRIMARY KEY,
      user_name VARCHAR(256),
      client_id VARCHAR(256)
    );
    
    create table oauth_access_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication_id VARCHAR(256) PRIMARY KEY,
      user_name VARCHAR(256),
      client_id VARCHAR(256),
      authentication BLOB,
      refresh_token VARCHAR(256)
    );
    
    create table oauth_refresh_token (
      token_id VARCHAR(256),
      token BLOB,
      authentication BLOB
    );
    
    create table oauth_code (
      code VARCHAR(256), authentication BLOB
    );
    
    create table oauth_approvals (
        userId VARCHAR(256),
        clientId VARCHAR(256),
        scope VARCHAR(256),
        status VARCHAR(10),
        expiresAt DATETIME,
        lastModifiedAt DATETIME
    );

      3.2、将客户端相关信息存放到oauth_client_details表中

      3.3、pom中添加数据库连接等依赖,application.yml中添加数据库连接信息

      3.4、修改认证服务器配置类

    /**
     * OAuth2认证服务器配置类
     * 需要继承AuthorizationServerConfigurerAdapter类,覆盖里面三个configure方法
     * 并添加@EnableAuthorizationServer注解,指定当前应用做为认证服务器
     *
     * @author caofanqi
     * @date 2020/1/31 18:04
     */
    @Configuration
    @EnableAuthorizationServer
    public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    
    
        @Resource
        private AuthenticationManager authenticationManager;
    
        @Resource
        private DataSource dataSource;
    
        /**
         * 配置授权服务器的安全性
         * checkTokenAccess:验证令牌需要什么条件,isAuthenticated():需要经过身份认证。
         * 此处的passwordEncoders是为client secrets配置的。
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.checkTokenAccess("isAuthenticated()").passwordEncoder(new BCryptPasswordEncoder());
        }
    
    
        /**
         * 配置客户端服务
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //从数据库中读取
            clients.jdbc(dataSource);
        }
    
        /**
         * 配置授权服务器终端的非安全特征
         * authenticationManager 校验用户信息是否合法
         * tokenStore:token存储
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager).tokenStore(new JdbcTokenStore(dataSource));
        }
    
    }

      3.5、启动项目,请求令牌,可以发现oauth_access_token中多了一条记录

       使用该令牌去访问资源服务器,可以正常访问,重启 认证服务器依然生效。

    项目源码:https://github.com/caofanqi/study-security/tree/dev-Refactoring

  • 相关阅读:
    SQL Server事务执行一半出错是否自动回滚整个事务 【转】
    html5 canvas做的俄罗斯方块
    laravel-admin 模型创建、数据迁移、以及关联模型控制器
    laravel-admin 安装
    Composer 安装时要求输入授权用户名密码?
    查找mysql中未提交的事务
    SSH 登录时出现如下错误:No supported key exchange algorithms
    MySQL运行一段时间后自动停止问题的排查
    浅谈PHP中的数组和JS中的数组
    MySQL中使用group_concat()函数数据被截取(有默认长度限制),谨慎!
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12251591.html
Copyright © 2011-2022 走看看