【问题标题】:SpringBoot authentication DataSourceSpring Boot 身份验证数据源
【发布时间】:2015-08-09 18:56:13
【问题描述】:

我在我的应用程序中使用 SpringBoot、Spring Security、C3P0 和 JPA hibernate。

我如何告诉 SpringBoot 尝试使用与 .properties 文件中提供的数据源不同的数据源对用户进行身份验证。

编辑 1

public class AuthFilter extends AbstractAuthenticationProcessingFilter {
    private boolean postOnly = true;

    public AuthFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: "
                            + request.getMethod());
        }

        String email = request.getParameter("username");
        String password = request.getParameter("password");

        String username = email.substring(0, email.indexOf("@"));
        String db = email.substring(email.indexOf("@") + 1, email.lastIndexOf("."));

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Here I should change the DataSource before the authentication occurs.
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
}

【问题讨论】:

  • 如果您想在 Spring Security 中使用不同的数据源进行身份验证,您可能需要设置另一个数据源。
  • 我实际上是在他尝试登录时从用户名确定数据源名称,使用 AbstractAuthenticationProcessingFilter,但我找不到在身份验证发生之前调用 CurrentTenantIdentifierResolver 或 MultiTenantConnectionProvider 更改数据库的方法.
  • 贴一些代码,我根本不知道 Spring Boot,但是知道 Spring Security、Hibernate 和 C3P0。
  • 我编辑了我的帖子,db String 是应该使用的数据库的名称。
  • 是否有什么阻止您实施自己的自定义身份验证管理器,然后在那里实施您的登录?

标签: spring hibernate spring-security spring-boot c3p0


【解决方案1】:

OP 解决方案

我在我的AuthenticationProvider 中为客户创建了一个sessionFactory 和一个数据源,并手动搜索他而不是使用我的服务。它奏效了。

