对于Web应用程序,我尝试实现多租户方法,为每个租户提供单独的数据库。安全数据可通过以下路径访问:
http://myurl.com:8080/myapp/tenantId/path/to/data
e.g. access the profile data of john which belongs to tenant 1:
http://myurl.com:8080/myapp/1/john
特定于租户的数据访问的实现非常简单(如果需要,我将在此处添加相应的代码)。但是,我不确定我是否以正确的方式保护数据。以下是我为身份验证所做的事情:我实现了一个过滤器,以使用租户ID增强用户名:
public class TenantUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String delimiter = ":";
private static final String tenantParameterIdentifier = "tenant";
@Override
protected String obtainUsername(HttpServletRequest request) {
String username = super.obtainUsername(request);
// The login data is equipped with a 'tenant' field in a user-defined login page
String tenant = request.getParameter(tenantParameterIdentifier);
return tenant + delimiter + username;
}
}
UserDetailsWithTenantService
使用此修改后的名称加载特定用户的详细信息:
@Override
public UserDetails loadUserByUsername(String tenantAndUsername) throws UsernameNotFoundException {
...
return new TenantUser(userName, account.getPassword(), tenantName, authorities);
...
}
授权是由另一个过滤器完成的,此时我很不确定我是否正在做正确的事情:
public class TenantAuthorizationFilter extends GenericFilterBean {
private static final String loginPath = "/j_spring_security_check";
private final static int tenantIndex = 1;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
actualDoFilter(req, res, chain);
chain.doFilter(req, res);
}
private void actualDoFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) return;
Object principal = authentication.getPrincipal();
if (principal.equals("anonymousUser")) return;
TenantUser userDetails = (TenantUser) principal;
String requestServletPath = request.getServletPath();
if (requestServletPath.equals(loginPath)) return;
Pattern pattern = Pattern.compile("/(\\w*).*");
Matcher m = pattern.matcher(requestServletPath);
m.find();
String requestedTenantName = m.group(tenantIndex);
if (requestedTenantName.isEmpty()) return;
String authenticatedTenantName = userDetails.getTenant();
if (!authenticatedTenantName.equals(requestedTenantName)) {
throw new AccessDeniedException("");
}
}
}
因此,基本上过滤器会检查经过身份验证的用户是否尝试访问不同于其授权的租户的数据。在这种情况下,会抛出相应的异常。
我的root-context.xml
:
<http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
<intercept-url pattern="/login*" access="permitAll" />
<intercept-url pattern="/**" access="isAuthenticated()" />
<custom-filter position="FORM_LOGIN_FILTER" ref="tenantAuthenticationFilter" />
<!-- not sure whether this position for an authorization filter is recommended -->
<custom-filter after="FILTER_SECURITY_INTERCEPTOR" ref="tenantAuthorizationFilter" />
<logout logout-success-url="/" />
</http>
答案 0 :(得分:0)
似乎方法很好。但是,我如何发送另一个租户代码给Gail访问他们的数据。
理想情况下,应该有一种具有授权标头的休息类型安全系统,或者让登录者识别租户,并根据登录的租户上下文验证参数。
我认为这会比现在更安全。请与我们分享您的理解。