我在我的应用程序中使用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;
}
}
答案 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)
需要编写 CustomDataSourceAuthnticationProvider 。
参考:
https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-mvc-boot
https://www.baeldung.com/spring-security-authentication-with-a-database