【讨论】:

    【解决方案2】:

    如果您需要使用不同的系统,您可以使用 ReST 模板在自定义 authenticationProvider 中调用 API。因此,您可以通过 ReST API 进行身份验证。像这样的东西,

    public static final String REST_SERVICE_URI = "<your microservice end 
      point>";
    Users authApiObj = new Users();
    authApiObj.username = "username";
    authApiObj.password = "username";
    
    RestTemplate restTemplate = new RestTemplate(); 
    HttpEntity<Object> request = new HttpEntity<Object>(authApiObj, headers);
    String responseString = restTemplate.postForObject(REST_SERVICE_URI, 
       request, String.class);
    

    否则您需要配置 spring cloud config server 并选择启动您的 spring boot 应用程序并从配置的 spring config server 访问应用程序中加载的动态属性

    【讨论】:

      【解决方案3】:

      我知道问题被问到了;但可能会帮助其他人寻找答案(答案可能有点长)。

      注意:以下逻辑不会在同一请求中切换到多个数据库源。 JPA 连接到application.properties 文件中指向的主数据源。下面的逻辑只是使用 JDBC TEMPLATE FOR ADDITIONAL DB Connection。

      SpringBoot 2.0.1 引入了AbstractRoutingDataSource,使用它可以动态地为每个请求使用适当的数据源。 datasource 的类型为 Map&lt;Object, Object&gt;

      本质上,数据源映射应该包含连接标识符作为键和数据源作为值。有两种填充地图的方法。

      第一种方式:这里 dataSourceMap 将仅在应用程序启动时填充。 (假设所有用户都存在于一个不同的单个数据库中(除了主数据库),这种方法应该足够了。)

      一个。创建一个在应用程序启动时加载的配置文件ClientDataSourceConfig.java,其中手动准备了dataSourceMap,并为JPA设置了默认的目标数据库源。

      ClientDataSourceConfig.java:

      import com.company.tripmis.datasource.ClientDataSourceRouter;
      
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class ClientDataSourceConfig {
      
          @Value("${spring.datasource.driver}")
          private String driver;
      
          @Value("${spring.datasource.url}")
          private String url;
      
          @Value("${spring.datasource.username}")
          private String username;
      
          @Value("${spring.datasource.password}")
          private String password;
      
          public ClientDataSourceRouter dataSource;
      
          @Bean(name = "getDataSource") // <-- IMP: Bean and Returning manually created ClientDataSourceRouter
          public ClientDataSourceRouter getDataSource() throws Exception {
              dataSource = new ClientDataSourceRouter();
      
              // Here we are initialising default DB from application.properties file manually.
      
              dataSource.init(driver, url, username, password);
              return dataSource;
          }
      
      }
      

      b.在ClientDataSourceRouter 中,通过连接到主数据库(对于多租户系统)或静态提及其他数据库并将其添加到dataSourceMap 并设置要使用的默认数据库(理想情况下是主数据库)来准备数据源映射。

      ClientDataSourceRouter.java:

      import static com.company.tripmis.utils.Helpers.prepareDataSourceConnectionObj;
      
      import java.util.HashMap;
      import java.util.List;
      
      import com.company.tripmis.model.entites.GlobalSetup;
      
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.jdbc.datasource.DriverManagerDataSource;
      import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
      
      public class ClientDataSourceRouter extends AbstractRoutingDataSource {
      
          private HashMap<Object, Object> dataSourceMap;
      
          public void init(String driver, String url, String username, String password) throws Exception {
              try {
                  DriverManagerDataSource defaultDataSource = new DriverManagerDataSource();
                  defaultDataSource.setDriverClassName(driver);
                  defaultDataSource.setUrl(url);
                  defaultDataSource.setUsername(username);
                  defaultDataSource.setPassword(password);
      
                  /**
                   * I am using below code for MultiTenantDB connection and hence reading and
                   * initialise multiple DBs but, for the above case, assuming User authentication
                   * DB is ONE seperate DB, you can replace the below JDBC Template part with User
                   * Authentication DB connection info and add it to this.dataSourceMap with key.
                   */
                  JdbcTemplate jdbcTemplate = new JdbcTemplate(defaultDataSource);
                  List<GlobalSetup> globalSetupList = jdbcTemplate
                          .query("SELECT `id`, `key`, `value`, `description` FROM global_setup;", (rs, rowNum) -> {
                              GlobalSetup globalSetup = new GlobalSetup();
                              globalSetup.setId(rs.getString(1));
                              globalSetup.setKey(rs.getString(2));
                              globalSetup.setValue(rs.getString(3));
                              globalSetup.setDescription(rs.getString(4));
                              return globalSetup;
                          });
      
                  this.dataSourceMap = new HashMap<>();
                  for (GlobalSetup globalSetup : globalSetupList) {
                      String key = globalSetup.getKey();
                      String configString = globalSetup.getValue();
                      DriverManagerDataSource dataSource = prepareDataSourceConnectionObj(configString);
                      this.dataSourceMap.put(key, dataSource);
                  }
                  // Comment till here and add single DB connection info to DBMap.
      
                  this.setTargetDataSources(dataSourceMap);
                  this.setDefaultTargetDataSource(defaultDataSource);
              } catch (Exception ex) {
                  throw ex;
              }
          }
      
          @Override
          protected Object determineCurrentLookupKey() {
              return ClientDataSourceContext.getClientDatabase();
          }
      
          public HashMap<Object, Object> getDataSources() {
              return this.dataSourceMap;
          }
      }
      

      第二种方式:为了动态更新dataSourceMap,创建一个过滤器,它将在每次请求时从数据库中读取并更新。 (应该使用 caching 相同,这样,DB 不会在每个请求上都被命中,并定期更新 CacheBucket。)

      现在,在应用程序启动时向 datasourceMap 添加了多个 DB 连接,我们需要做的就是在需要时使用JDBCTemplate 连接进行用户身份验证。

      这可以通过使用过滤器或 HTTPInterceptors 拦截 HttpRequest 来完成,也可以根据需要在Controller/Service 层完成。

      代码在Filter级别进行身份验证。

      import java.io.IOException;
      import java.util.HashMap;
      
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import javax.sql.DataSource;
      
      import com.company.tripmis.datasource.ClientDataSourceContext;
      import com.company.tripmis.datasource.ClientDataSourceRouter;
      import com.company.tripmis.model.pojo.ErrorModel;
      import com.company.tripmis.model.pojo.ResponseModel;
      import com.fasterxml.jackson.databind.ObjectMapper;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.ApplicationContext;
      import org.springframework.core.annotation.Order;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.stereotype.Component;
      
      @Component
      @Order(2)
      public class RequestDataSourceFilter implements Filter {
      
          @Autowired
          private ApplicationContext applicationContext;
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                  throws IOException, ServletException {
              HttpServletRequest httpRequest = (HttpServletRequest) request;
              String companyId = httpRequest.getRequestURI().split("/")[1];
      
              ClientDataSourceRouter clientDataSourceRouterObj = (ClientDataSourceRouter) applicationContext
                      .getBean("getDataSource");
              HashMap<Object, Object> dataSourcesMap = clientDataSourceRouterObj.getDataSources();
              // Some of the following code is for MultiTenant, if not needed, please ignore
              if (dataSourcesMap.get(companyId) == null) {
                  ResponseModel responseModel = new ResponseModel();
                  ErrorModel errorModel = new ErrorModel();
                  errorModel.setCode("DB_RES_NOT_FND");
                  errorModel.setMessage("The DB schema mapping not found for the given: " + companyId);
                  responseModel.setSuccess(false);
                  responseModel.setMessage("Requested DB resource not found");
                  responseModel.setError(errorModel);
                  ((HttpServletResponse) response).setHeader("Content-Type", "application/json");
                  ((HttpServletResponse) response).setStatus(400);
                  response.getOutputStream().write(new ObjectMapper().writeValueAsString(responseModel).getBytes());
                  return;
              } else {
                  String username = ""; // Read username from request
                  JdbcTemplate jdbcTemplate = new JdbcTemplate((DataSource) dataSourcesMap.get(companyId));
                  String sqlQuery = "select * from user where username = ?";
                  // Prepare the query
                  List<Model> userList = jdbcTemplate.query(sqlQuery, new Object[] { username },
                          new UserAuthMapper());
                  // Replace with proper query and create a custom mapper as required.
                  // And continue with the authentication here.
              }
              ClientDataSourceContext.setClientName(companyId);
              chain.doFilter(request, response);
          }
      }
      

      上面的userList(不是列表)会有用户的详细信息,可以用来进行认证。

      可以扩展上述逻辑以根据请求切换到不同的数据库(数据源)(最适合多租户系统,其中 baseUrl 定义了用于该请求生命周期的数据库)。

      @Bean(name = "getDataSource") 必须作为@DependsOn 添加到应用程序文件中,以便在应用程序启动期间运行 init 方法。

      希望上面的回答至少能帮助你提供一些基本的理解。

      【讨论】:

        【解决方案4】:
        猜你喜欢
        • 2018-02-27
        • 2017-04-12
        • 2019-02-13
        • 2018-10-09
        • 2019-04-22
        • 2022-10-05
        • 1970-01-01
        • 2017-07-30
        • 2020-06-09
        相关资源
        最近更新 更多