开源地图服务geoserver利用spring security进行用户验证和授权。
一、有关spring security。
SecurityContextHolder 负责存储安全上下文,保存用户信息,权限等等。
Authentication 认证信息类,身份信息、密码信息、细节、认证信息。
UserDetails 用户信息,包含用户的一些基本字段、密码、用户名等。
UserDetailsService 用户信息,是一个接口、一般可以重数据库中获取用户名、密码创建。
AuthenticationManager 认证管理类、通过实现该接口实现自定义认证。
AuthenticationProvider 认证提供者、是一个接口、一个真正的认证、一个是满足某种条件才认证。spring中提供了多种实现 类。
二、关于geoserver地图服务验证。
首先程序是从GeoServerApplication进入的,我们来看该类的方法init都做了哪些工作。从下面的代码中可以知道方法中创建了GeoServerSecurityManager。
/** Initialization override which sets up a locator for i18n resources. */
protected void init() {
// enable GeoServer custom resource locators
getResourceSettings().setUseMinifiedResources(false);
getResourceSettings().setResourceStreamLocator(new GeoServerResourceStreamLocator());
/*
* The order string resource loaders are added to IResourceSettings is of importance so we need to add any contributed loader prior to the
* standard ones so it takes precedence. Otherwise it won't be hit due to GeoServerStringResourceLoader never resolving to null but falling
* back to the default language
*/
List alternateResourceLoaders =
getBeansOfType(IStringResourceLoader.class);
for (IStringResourceLoader loader : alternateResourceLoaders) {
LOGGER.info("Registering alternate resource loader: " + loader);
getResourceSettings().getStringResourceLoaders().add(loader);
}
getResourceSettings()
.getStringResourceLoaders()
.add(0, new GeoServerStringResourceLoader());
getDebugSettings().setAjaxDebugModeEnabled(false);
getApplicationSettings().setPageExpiredErrorPage(GeoServerExpiredPage.class);
// generates infinite redirections, commented out for the moment
// getSecuritySettings().setCryptFactory(GeoserverWicketEncrypterFactory.get());
// theoretically, this replaces the old GeoServerRequestEncodingStrategy
// by making the URLs encrypted at will
GeoServerSecurityManager securityManager = getBeanOfType(GeoServerSecurityManager.class);
setRootRequestMapper(
new DynamicCryptoMapper(getRootRequestMapper(), securityManager, this));
getRequestCycleListeners().add(new CallbackRequestCycleListener(this));
WebUIMode webUIMode = getGeoServer().getGlobal().getWebUIMode();
if (webUIMode == null) {
webUIMode = WebUIMode.DEFAULT;
}
switch (webUIMode) {
case DO_NOT_REDIRECT:
getRequestCycleSettings().setRenderStrategy(RenderStrategy.ONE_PASS_RENDER);
break;
case REDIRECT:
getRequestCycleSettings().setRenderStrategy(RenderStrategy.REDIRECT_TO_BUFFER);
break;
case DEFAULT:
getRequestCycleSettings()
.setRenderStrategy(
defaultIsRedirect
? RenderStrategy.REDIRECT_TO_BUFFER
: RenderStrategy.ONE_PASS_RENDER);
}
}
断点进入到GeoServerSecurityManager内,首先程序从实现spring的接口方法onApplicationEvent进入,GeoServerSecurityManager类定义了reload()方法。该方法大意是存储一个实例后,重新加载的配置将会改变。所以使用reload意为重新加载。
/**
* Reload the configuration which may have been updated in the meanwhile; after a restore as an
* instance.
*/
public void reload() {
try {
Resource masterPasswordInfo = security().get(MASTER_PASSWD_INFO_FILENAME);
if (masterPasswordInfo.getType() != Type.UNDEFINED) {
LOGGER.warning(
masterPasswordInfo.path()
+ " is a security risk. Please read this file and remove it afterward");
}
} catch (Exception e1) {
throw new RuntimeException(e1);
}
// migrate from old security config
try {
Version securityVersion = getSecurityVersion();
boolean migratedFrom21 = false;
if (securityVersion.compareTo(VERSION_2_2) < 0) {
migratedFrom21 = migrateFrom21();
}
if (securityVersion.compareTo(VERSION_2_3) < 0) {
removeErroneousAccessDeniedPage();
migrateFrom22(migratedFrom21);
}
if (securityVersion.compareTo(VERSION_2_4) < 0) {
migrateFrom23();
}
if (securityVersion.compareTo(VERSION_2_5) < 0) {
migrateFrom24();
}
if (securityVersion.compareTo(CURR_VERSION) < 0) {
writeCurrentVersion();
}
} catch (Exception e1) {
throw new RuntimeException(e1);
}
// read config and initialize... we do this now since we can be ensured that the spring
// context has been property initialized, and we can successfully look up security
// plugins
KeyStoreProvider keyStoreProvider = getKeyStoreProvider();
try {
// check for an outstanding masster password change
keyStoreProvider.commitMasterPasswordChange();
// check if there is an outstanding master password change in case of SPrin injection
init();
for (GeoServerSecurityProvider securityProvider :
GeoServerExtensions.extensions(GeoServerSecurityProvider.class)) {
securityProvider.init(this);
}
} catch (Exception e) {
throw new BeanCreationException("Error occured reading security configuration", e);
}
}
在GeoServerSecurityManager成员变量userGroupServices和roleServices定义了用户组和角色组。用于存储用户信息、角色信息。
/** cached user groups */
ConcurrentHashMap userGroupServices =
new ConcurrentHashMap();
/** cached role services */
ConcurrentHashMap roleServices =
new ConcurrentHashMap();
在的reload方法中调用了 migrateFrom21()方法,将创建一个带有方法initializeFromConfig的GeoServerSecurityService接回调。同时如果遇到在userGroupServices没有默认的用户名为admin,密码为geosever的用户,则userGroupServices将为创建该用户。该账户的目的用于用户登录后进行验证。我们来看一下migrateFrom21方法具体源代码。
boolean migrateFrom21() throws Exception {
if (role().getType() != Type.UNDEFINED) {
Resource oldUserFile = security().get("users.properties.old");
if (oldUserFile.getType() != Type.UNDEFINED) {
LOGGER.warning(oldUserFile.path() + " could be removed manually");
}
return false; // already migrated
}
LOGGER.info("Start security migration");
// master password configuration
MasterPasswordProviderConfig mpProviderConfig =
loadMasterPassswordProviderConfig("default");
if (mpProviderConfig == null) {
mpProviderConfig = new URLMasterPasswordProviderConfig();
mpProviderConfig.setName("default");
mpProviderConfig.setClassName(URLMasterPasswordProvider.class.getCanonicalName());
mpProviderConfig.setReadOnly(false);
((URLMasterPasswordProviderConfig) mpProviderConfig).setURL(new URL("file:passwd"));
((URLMasterPasswordProviderConfig) mpProviderConfig).setEncrypting(true);
saveMasterPasswordProviderConfig(mpProviderConfig, false);
// save out the default master password
MasterPasswordProvider mpProvider =
loadMasterPasswordProvider(mpProviderConfig.getName());
Resource propFile = security().get("users.properties");
Properties userprops = null;
if (propFile.getType() == Type.RESOURCE) userprops = Util.loadPropertyFile(propFile);
mpProvider.setMasterPassword(extractMasterPasswordForMigration(userprops));
}
MasterPasswordConfig mpConfig = new MasterPasswordConfig();
mpConfig.setProviderName(mpProviderConfig.getName());
saveMasterPasswordConfig(mpConfig);
// check for services.properties, create if necessary
Resource serviceFile = security().get("services.properties");
if (serviceFile.getType() == Type.UNDEFINED) {
org.geoserver.util.IOUtils.copy(
Util.class.getResourceAsStream("serviceTemplate.properties"),
serviceFile.out());
}
long checkInterval = 10000; // 10 secs
// check for the default user group service, create if necessary
GeoServerUserGroupService userGroupService =
loadUserGroupService(XMLUserGroupService.DEFAULT_NAME);
KeyStoreProvider keyStoreProvider = getKeyStoreProvider();
keyStoreProvider.reloadKeyStore();
keyStoreProvider.setUserGroupKey(
XMLUserGroupService.DEFAULT_NAME, randomPasswdProvider.getRandomPassword(32));
keyStoreProvider.storeKeyStore();
PasswordValidator validator = loadPasswordValidator(PasswordValidator.DEFAULT_NAME);
if (validator == null) {
// Policy allows any password except null, this is the default
// at before migration
PasswordPolicyConfig pwpconfig = new PasswordPolicyConfig();
pwpconfig.setName(PasswordValidator.DEFAULT_NAME);
pwpconfig.setClassName(PasswordValidatorImpl.class.getName());
pwpconfig.setMinLength(0);
savePasswordPolicy(pwpconfig);
validator = loadPasswordValidator(PasswordValidator.DEFAULT_NAME);
}
validator = loadPasswordValidator(PasswordValidator.MASTERPASSWORD_NAME);
if (validator == null) {
// Policy requires a minimum of 8 chars for the master password
PasswordPolicyConfig pwpconfig = new PasswordPolicyConfig();
pwpconfig.setName(PasswordValidator.MASTERPASSWORD_NAME);
pwpconfig.setClassName(PasswordValidatorImpl.class.getName());
pwpconfig.setMinLength(8);
savePasswordPolicy(pwpconfig);
validator = loadPasswordValidator(PasswordValidator.MASTERPASSWORD_NAME);
}
if (userGroupService == null) {
XMLUserGroupServiceConfig ugConfig = new XMLUserGroupServiceConfig();
ugConfig.setName(XMLUserGroupService.DEFAULT_NAME);
ugConfig.setClassName(XMLUserGroupService.class.getName());
ugConfig.setCheckInterval(checkInterval);
ugConfig.setFileName(XMLConstants.FILE_UR);
ugConfig.setValidating(true);
// start with weak encryption, plain passwords can be restored
ugConfig.setPasswordEncoderName(
loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, null, false).getName());
ugConfig.setPasswordPolicyName(PasswordValidator.DEFAULT_NAME);
saveUserGroupService(ugConfig);
userGroupService = loadUserGroupService(XMLUserGroupService.DEFAULT_NAME);
}
// check for the default role service, create if necessary
GeoServerRoleService roleService = loadRoleService(XMLRoleService.DEFAULT_NAME);
if (roleService == null) {
XMLRoleServiceConfig gaConfig = new XMLRoleServiceConfig();
gaConfig.setName(XMLRoleService.DEFAULT_NAME);
gaConfig.setClassName(XMLRoleService.class.getName());
gaConfig.setCheckInterval(checkInterval);
gaConfig.setFileName(XMLConstants.FILE_RR);
gaConfig.setValidating(true);
gaConfig.setAdminRoleName(XMLRoleService.DEFAULT_LOCAL_ADMIN_ROLE);
gaConfig.setGroupAdminRoleName(XMLRoleService.DEFAULT_LOCAL_GROUP_ADMIN_ROLE);
saveRoleService(gaConfig);
roleService = loadRoleService(XMLRoleService.DEFAULT_NAME);
}
String filterName = GeoServerSecurityFilterChain.BASIC_AUTH_FILTER;
GeoServerSecurityFilter filter = loadFilter(filterName);
if (filter == null) {
BasicAuthenticationFilterConfig bfConfig = new BasicAuthenticationFilterConfig();
bfConfig.setName(filterName);
bfConfig.setClassName(GeoServerBasicAuthenticationFilter.class.getName());
bfConfig.setUseRememberMe(true);
saveFilter(bfConfig);
}
/*filterName = GeoServerSecurityFilterChain.BASIC_AUTH_NO_REMEMBER_ME_FILTER;
filter = loadFilter(filterName);
if (filter==null) {
BasicAuthenticationFilterConfig bfConfig = new BasicAuthenticationFilterConfig();
bfConfig.setClassName(GeoServerBasicAuthenticationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setUseRememberMe(false);
saveFilter(bfConfig);
}*/
filterName = GeoServerSecurityFilterChain.FORM_LOGIN_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
UsernamePasswordAuthenticationFilterConfig upConfig =
new UsernamePasswordAuthenticationFilterConfig();
upConfig.setClassName(GeoServerUserNamePasswordAuthenticationFilter.class.getName());
upConfig.setName(filterName);
upConfig.setUsernameParameterName(
UsernamePasswordAuthenticationFilterConfig.DEFAULT_USERNAME_PARAM);
upConfig.setPasswordParameterName(
UsernamePasswordAuthenticationFilterConfig.DEFAULT_PASSWORD_PARAM);
saveFilter(upConfig);
}
filterName = GeoServerSecurityFilterChain.SECURITY_CONTEXT_ASC_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
SecurityContextPersistenceFilterConfig pConfig =
new SecurityContextPersistenceFilterConfig();
pConfig.setClassName(GeoServerSecurityContextPersistenceFilter.class.getName());
pConfig.setName(filterName);
pConfig.setAllowSessionCreation(true);
saveFilter(pConfig);
}
filterName = GeoServerSecurityFilterChain.SECURITY_CONTEXT_NO_ASC_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
SecurityContextPersistenceFilterConfig pConfig =
new SecurityContextPersistenceFilterConfig();
pConfig.setClassName(GeoServerSecurityContextPersistenceFilter.class.getName());
pConfig.setName(filterName);
pConfig.setAllowSessionCreation(false);
saveFilter(pConfig);
}
filterName = GeoServerSecurityFilterChain.ANONYMOUS_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
AnonymousAuthenticationFilterConfig aConfig = new AnonymousAuthenticationFilterConfig();
aConfig.setClassName(GeoServerAnonymousAuthenticationFilter.class.getName());
aConfig.setName(filterName);
saveFilter(aConfig);
}
filterName = GeoServerSecurityFilterChain.REMEMBER_ME_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
RememberMeAuthenticationFilterConfig rConfig =
new RememberMeAuthenticationFilterConfig();
rConfig.setClassName(GeoServerRememberMeAuthenticationFilter.class.getName());
rConfig.setName(filterName);
saveFilter(rConfig);
}
filterName = GeoServerSecurityFilterChain.FILTER_SECURITY_INTERCEPTOR;
filter = loadFilter(filterName);
if (filter == null) {
SecurityInterceptorFilterConfig siConfig = new SecurityInterceptorFilterConfig();
siConfig.setClassName(GeoServerSecurityInterceptorFilter.class.getName());
siConfig.setName(filterName);
siConfig.setAllowIfAllAbstainDecisions(false);
siConfig.setSecurityMetadataSource("geoserverMetadataSource");
saveFilter(siConfig);
}
filterName = GeoServerSecurityFilterChain.FILTER_SECURITY_REST_INTERCEPTOR;
filter = loadFilter(filterName);
if (filter == null) {
SecurityInterceptorFilterConfig siConfig = new SecurityInterceptorFilterConfig();
siConfig.setClassName(GeoServerSecurityInterceptorFilter.class.getName());
siConfig.setName(filterName);
siConfig.setAllowIfAllAbstainDecisions(false);
siConfig.setSecurityMetadataSource("restFilterDefinitionMap");
saveFilter(siConfig);
}
filterName = GeoServerSecurityFilterChain.FORM_LOGOUT_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
LogoutFilterConfig loConfig = new LogoutFilterConfig();
loConfig.setClassName(GeoServerLogoutFilter.class.getName());
loConfig.setName(filterName);
saveFilter(loConfig);
}
filterName = GeoServerSecurityFilterChain.DYNAMIC_EXCEPTION_TRANSLATION_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
ExceptionTranslationFilterConfig bfConfig = new ExceptionTranslationFilterConfig();
bfConfig.setClassName(GeoServerExceptionTranslationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setAuthenticationFilterName(null);
bfConfig.setAccessDeniedErrorPage("/accessDenied.jsp");
saveFilter(bfConfig);
}
filterName = GeoServerSecurityFilterChain.GUI_EXCEPTION_TRANSLATION_FILTER;
filter = loadFilter(filterName);
if (filter == null) {
ExceptionTranslationFilterConfig bfConfig = new ExceptionTranslationFilterConfig();
bfConfig.setClassName(GeoServerExceptionTranslationFilter.class.getName());
bfConfig.setName(filterName);
bfConfig.setAuthenticationFilterName(GeoServerSecurityFilterChain.FORM_LOGIN_FILTER);
bfConfig.setAccessDeniedErrorPage("/accessDenied.jsp");
saveFilter(bfConfig);
}
// check for the default auth provider, create if necessary
GeoServerAuthenticationProvider authProvider =
loadAuthenticationProvider(GeoServerAuthenticationProvider.DEFAULT_NAME);
if (authProvider == null) {
UsernamePasswordAuthenticationProviderConfig upAuthConfig =
new UsernamePasswordAuthenticationProviderConfig();
upAuthConfig.setName(GeoServerAuthenticationProvider.DEFAULT_NAME);
upAuthConfig.setClassName(UsernamePasswordAuthenticationProvider.class.getName());
upAuthConfig.setUserGroupServiceName(userGroupService.getName());
saveAuthenticationProvider(upAuthConfig);
authProvider = loadAuthenticationProvider(GeoServerAuthenticationProvider.DEFAULT_NAME);
}
// save the top level config
SecurityManagerConfig config = new SecurityManagerConfig();
config.setRoleServiceName(roleService.getName());
config.getAuthProviderNames().add(authProvider.getName());
config.setEncryptingUrlParams(false);
// start with weak encryption
config.setConfigPasswordEncrypterName(
loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, true, false).getName());
// setup the default remember me service
RememberMeServicesConfig rememberMeConfig = new RememberMeServicesConfig();
rememberMeConfig.setClassName(GeoServerTokenBasedRememberMeServices.class.getName());
config.setRememberMeService(rememberMeConfig);
config.setFilterChain(GeoServerSecurityFilterChain.createInitialChain());
saveSecurityConfig(config);
// TODO: just call initializeFrom
userGroupService.setSecurityManager(this);
roleService.setSecurityManager(this);
// populate the user group and role service
GeoServerUserGroupStore userGroupStore = userGroupService.createStore();
GeoServerRoleStore roleStore = roleService.createStore();
// migrate from users.properties
Resource usersFile = security().get("users.properties");
if (usersFile.getType() == Type.RESOURCE) {
// load user.properties populate the services
Properties props = Util.loadPropertyFile(usersFile);
UserAttributeEditor configAttribEd = new UserAttributeEditor();
for (Iterator
初始为用户名和密码后,我们来看一下验证。geoserver使用spring的拦截器实现用户名密码登录。自定义拦截器bean配置在web目录下的core、src、main、java目录下的applicationContext中。我们来看一下配置文件。
我们来看一下GeoServerUserNamePasswordAuthenticationFilter拦截器的定义。
public class GeoServerUserNamePasswordAuthenticationFilter extends GeoServerCompositeFilter
implements GeoServerAuthenticationFilter {
// public static final String URL_FOR_LOGIN = "/j_spring_security_check";
public static final String URL_LOGIN_SUCCCESS = "/web";
public static final String URL_LOGIN_FAILURE =
"/web/wicket/bookmarkable/org.geoserver.web.GeoServerLoginPage?error=true";
public static final String URL_LOGIN_FORM =
"/web/wicket/bookmarkable/org.geoserver.web.GeoServerLoginPage?error=false";
// public static final String URL_LOGIN_FORM="/admin/login.do";
private LoginUrlAuthenticationEntryPoint aep;
String[] pathInfos;
//https://www.cnblogs.com/lexiaofei/p/7018405.html?utm_source=itdadao&utm_medium=referral
@Override
public void initializeFromConfig(SecurityNamedServiceConfig config) throws IOException {
super.initializeFromConfig(config);
pathInfos = GeoServerSecurityFilterChain.FORM_LOGIN_CHAIN.split(",");
UsernamePasswordAuthenticationFilterConfig upConfig =
(UsernamePasswordAuthenticationFilterConfig) config;
aep = new LoginUrlAuthenticationEntryPoint(URL_LOGIN_FORM);
aep.setForceHttps(false);
try {
aep.afterPropertiesSet();
} catch (Exception e2) {
throw new IOException(e2);
}
RememberMeServices rms = securityManager.getRememberMeService();
// add login filter
UsernamePasswordAuthenticationFilter filter =
new UsernamePasswordAuthenticationFilter() {
@Override
protected boolean requiresAuthentication(
HttpServletRequest request, HttpServletResponse response) {
for (String pathInfo : pathInfos) {
if (getRequestPath(request).startsWith(pathInfo)) return true;
}
return false;
}
};
filter.setPasswordParameter(upConfig.getPasswordParameterName());
filter.setUsernameParameter(upConfig.getUsernameParameterName());
filter.setAuthenticationManager(getSecurityManager().authenticationManager());
filter.setRememberMeServices(rms);
GeoServerWebAuthenticationDetailsSource s = new GeoServerWebAuthenticationDetailsSource();
filter.setAuthenticationDetailsSource(s);
filter.setAllowSessionCreation(false);
// filter.setFilterProcessesUrl(URL_FOR_LOGIN);
SimpleUrlAuthenticationSuccessHandler successHandler =
new SimpleUrlAuthenticationSuccessHandler();
successHandler.setDefaultTargetUrl(URL_LOGIN_SUCCCESS);
filter.setAuthenticationSuccessHandler(successHandler);
SimpleUrlAuthenticationFailureHandler failureHandler =
new SimpleUrlAuthenticationFailureHandler();
// TODO, check this when using encrypting of URL parameters
failureHandler.setDefaultFailureUrl(URL_LOGIN_FAILURE);
filter.setAuthenticationFailureHandler(failureHandler);
// filter.afterPropertiesSet();
getNestedFilters().add(filter);
}
@Override
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return aep;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
req.setAttribute(GeoServerSecurityFilter.AUTHENTICATION_ENTRY_POINT_HEADER, aep);
super.doFilter(req, res, chain);
}
/** @see org.geoserver.security.filter.GeoServerAuthenticationFilter#applicableForHtml() */
@Override
public boolean applicableForHtml() {
return true;
}
/** @see org.geoserver.security.filter.GeoServerAuthenticationFilter#applicableForServices() */
@Override
public boolean applicableForServices() {
return false;
}
}
GeoServerUserNamePasswordAuthenticationFilter类实现了initializeFromConfig方法。配置了UsernamePasswordAuthenticationFilter相关参数,如setPasswordParameter设置密码参数、设置setUsernameParameter用户参数、设置认证管理类setAuthenticationManager、设置成功跳转setAuthenticationSuccessHandler等。
在上面的migrateFrom21方法中,最后还设置配置config,设置了用户名密码验证提供类。在用户名密码提供类UsernamePasswordAuthenticationProvider的实现接口中有配置config参数,利用该参数获取admin用户名的验证实例UserDetailsService。该UserDetailsService是我们在migrateFrom21方法中创建好的,UsernamePasswordAuthenticationProvider的成员变量DaoAuthenticationProvider利用setUserDetailsService方法设置该正确验证实例。
好了,做好了前期的创建正确UserDetailsService和获取前台接收的用户名和密码后,利用Spring Security中的相关类对用户进行验证。
更多内容,请关注公众号