-
spring boot 정리 (locale, messageSource, security, jpa, hibernate, Scheduler, config)IT/spring 2021. 2. 4. 15:43
안녕하세요
Spring boot 정리 해봅니다.
https://github.com/visualkhh/lib-spring/tree/master/boot/default_template
visualkhh@gmail.com
springboot로 만든 기본틀입니다.
사용 : spring jpa, hibernate, scheduler, config, groovyTemplate
//설명은 소스안 주석으로 설명하겠습니다.
build.gradle
group 'com.boot'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'war'
apply plugin: 'groovy'
version = '1.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
compileJava {
options.fork = true
options.forkOptions.executable = "${System.getenv().JAVA_HOME}/bin/javac" // assumes that javac is on PATH
//options.compilerArgs << "-XDignore.symbol.file"// does not exist 오류나면..
}
buildscript {
ext {
springBootVersion = '1.4.1.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
// classpath("org.springframework:springloaded:1.2.5.RELEASE")
}
}
repositories {
maven { url "http://119.206.205.172:8081/nexus/content/repositories/releases/" }
mavenCentral()
// maven { url "http://www.egovframe.go.kr/maven/" }
}
dependencies {
// egov 전자정부 프레임워크 감리통과하려면 아래 풀어 dependencies 걸어서 감리만 통과하여라
// compile(group: 'egovframework.rte', name: 'egovframework.rte.ptl.mvc', version:'3.5.0') {
// exclude(group: 'egovframework.rte')
//// exclude(module: 'commons-logging')
// }
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-security:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-integration:${springBootVersion}")
//devtools
compile("org.springframework.boot:spring-boot-devtools")
//config를위하여
compile("org.springframework.boot:spring-boot-configuration-processor")
//optional ("org.springframework.boot:spring-boot-configuration-processor")
//view template select one!
compile("org.springframework.boot:spring-boot-starter-groovy-templates:${springBootVersion}")
//compile("org.springframework.boot:spring-boot-starter-thymeleaf:${springBootVersion}")
//compile("org.springframework.boot:spring-boot-starter-velocity:${springBootVersion}")
/*jsp jstl viwe 쓰려면
compile("javax.servlet:jstl");
compile("org.apache.tomcat.embed:tomcat-embed-jasper")
*/
compile("com.h2database:h2:1.4.192");
compile('com.jayway.jsonpath:json-path:2.1.0')
/* lombok*/
compile group: 'org.projectlombok', name: 'lombok', version: '1.16.8'
/* quartz */
compile group: 'org.quartz-scheduler', name: 'quartz', version: '1.8.6'
// https://mvnrepository.com/artifact/javax.transaction/jta
compile group: 'javax.transaction', name: 'jta', version: '1.1'
/* email */
compile group: 'javax.mail', name: 'mail', version: '1.4.7'
/* class scaner https://sites.google.com/site/javacornproject/corn-cps */
compile group: 'net.sf.corn', name: 'corn-cps', version: '1.1.7'
testCompile ("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
compile group: 'com.omnicns', name: 'common-java', version: '0.0.1'
compile group: 'com.omnicns', name: 'common-jsp', version: '0.0.1'
compile group: 'com.omnicns', name: 'common-spring', version: '0.0.1'
testCompile group: 'junit', name: 'junit', version: '4.11'
}
compileJava.dependsOn(processResources)
idea {
module {
inheritOutputDirs = false
outputDir = file("$buildDir/classes/main/")
}
}application.yml
#http://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
#http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
application:
version: 1.0
title: Board Application
spring:
devtools:
livereload:
enabled: true
freemarker:
cache: false
# templateLoaderPath:
config:
location:
application:
name: springboot
main:
banner-mode: off
h2:
console:
enabled: true # Enable the console.
path: /h2-console # Path at which the console will be available.
settings:
trace: false # Enable trace output.
web-allow-others: false # Enable remote access.
datasource:
name: testdb
initialize: true # Populate the database using 'data.sql'.
#schema:
#data:
platform: all # Platform to use in the schema resource (schema-${platform}.sql).
# url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driverClassName: org.h2.Driver
username: sa
password:
continue-on-error: true
# jpa:
# database-platform: org.hibernate.dialect.H2Dialect
# generate-ddl: true
# show-sql: true
# open-in-view: true
# hibernate:
# ddl-auto: create-drop
# properties:
# hibernate.current_session_context_class: thread
# hibernate.jdbc.batch_size: 50
# hibernate.format_sql: false
# hibernate.validator.apply_to_ddl: false
# hibernate.validator.autoregister_listeners: false
# javax.persistence.validation.mode: none
## hibernate.current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext
data:
repositories:
enabled: true
http:
multipart:
enabled: true # Enable support of multi-part uploads.
mvc:
locale: ko_KR
groovy:
template:
cache: false
# INTERNATIONALIZATION (MessageSourceAutoConfiguration)
messages:
# always-use-message-format: false # Set whether to always apply the MessageFormat rules, parsing even messages without arguments.
cache-seconds: -1 # Loaded resource bundle files cache expiration, in seconds. When set to -1, bundles are cached forever.
basename: classpath:/messages/message # Comma-separated list of basenames, each following the ResourceBundle convention.
encoding: UTF-8 # Message bundles encoding.
# fallback-to-system-locale: true # Set whether to fall back to the system Locale if no files for a specific Locale have been found.
# SECURITY (SecurityProperties)
security:
basic:
authorize-mode: role # Security authorize mode to apply.
enabled: true # Enable basic authentication.
path: /** # Comma-separated list of paths to secure.
realm: Spring # HTTP basic realm name.
enable-csrf: true # Enable Cross Site Request Forgery support.
filter-order: 0 # Security filter chain order.
filter-dispatcher-types: ASYNC, FORWARD, INCLUDE, REQUEST # Security filter chain dispatcher types.
headers:
cache: true # Enable cache control HTTP headers.
content-type: true # Enable "X-Content-Type-Options" header.
frame: true # Enable "X-Frame-Options" header.
#hsts= # HTTP Strict Transport Security (HSTS) mode (none, domain, all).
xss: true # Enable cross site scripting (XSS) protection.
#ignored= # Comma-separated list of paths to exclude from the default secured paths.
require-ssl: false # Enable secure channel for all requests.
sessions: stateless # Session creation policy (always, never, if_required, stateless).
user:
name: user # Default admin name.
password: user # Password for the default admin name. A random password is logged on startup by default.
role: USER # Granted roles for the default admin name.
# view:
# prefix:
# suffix: :.vm
logging:
level:
root: INFO
org.springframework.web: INFO
org.hibernate: INFO
com.khh: INFO
multipart:
maxFileSize: 10Mb
######## CUSTOM PROPERTIES ##########
hibernate:
mapping-locations: classpath:hibernate/*.hbm.xml
# packages-to-scan: com.khh.project
# annotated-packages: com.khh.project
properties:
# hibernate.connection.url
hibernate.dialect: org.hibernate.dialect.H2Dialect
hibernate.show_sql: true
hibernate.hbm2ddl.auto: update
hibernate.current_session_context_class: thread
project:
properties:
hello: hello22
---
spring:
profiles: development
logging:
level:
root: INFO
org.springframework.web: INFO
org.hibernate: DEBUG
com.khh: DEBUG
---
spring:
profiles: production
logging:
#file: logs/application.log
level:
root: INFO
org.springframework.web: INFO
org.hibernate: INFO
com.khh: INFOWebMvcConfigurerAdapter
1.다국어(locale)
2.messageSource
3.interceptor
- springsecurity csrf
package com.khh.project.config.web;
import com.khh.project.web.error.ErrorController;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
@Configuration
@EnableWebMvc
@Slf4j
public class WebMvcConfigurerAdapter extends org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter {
@Value("${spring.mvc.locale}")
Locale locale = null;
@Value("${spring.messages.basename}")
String messagesBasename = null;
@Value("${spring.messages.encoding}")
String messagesEncoding = null;
@Value("${spring.messages.cache-seconds}")
int messagesCacheSeconds;
@Autowired
SessionFactory sessionFactory = null;
// preHandle boolean 1. 클라이언트의 요청을 컨트롤러에 전달 하기 전에 호출
// false 인 경우 intercepter 또는 controller 를 실행 시키지 않고 요청 종료
// postHandle void 1. 컨트롤러 로직 실행 된 후 호출됨2. 컨트롤러 실행 도중 error 발생의 경우 postHandle() 는 실행 되지 않음
// request 로 넘어온 데이터 가공시 많이 쓰임
// afterCompletion void 1. 컨트롤러 로직 실행 된 후 호출 됨 2. 컨트롤러 실행 도중이나 view 페이지 실행 도중 error 발생 해도 실행됨
// 공통 Exception 처리 로직 작성시 많이 쓰임
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(csrfTokenAddingInterceptor()).addPathPatterns("/**"); //.includePathPatterns("/**") .excludePathPatterns("/**/*.ecxld");
registry.addInterceptor(localeChangeInterceptor()).addPathPatterns("/**");
registry.addInterceptor(sessionFactoryTransctionInterceptor()).addPathPatterns("/**");
}
@Bean
public HandlerInterceptor sessionFactoryTransctionInterceptor() {
return new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
sessionFactory.getCurrentSession().beginTransaction();
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
sessionFactory.getCurrentSession().getTransaction().commit();
}
};
}
@Bean
public HandlerInterceptor csrfTokenAddingInterceptor() {
return new HandlerInterceptorAdapter() {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView view) {
CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (token != null && null != view) {
view.addObject(token.getParameterName(), token);
}
}
};
}
//다국어 https://justinrodenbostel.com/2014/05/13/part-4-internationalization-in-spring-boot/
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(locale);
return slr;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
//message source
@Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(messagesBasename); //"classpath:/messages/message"
messageSource.setDefaultEncoding(messagesEncoding);
messageSource.setCacheSeconds(messagesCacheSeconds);
return messageSource;
}
@Bean
public MessageSourceAccessor getMessageSourceAccessor(){
ReloadableResourceBundleMessageSource m = messageSource();
return new MessageSourceAccessor(m);
}
//로그인페이지.
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController(WebSecurityConfigurerAdapter.LOGIN_PAGE).setViewName("security/login");
}
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> {
container.addErrorPages(
new ErrorPage(HttpStatus.UNAUTHORIZED, ErrorController.ERROR_401),
new ErrorPage(HttpStatus.FORBIDDEN, ErrorController.ERROR_403),
new ErrorPage(HttpStatus.NOT_FOUND, ErrorController.ERROR_404),
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, ErrorController.ERROR_500),
new ErrorPage(Throwable.class, ErrorController.ERROR_DEFAULT)
);
};
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resource/**") .addResourceLocations("/resource/");
registry.addResourceHandler("/static/**") .addResourceLocations("/static/");
registry.addResourceHandler("/static/**") .addResourceLocations("/static/");
registry.addResourceHandler("/img/**") .addResourceLocations("/img/");
registry.addResourceHandler("/image/**") .addResourceLocations("/image/");
}
}spring jpa와 함께 hibernate original 사용하기위해 설정
package com.khh.project.config;
import com.khh.Application;
import com.khh.project.config.properties.HibernateProperties;
import com.khh.project.config.properties.ProjectProperties;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.*;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;
import org.springframework.orm.hibernate4.HibernateTemplate;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
@Configuration
@EnableTransactionManagement
@Slf4j
public class HibernateConfig {
@Autowired
DataSource dataSource;
@Autowired
HibernateProperties hibernateProperties;
@Autowired
ProjectProperties ProjectProperties;
@Autowired
private ResourceLoader resourceLoader;
//https://github.com/netgloo/spring-boot-samples/blob/master/spring-boot-mysql-hibernate/src/main/java/netgloo/configs/DatabaseConfig.java
@Bean
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
factory.setPackagesToScan();
factory.setDataSource(dataSource);
if (hibernateProperties.getMappingLocations() != null) {
ClassLoader cl = this.getClass().getClassLoader();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = resolver.getResources(hibernateProperties.getMappingLocations()) ;
for (Resource resource: resources){
factory.setMappingLocations(resources);
}
}
if (hibernateProperties.getPackagesToScan() != null) {
factory.setPackagesToScan(hibernateProperties.getPackagesToScan());
}else{
factory.setPackagesToScan(Application.BASE_PACKAGES);
}
if (hibernateProperties.getAnnotatedPackages() != null) {
factory.setAnnotatedPackages(hibernateProperties.getAnnotatedPackages());
}else{
factory.setAnnotatedPackages(Application.BASE_PACKAGES);
}
factory.setHibernateProperties(hibernateProperties.getProperties());
Properties hibernateProperties = new Properties();
SessionFactory sessionFactory = factory.getObject();
return factory;
}
} // class DatabaseConfigSpringBootServletInitializer
- Scheduler, characterEncoding, War
package com.khh.project.config;
import com.khh.Application;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.Filter;
import java.nio.charset.Charset;
@Configuration
@EnableScheduling
public class SpringBootServletInitializer extends org.springframework.boot.web.support.SpringBootServletInitializer {
//war를 처리하기위해 필요하다.
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
@Bean
public HttpMessageConverter<String> responseBodyConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
@Bean
public Filter characterEncodingFilter() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return characterEncodingFilter;
}
//////////////////////////scheduled 스케쥴 하기위하여//////////////////////
//////////////@EnableScheduling 붙쳐줘야한다
@Bean
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean bean = new ScheduledExecutorFactoryBean();
bean.setPoolSize(5);
return bean;
}
}WebSecurityConfigurerAdapter
- Security
package com.khh.project.config.web;
import com.khh.project.config.web.security.*;
import com.khh.project.config.web.security.user.LoginUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import static org.hibernate.criterion.Restrictions.and;
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@EnableWebSecurity
public class WebSecurityConfigurerAdapter extends org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter {
@Value("${spring.h2.console.enabled}")
boolean h2ConsoleEnabled;
@Value("${spring.h2.console.path}")
String h2ConsolePath;
public static final String ROOT_PATH = "/";
public static final String SECURITY_PATH = "/security";
public static final String ANON_PATH = "/anon";
public static final String AUTH_PATH = "/auth";
public static final String LOGIN_PAGE = SECURITY_PATH+"/login";
public static final String LOGIN_PROCESSING_URL = SECURITY_PATH+"/sign_in";
public static final String FAILURE_URL = SECURITY_PATH+"/fail";
public static final String USERNAME_PARAMETER = "username";
public static final String PASSWORD_PARAMETER = "password";
public static final String DEFAULT_SUCCESS_URL = ROOT_PATH;
public static final String LOGOUT_SUCCESS_URL = ROOT_PATH;
public static final String SESSION_EXPIRED_URL = LOGIN_PAGE+"?expred";
public static final String SESSION_INVALIDSESSION_URL = LOGIN_PAGE+"?invalid";
public static final String LOGOUT_URL = SECURITY_PATH+"/sign_out";
public static final String REMEMBER_ME_KEY = "REMEBMER_ME_KEY";
public static final String REMEMBER_ME_COOKE_NAME = "REMEMBER_ME_COOKE";
// @Autowired
// private LoginUserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
// @Autowired
// private DataSource dataSource;
@Override
public void configure(WebSecurity web) throws Exception {
if(h2ConsoleEnabled) {
web.ignoring().antMatchers(h2ConsolePath+"/**");
}
web.ignoring().antMatchers("/resource/**","/static/**","/img/**","/image/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
log.debug("-----security HttpSecurity-----"+http);
http
.anonymous()
.and()
.authorizeRequests()
.antMatchers("/", ANON_PATH +"/**") .permitAll()
.antMatchers(AUTH_PATH +"/**") .hasRole("AUTH")
.antMatchers("/admin/**") .hasRole("ADMIN")
.antMatchers("/board/**") .hasRole("USER")
//.antMatchers("/board/**").hasAnyAuthority()
.anyRequest().authenticated()
.and()
.sessionManagement() //http://niees.tistory.com/17
.maximumSessions(1)
.expiredUrl(SESSION_EXPIRED_URL) //중복 로그인이 일어났을 경우 이동 할 주소
.maxSessionsPreventsLogin(false) //만약 두번째 인증을 거부하게 하고 싶은 경우concurrency-control에 error-if-maximum-exceeded="true"속성을 지정하면 된다.
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl(SESSION_INVALIDSESSION_URL)
.and()
.formLogin()
.loginPage(LOGIN_PAGE) //로그인 페이지
.loginProcessingUrl(LOGIN_PROCESSING_URL) //login-processing-url 로그인 페이지 form action에 입력할 주소 지정
.failureUrl(FAILURE_URL) //실패시 이동될 페이지
.usernameParameter(USERNAME_PARAMETER)
.passwordParameter(PASSWORD_PARAMETER)
.defaultSuccessUrl(DEFAULT_SUCCESS_URL) //성공시 이동될 페이지
.failureHandler(authenticationFailureHandler())
.successHandler(authenticationSuccessHandler())
.permitAll()
.and()
.rememberMe()
.key(REMEMBER_ME_KEY)
.rememberMeServices(tokenBasedRememberMeServices())
.and()
.logout()
.deleteCookies(REMEMBER_ME_COOKE_NAME)
.deleteCookies("JSESSIONID")
.logoutUrl(LOGOUT_URL)
.invalidateHttpSession(true)
.logoutSuccessUrl(LOGOUT_SUCCESS_URL)
// .logoutSuccessHandler(logoutSuccessHandler()) //커스텀으로 로그아웃된거에 대한 처리를 해주면 로그아웃성공URL로 가지 않으니 커스텀할떄 사용해여라
.logoutRequestMatcher(new AntPathRequestMatcher(LOGOUT_URL))
.permitAll()
.and()
.authenticationProvider(authenticationProvider()) //configure(AuthenticationManagerBuilder auth) 오버라이딩해서 추가할수도있다.
.csrf();
}
///////////////////////////AuthenticationProvider/////////////////////////////////////////////////
/**/ //AuthenticationProvider Interface는 자동으로 클래스패스에 있는거 찾아서 쓴다
/**/ //
/**/ // //DaoAuthenticationProvider daoAuthenticationProvider() 대신 이거 아래 처럼 등록을해도된다
/**/ // @Override
/**/ // protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**/ // auth.authenticationProvider(authenticationProvider());
/**/ // }
/**/ // @Bean //스프링에서 제공하는 기본적인 아이디 idpassword관련 처리
/**/ // public DaoAuthenticationProvider daoAuthenticationProvider() {
/**/ // DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
/**/ // daoAuthenticationProvider.setUserDetailsService(userDetailsService());
/**/ // daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
/**/ // daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
/**/ // return daoAuthenticationProvider;
/**/ // }
/**/ @Bean
/**/ public AuthenticationProvider authenticationProvider(){
/**/ return new AuthenticationProvider();
/**/ }
/////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
/**/ //커스텀 시큐리티 인텁셉터 하려면 아래를 사용하면된다
/**/ //사용전에 httpSecurity쪽에 추가해줘야된다 .addFilterBefore(filterSecurityInterceptor(), UsernamePasswordAuthenticationFilter.class)
/**/ ////////http://aoruqjfu.fun25.co.kr/index.php/post/657
/**/ @Bean
/**/ public FilterSecurityInterceptor filterSecurityInterceptor() {
/**/ FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
/**/ filterSecurityInterceptor.setAuthenticationManager(authenticationManager);
/**/ filterSecurityInterceptor.setSecurityMetadataSource(filterInvocationSecurityMetadataSource());
/**/ filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());
/**/ return filterSecurityInterceptor;
/**/ }
/**/ @Bean
/**/ public AffirmativeBased affirmativeBased() {
/**/ List<AccessDecisionVoter<? extends Object>> accessDecisionVoters = new ArrayList<>();
/**/ accessDecisionVoters.add(roleVoter());
/**/ AffirmativeBased affirmativeBased = new AffirmativeBased(accessDecisionVoters);
/**/ return affirmativeBased;
/**/ }
/**/ @Bean
/**/ public RoleHierarchyVoter roleVoter() {
/**/ RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy());
/**/ roleHierarchyVoter.setRolePrefix("ROLE_");
/**/ return roleHierarchyVoter;
/**/ }
/**/ //RoleHierarchy 설정
/**/ @Bean
/**/ public RoleHierarchy roleHierarchy() {
/**/ RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
/**/ roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
/**/ return roleHierarchy;
/**/ }
/**/ //시큐리트쪽 부분에서 사용자가 화면 페이지 호출하면 매번 호출되는 클래스 중요함
/**/ @Bean
/**/ public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource(){
/**/ return new FilterInvocationSecurityMetadataSource();
/**/ }
///////////////////////////////////////////////////////////////////////////////////////////////////
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder();
}
@Bean
public LoginUserDetailsService userDetailsService(){
return new LoginUserDetailsService();
}
@Bean
public RememberMeServices tokenBasedRememberMeServices() {
TokenBasedRememberMeServices tokenBasedRememberMeServices = new TokenBasedRememberMeServices(REMEMBER_ME_KEY, userDetailsService());
tokenBasedRememberMeServices.setAlwaysRemember(true);
tokenBasedRememberMeServices.setTokenValiditySeconds(60 * 60 * 24 * 31);
tokenBasedRememberMeServices.setCookieName(REMEMBER_ME_COOKE_NAME);
return tokenBasedRememberMeServices;
}
//login,out 정상처리 및 실패에 대한 Bean
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
log.debug("#### login Success handler #####");
return new AuthenticationSuccessHandler();
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
log.debug("#### login Failurer handler #####");
return new AuthenticationFailureHandler();
}
//로그아웃 성공시 핸들링
@Bean
public LogoutSuccessHandler logoutSuccessHandler(){
return new LogoutSuccessHandler();
}
//
//REMEMBER ME를 위한.
// @Bean
// public PersistentTokenRepository persistentTokenRepository() {
// JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
// tokenRepositoryImpl.setDataSource(dataSource);
// return tokenRepositoryImpl;
// }
}security groovyTemplate에서 csrf넣기 (interceptor설정부분)
login.tpl
//http://www.w3ii.com/ko/groovy/groovy_template_engines.html
layout 'layout.tpl', title: 'login',
content: contents {
div{
form(action:"${WebSecurityConfigurerAdapter.LOGIN_PROCESSING_URL}", method:'POST'){
input(type:'text', name:'username', value:'')
input(type:'password', name:'password', value:'')
input(type:'hidden', name:"${_csrf.parameterName}", value:"${_csrf.token}")
input(type:'submit', value:'submit')
}
}
}UserDetailsService
package com.khh.project.config.web.security.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
//@Transactional //http://jdm.kr/blog/141
public class LoginUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService{
@Autowired
MessageSourceAccessor messageSource;
@Autowired
LoginUserRepository userRepository;
@Override
public LoginUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LoginUserDetails userDetails = userRepository.findByUsername(username);
if (null == userDetails) {
throw new UsernameNotFoundException(messageSource.getMessage("error.login.fail"));
}
return userDetails;
}
}AuthenticationProvider
- 로그인 처리
import com.khh.project.config.web.security.user.LoginUserDetails;
import com.khh.project.config.web.security.user.LoginUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
public class AuthenticationProvider implements org.springframework.security.authentication.AuthenticationProvider {
@Autowired
LoginUserDetailsService userDetailsService;
@Autowired
MessageSourceAccessor messageSource;
@Autowired
PasswordEncoder encoder;
@Override
// @Transactional //http://jdm.kr/blog/141
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WebAuthenticationDetails detail = (WebAuthenticationDetails) authentication.getDetails();
String remoteIP = detail.getRemoteAddress();
String username = (String)authentication.getPrincipal();
String password = (String)authentication.getCredentials();
LoginUserDetails userDetails = userDetailsService.loadUserByUsername(username);
log.info("Login try ip : -> "+remoteIP+" input id("+username+") info:"+userDetails);
if(null == userDetails || userDetails.isAccountNonLocked()==false || userDetails.isAccountNonExpired() == false || userDetails.isEnabled() == false || userDetails.isCredentialsNonExpired() == false) {
throw new UsernameNotFoundException(messageSource.getMessage("error.login.fail"));
}
if(!encoder.matches(encoder.encode(password),userDetails.getPassword())){ //실패
throw new BadCredentialsException(messageSource.getMessage("error.login.fail"));
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
// token.setDetails(userDetails);
return token;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}AuthenticationSuccessHandler
- 로그인성공시
package com.khh.project.config.web.security;
import com.khh.project.config.web.WebSecurityConfigurerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//로그인 성공 핸들러
@Slf4j
public class AuthenticationSuccessHandler implements org.springframework.security.web.authentication.AuthenticationSuccessHandler {
@Autowired
protected AuthenticationManager authenticationManager;
private RequestCache requestCache = null;
private RedirectStrategy redirectStrategy = null;
public AuthenticationSuccessHandler(){
requestCache = new HttpSessionRequestCache();
redirectStrategy = new DefaultRedirectStrategy();
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
sendRedirectDefaultUrl(request,response);
}
private void sendRedirectSessionUrl(HttpServletRequest request,HttpServletResponse response) throws IOException {
SavedRequest savedRequest = requestCache.getRequest(request, response);
String targetUrl = savedRequest.getRedirectUrl();
redirectStrategy.sendRedirect(request, response, targetUrl);
}
private void sendRedirectRefererUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {
String targetUrl = request.getHeader("REFERER");
redirectStrategy.sendRedirect(request, response, targetUrl);
}
private void sendRedirectDefaultUrl(HttpServletRequest request, HttpServletResponse response) throws IOException {
redirectStrategy.sendRedirect(request, response, WebSecurityConfigurerAdapter.DEFAULT_SUCCESS_URL);
}
}LoginUserDetails
package com.khh.project.config.web.security.user;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Collection;
@Entity
@Table(name = "USER")
@Data
public class LoginUserDetails implements org.springframework.security.core.userdetails.UserDetails, Serializable {
@Id
@Column(name = "USERNAME", unique = true, nullable = false)
private String username;
@Column(name = "PASSWORD", nullable = false)
private String password;
@Column(name = "ACCOUNTNONEXPIRED")
private boolean accountNonExpired;
@Column(name = "ACCOUNTNONLOCKED")
private boolean accountNonLocked;
@Column(name = "CREDENTIALSNONEXPIRED")
private boolean credentialsNonExpired;
@Column(name = "ENABLED")
private boolean enabled;
@OneToMany(fetch = FetchType.EAGER)
// @OneToMany
@JoinColumn(name="USERNAME")
private Collection<LoginUserGrantedAuthority> authorities;
}LoginUserGrantedAuthority
package com.khh.project.config.web.security.user;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "AUTHORITY")
@Data
public class LoginUserGrantedAuthority implements org.springframework.security.core.GrantedAuthority {
@Id
@Column(name = "USERNAME")
private String username;
@Id
@Column(name = "AUTHORITY")
private String authority = null;
public LoginUserGrantedAuthority() {
}
public LoginUserGrantedAuthority(String authority) {
this.authority = authority;
}
}SecurityController
package com.khh.project.web.security;
import com.khh.project.config.web.WebSecurityConfigurerAdapter;
import groovy.util.logging.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
@RequestMapping(WebSecurityConfigurerAdapter.SECURITY_PATH)
public class SecurityController {
@RequestMapping({"","/"})
String index(HttpServletRequest request, HttpServletResponse response, Model model) {
return "security/login";
}
//수동로그아웃시킬때 사용하세요
public void logout(HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
CookieClearingLogoutHandler cookieClearingLogoutHandler = new CookieClearingLogoutHandler(new String[]{"remember-me","JSESSIONID",WebSecurityConfigurerAdapter.REMEMBER_ME_COOKE_NAME});
SecurityContextLogoutHandler securityContextLogoutHandler = new SecurityContextLogoutHandler();
cookieClearingLogoutHandler.logout(request, response, (Authentication)null);
securityContextLogoutHandler.logout(request, response, (Authentication)null);
request.getSession().invalidate();
}
}
}@ControllerAdvice
- error Controller
@org.springframework.web.bind.annotation.ControllerAdvice(Application.BASE_PACKAGES)
@Slf4j
public class ControllerAdvice {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseBody
ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, EntityNotFoundException exception){
log(request,response,exception);
//log.trace(exception.getMessage(),exception);
ModelAndView mav = new ModelAndView();
response.setHeader("x-status", "Exception");
mav.addObject("throwable", exception);
mav.setViewName("error/default");
return mav;
}
@ExceptionHandler({UsernameNotFoundException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, UsernameNotFoundException exception){
log(request,response,exception);
//log.trace(exception.getMessage(),exception);
ModelAndView mav = new ModelAndView();
response.setHeader("x-status", "Exception");
mav.addObject("throwable", exception);
mav.setViewName("error/default");
return mav;
}
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable exception){
log(request,response,exception);
//log.trace(exception.getMessage(),exception);
ModelAndView mav = new ModelAndView();
response.setHeader("x-status", "Exception");
mav.addObject("throwable", exception);
mav.setViewName("error/default");
return mav;
}
private void log(HttpServletRequest request, HttpServletResponse response, Throwable ex) {
Throwable tex = StackTraceUtil.getLastCause(ex);
StackTraceElement[] stacks = tex.getStackTrace();
StringBuffer logInfo = new StringBuffer();
logInfo.append(ex.getClass().getName());
logInfo.append("|").append(request.getRequestURI());//API CODE(URL)
logInfo.append("|").append(RequestUtil.getRemoteAddr(request)); // 접속IP
logInfo.append("|").append(null==ex.getMessage()?"":ex.getMessage().replace("\n","").replace("\r", "")); // 처리 담당자 ID
logInfo.append("|").append(Stream.of(stacks).map(at->at.toString()).collect(Collectors.joining("<<")));
log.error(logInfo.toString().replace("\n", "").replace("\r",""));
}
}schduler
@Component
@Slf4j
public class Scheduler {
// 매일 5시 30분 0초에 실행한다.
@Scheduled(cron = "0 30 5 * * *")
public void aJob() {
// 실행될 로직
}
// 매월 1일 0시 0분 0초에 실행한다.
@Scheduled(cron = "0 0 0 1 * *")
public void anotherJob() {
// 실행될 로직
}
@Scheduled(cron = "0/10 * * * * ?")
public void anotherJob2() {
log.debug(new Date().toString());
}
}ErrorController
@Controller
@Slf4j
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {
public static final String ERROR_PATH = "/error";
public static final String ERROR_DEFAULT = ERROR_PATH + "/default";
public static final String ERROR_401 = ERROR_PATH + "/401";
public static final String ERROR_403 = ERROR_PATH + "/403";
public static final String ERROR_404 = ERROR_PATH + "/404";
public static final String ERROR_500 = ERROR_PATH + "/500";
@RequestMapping(value = ERROR_DEFAULT, method = GET)
public String defaultError() {
return "error/default";
}
@RequestMapping(value = ERROR_401, method = GET)
public String error401() {
return "error/401";
}
@RequestMapping(value = ERROR_403, method = GET)
public String error403() {
return "error/403";
}
@RequestMapping(value = ERROR_404, method = GET)
public String error404() {
return "error/404";
}
@RequestMapping(value = ERROR_500, method = GET)
public String error500() {
return "error/500";
}
@Override
public String getErrorPath() {
return ERROR_DEFAULT;
}
}hibernate, springJPA
package com.khh.project.web.admin;
import com.khh.project.web.admin.domain.Authority;
import com.khh.project.web.admin.domain.User;
import com.khh.project.web.admin.repository.UserRepository;
import com.omnicns.java.db.hibernate.Hibernater;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/admin")
@Transactional
@javax.transaction.Transactional(rollbackOn = { Exception.class })
@Slf4j
public class AdminController {
@Autowired
public SessionFactory sessionFactory;
@Autowired
public UserRepository userRepository;
@RequestMapping({"","/"})
@ResponseBody
String home() {
return "admin main";
}
@RequestMapping("/save")
@ResponseBody
String save() {
Session currentSession = sessionFactory.getCurrentSession();
currentSession.beginTransaction();
User u = new User();
u.setUsername("zzz");
u.setPassword("zzz");
Authority a = new Authority();
a.setUsername("zzz");
a.setAuthority("vvv");
List l= new ArrayList<>();
l.add(a);
u.setAuthorities(l);
currentSession.save(u);
currentSession.flush();
currentSession.clear();
currentSession.getTransaction().commit();
return "ok";
}
@RequestMapping("/list")
@ResponseBody
List<User> list() {
Session currentSession = sessionFactory.getCurrentSession();
// currentSession.beginTransaction();
Criteria crit = currentSession.createCriteria(User.class);
// crit.setFetchMode("authorities", FetchMode.JOIN); //강제로 지연로딩 사용안함
//lazy로되어있다면 아래처럼 DISTINCT_ROOT_ENTITY를하던가 트랜젝션을 닫지말고 그냥 흘려보네 그냥 지연로딩을 사용해도된다. 지연로딩 사용하면 각각 쿼리날라간다
////crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);//조인 쿼리가 날라간다
//위처럼할수도 있지만 엔티티에도 넣어도된다 @Fetch(FetchMode.SELECT) 이것도 각각 날라간다
List<User> list = crit.list();
// currentSession.flush();
// currentSession.clear();
// currentSession.getTransaction().commit();
// List<User> list = (List<User>) crit.uniqueResult();
// currentSession.getTransaction().commit();
// currentSession.close();
return list;
}
@RequestMapping("/list2")
@ResponseBody
List<User> list2() {
List<User> list = userRepository.findAll();
return list;
}
@RequestMapping("/list3")
@ResponseBody
User list3() {
User list = userRepository.findByUsername("khh");
return list;
}
}Application
package com.khh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableAutoConfiguration
@SpringBootApplication(scanBasePackages = {Application.BASE_PACKAGES})
@EnableTransactionManagement
public class Application {
public static final String BASE_PACKAGES = "com.khh.project";
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}spring boot로 프로젝트 시작하기전에 참고하셔서 사용하셨으면 좋겠습니다.
먼저 삽질한거 공유합니다. 배워서 남주자~
ps: libqa 참고 하였습니다. 퀵형~ 고마워욤~
libqa github : https://github.com/visualkhh/libqa
springboot template github : https://github.com/visualkhh/lib-spring/tree/master/boot/default_template
visualkhh@gmail.com 감사합니다.
'IT > spring' 카테고리의 다른 글
spring boot swagger setting (0) 2021.02.05 spring boot oaauth2.0 (링크 정리 공유) (0) 2021.02.04 spring boot 에서 JOOQ 사용시, 구동 1분이상 느려짐 현상 (버그) (0) 2021.02.04 spring에서 FCM (firebase cloud messaging) push 보내기. (11) 2021.02.04 JWT Token을 spring security에서 손쉽게 검증하기 위한 방법 (0) 2021.01.26