SpringBoot身份验证DataSource

时间:2015-05-27 15:22:26

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

我在我的应用程序中使用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;
    }
}

4 个答案:

答案 0 :(得分:0)

OP解决方案

  

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

答案 1 :(得分:0)

如果您需要使用不同的系统,可以使用ReST Template.So通过ReST API在自定义authenticationProvider中调用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配置服务器并选择启动Spring启动应用程序,并从配置的spring配置服务器访问应用程序中加载的动态属性

答案 2 :(得分:0)

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

注意:在相同的请求内,以下逻辑不会切换到多个数据库源。 JPA已连接到application.properties文件中指向的主数据源。下面的逻辑仅使用JDBC TEMPLATE进行其他数据库连接。

SpringBoot 2.0.1引入了AbstractRoutingDataSource,使用它可以针对每个请求动态使用适当的dataSource。 datasource的类型为Map<Object, Object>

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

第一种方式:此处dataSourceMap仅在应用程序启动时填充。 (假设所有用户都存在于不同单个数据库中(而不是主数据库),则此方法就足够了。)

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

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中,通过连接到主DB(对于多租户系统)或静态提及其他DB并将其添加到dataSourceMap中来准备数据源映射,并设置要使用的默认DB(理想情况下)主数据库)。

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,创建一个Filter,该Filter将在每次请求时从DB读取并更新它。 (应该使用caching来做到这一点,以便不会在每个请求上都命中DB,并定期更新CacheBucket。)

现在,在应用程序启动时,多个数据库连接已添加到datasourceMap中,我们要做的就是在需要的地方使用JDBCTemplate连接进行用户身份验证。

这可以通过使用Filters或HTTPInterceptor拦截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(不是列表)将具有用户详细信息,可用于执行身份验证。

可以扩展上述逻辑,以每个请求切换到不同的DB(数据源)(对于多租户系统来说,这是理想的,其中baseUrl定义了用于该请求生命周期的DB)。

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

希望以上答案至少可以帮助您获得一些基本了解。

答案 3 :(得分:0)