/*
* Copyright 2014 MOSPA(Ministry of Security and Public Administration).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package egovframework.rte.fdl.security.config;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.StringUtils;
/**
* egov-security schema namespace 처리를 담당하는 bean 클래스
*
*<p>Desc.: 설정 간소화 처리에 사용되는 bean으로 설정 초기화를 처리</p>
*
* @author Vincent Han
* @since 2014.03.12
* @version 3.0
* @see <pre>
* == 개정이력(Modification Information) ==
*
* 수정일 수정자 수정내용
* ---------------------------------------------------------------------------------
* 2014.03.12 한성곤 Spring Security 설정 간소화 기능 추가
*
* </pre>
*/
public class SecurityConfigInitializer implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfigInitializer.class);
private ApplicationContext context;
private SecurityConfig config;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
config = (SecurityConfig) context.getBean(SecurityConfig.class);
}
@PostConstruct
public void init() {
LOGGER.debug("init() started...");
if (StringUtils.hasText(config.getAccessDeniedUrl())) {
LOGGER.debug("Replaced access denied url : {}", config.getAccessDeniedUrl());
registerAccessDeniedUrl(config.getAccessDeniedUrl());
}
if (StringUtils.hasText(config.getLoginUrl())) {
LOGGER.debug("Replaced login url : {}", config.getLoginUrl());
registerLoginUrl();
}
if (StringUtils.hasText(config.getLoginFailureUrl())) {
LOGGER.debug("Replaced login failure url : {}", config.getLoginFailureUrl());
registerLoginFailureUrl(config.getLoginFailureUrl());
}
if (StringUtils.hasText(config.getLogoutSuccessUrl())) {
LOGGER.debug("Replaced logout success url : {}", config.getLogoutSuccessUrl());
registerLogoutSuccessUrl(config.getLogoutSuccessUrl());
}
registerJdbcInfo(config.getJdbcUsersByUsernameQuery(), config.getJdbcAuthoritiesByUsernameQuery(), config.getJdbcMapClass());
if (StringUtils.hasText(config.getHash())) {
LOGGER.debug("Password Encoder Algorithm : {}", config.getHash());
registerHash(config.getHash(), config.isHashBase64());
}
if (config.getConcurrentMaxSessons() > 0 || StringUtils.hasText(config.getConcurrentExpiredUrl())) {
LOGGER.debug("Concurrent max sessions : {}", config.getConcurrentMaxSessons());
LOGGER.debug("Concurrent concurrent expired url : {}", config.getConcurrentExpiredUrl());
registerConcurrentControl(config.getConcurrentMaxSessons(), config.getConcurrentExpiredUrl());
}
if (StringUtils.hasText(config.getDefaultTargetUrl())) {
LOGGER.debug("Default target url : {}", config.getDefaultTargetUrl());
registerDefaultTargetUrl(config.getDefaultTargetUrl());
}
LOGGER.debug("init() ended...");
}
private <T extends Filter> T getSecurityFilter(Class<T> type) {
Map<String, DefaultSecurityFilterChain> filterChainMap = context.getBeansOfType(DefaultSecurityFilterChain.class);
for (DefaultSecurityFilterChain filterChain : filterChainMap.values()) {
for (Filter filter : filterChain.getFilters()) {
if (type.isInstance(filter)) {
return type.cast(filter);
}
}
}
//throw new NoSuchBeanDefinitionException("No bean of type [" + type.getName() + "] is defined.");
throw new NoSuchBeanDefinitionException(type);
}
private void registerLogoutSuccessUrl(String logoutSuccessUrl) {
LogoutFilter filter = getSecurityFilter(LogoutFilter.class);
checkUrl(logoutSuccessUrl);
try {
Field field = filter.getClass().getDeclaredField("logoutSuccessHandler");
field.setAccessible(true);
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = (SimpleUrlLogoutSuccessHandler) field.get(filter);
logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void registerLoginFailureUrl(String loginFailureUrl) {
UsernamePasswordAuthenticationFilter filter = getSecurityFilter(UsernamePasswordAuthenticationFilter.class);
checkUrl(loginFailureUrl);
SimpleUrlAuthenticationFailureHandler failureHandler = null;
Method method = null;
try {
//method = UsernamePasswordAuthenticationFilter.class.getDeclaredMethod("getFailureHandler", (Class<?>[]) null);
method = AbstractAuthenticationProcessingFilter.class.getDeclaredMethod("getFailureHandler", (Class<?>[]) null);
method.setAccessible(true);
failureHandler = (SimpleUrlAuthenticationFailureHandler) method.invoke(filter, (Object[]) null);
} catch (Exception ex) {
LOGGER.error("## registerLoginFailureUrl : {}", ex);
throw new RuntimeException(ex);
}
failureHandler.setDefaultFailureUrl(loginFailureUrl);
}
private void registerLoginUrl() {
/*
ExceptionTranslationFilter filter = getSecurityFilter(ExceptionTranslationFilter.class);
checkUrl(loginUrl);
LoginUrlAuthenticationEntryPoint entryPoint = (LoginUrlAuthenticationEntryPoint) filter.getAuthenticationEntryPoint();
entryPoint.setLoginFormUrl(loginUrl);
*/
// LoginUrlAuthenticationEntryPoint 설정을 통해 지정함 (LoginFormUrlFactoryBean 참조)
}
protected void registerAccessDeniedUrl(String accessDeniedUrl) {
ExceptionTranslationFilter filter = getSecurityFilter(ExceptionTranslationFilter.class);
checkUrl(accessDeniedUrl);
AccessDeniedHandlerImpl accessDeniedHandler = new AccessDeniedHandlerImpl();
accessDeniedHandler.setErrorPage(accessDeniedUrl);
filter.setAccessDeniedHandler(accessDeniedHandler);
}
protected void registerJdbcInfo(String jdbcUsersByUsernameQuery, String jdbcAuthoritiesByUsernameQuery, String jdbcMapClass) {
/*
EgovJdbcUserDetailsManager manager = (EgovJdbcUserDetailsManager)context.getBean(EgovJdbcUserDetailsManager.class);
//manager.setDataSource(dataSource); // set by DataSourceFactorybBean
manager.setUsersByUsernameQuery(jdbcUsersByUsernameQuery);
manager.setAuthoritiesByUsernameQuery(jdbcAuthoritiesByUsernameQuery);
manager.setMapClass(jdbcMapClass);
*/
// FactoryBean을 통해 지정
}
protected void registerHash(String hash, boolean isHashBase64) {
DaoAuthenticationProvider authentication = context.getBean(DaoAuthenticationProvider.class);
if (hash.equalsIgnoreCase("plaintext")) {
authentication.setPasswordEncoder(new PlaintextPasswordEncoder());
} else if (hash.equalsIgnoreCase("md5")) {
Md5PasswordEncoder password = new Md5PasswordEncoder();
password.setEncodeHashAsBase64(isHashBase64);
authentication.setPasswordEncoder(password);
} else if (hash.equalsIgnoreCase("sha")) {
ShaPasswordEncoder password = new ShaPasswordEncoder();
password.setEncodeHashAsBase64(isHashBase64);
authentication.setPasswordEncoder(password);
} else if (hash.equalsIgnoreCase("sha-256")) {
// CHECKSTYLE:OFF
ShaPasswordEncoder password = new ShaPasswordEncoder(256);
// CHECKSTYLE:ON
password.setEncodeHashAsBase64(isHashBase64);
authentication.setPasswordEncoder(password);
} else if (hash.equalsIgnoreCase("bcrypt")) {
BCryptPasswordEncoder password = new BCryptPasswordEncoder();
authentication.setPasswordEncoder(password);
} else {
throw new IllegalArgumentException("'hash' attribute have to be plaintext, md5, sha, sha-256, or bcrypt");
}
}
private void registerConcurrentControl(int concurrentMaxSessons, String concurrentExpiredUrl) {
// concurrentMaxSessons and concurrentExpiredURL are set by config (<session-management> and ConcurrentSessionFilter)
}
private void registerDefaultTargetUrl(String defaultTargetUrl) {
//UsernamePasswordAuthenticationFilter
AbstractAuthenticationProcessingFilter filter = getSecurityFilter(AbstractAuthenticationProcessingFilter.class);
checkUrl(defaultTargetUrl);
Method method = null;
try {
method = AbstractAuthenticationProcessingFilter.class.getDeclaredMethod("getSuccessHandler", (Class<?>[]) null);
method.setAccessible(true);
SavedRequestAwareAuthenticationSuccessHandler successHandler = (SavedRequestAwareAuthenticationSuccessHandler) method.invoke(filter, (Object[]) null);
successHandler.setAlwaysUseDefaultTargetUrl(true);
successHandler.setDefaultTargetUrl(defaultTargetUrl);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void checkUrl(String url) {
if (!UrlUtils.isValidRedirectUrl(url)) {
LOGGER.warn("Url ({} is not a valid redirect URL (must start with '/' or http(s))", url);
}
}
}