Spring Boot 빌드 파일을 이미지로 만들어 컨테이너에 올리기.

IT 지식/Docker|2018. 7. 1. 21:50

스프링 부트 애플리케이션을 Docker image로 빌드해서 컨테이너에 올리는 작업을 진행해 보겠다.

 

필요사항

  • JDK 1.8 later
  • Maven 3.2 이상
  • STS
  • Docker

 

Pom.xml 수정을 먼저 진행해야한다.

  • wedul 이라는 이름의 jar 파일이 생성된다.
  • docker에서 실행하기 위한 Maven 설정이 들어있는 jar file이 만들어 진다.
  • 만약 image prefix 값을 별도로 지정하지 않으면 artifact id가 명시된다.
<properties>
   <docker.image.prefix>wedul</docker.image.prefix>
</properties>
<build>
    <plugins>
        <plugin>
            <groupId>com.spotify</groupId>
            <artifactId>dockerfile-maven-plugin</artifactId>
            <version>1.3.6</version>
            <configuration>
                <repository>${docker.image.prefix}/${project.artifactId}</repository>
                <buildArgs>
                    <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                </buildArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

 

그리고 프로젝트 Root에 Dockerfile을 위치시켜야 한다. 

FROM java:8
VOLUME /tmp
ADD /target/wedulpos-0.0.1-SNAPSHOT.jar wedulpos.jar
ENTRYPOINT ["java","-jar","wedulpos.jar"]

DockerFile 옵션 설명 (출처 : https://www.callicoder.com/spring-boot-docker-example/)

VOLUME 

볼륨은 호스트 OS에서 컨테이너에 의해 생성 된 데이터를 유지하고 호스트 OS에서 컨테이너로 디렉토리를 공유하는 메커니즘입니다.

VOLUME 명령은 컨테이너에 지정된 경로로 마운트 포인트를 작성합니다. 컨테이너를 실행할 때 지정된 마운트 지점이 매핑 될 Hot OS의 디렉토리를 지정할 수 있습니다. 그런 다음, 컨테이너가 마운트 된 경로에 쓰는 것이 호스트 OS의 매핑 된 디렉토리에 기록됩니다. 볼륨의 가장 일반적인 사용 사례 중 하나는 컨테이너에 의해 생성 된 로그 파일을 호스트 OS에 저장하는 것입니다.

예를 들어, 응용 프로그램이 로그 파일을 /var/log/app.log 위치에 기록한다고 가정 해 봅시다. Dockerfile에 / var / log 경로로 VOLUME을 마운트 한 다음 컨테이너를 실행하는 동안이 마운트 지점이 매핑 될 호스트 OS의 디렉토리를 지정할 수 있습니다. 그런 다음 호스트 OS의 매핑 된 디렉토리에서 로그에 액세스 할 수 있습니다.

위의 Dockerfile에서 / tmp 경로를 사용하여 마운트 지점을 만들었습니다. 이것이 스프링 부트 응용 프로그램이 Tomcat에 대한 작업 디렉토리를 기본적으로 만드는 위치이기 때문입니다. 이 스프링 부트 응용 프로그램에는 바람둥이 디렉토리에 관심이 있기 때문에 필수는 아니지만. 그러나 Tomcat 액세스 로그와 같은 항목을 저장하려는 경우 VOLUMES는 매우 유용합니다.

 

ADD

ADD 명령은 새 파일과 디렉토리를 고정 이미지에 복사하는 데 사용됩니다.

 

ENTRYPOINT

응용 프로그램이 컨테이너 내부에서 실행되는 방법을 구성하는 곳입니다. 

 

그렇게 설정을 마치고 명령어를 통해 docker에서 사용할 수 있는 이미지 파일로 빌드를 진행한다.

./mvnw install dockerfile:build

설정을 정상적으로 하고 빌드가 진행이 완료되면 BUILD SUCCESS가 보일 것이다.

Docker image ls 명령어를 통해 지금 설치가 완료된 이미지 파일도 확인할 수 있다.

 

 

댓글()

Spring boot maven 빌드 후 jar 파일 실행 시 Mybatis type Alias 에러 수정

web/Spring|2018. 7. 1. 21:26

Spring Boot에서 Maven으로 빌드 후 생성된 jar 파일을 java -jar wedulpos.jar로 실행시키려 하였으나, 다음과 같은 오류가 발생하였다.


에러내용

[ERROR] [SpringApplication.java:842] Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authProvider': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'UserService': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper' defined in URL [jar:file:/wedulpos.jar!/BOOT-INF/classes!/com/wedul/wedulpos/user/dao/UserMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/wedul/common/config/RootApplicationContextConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:587)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:373)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1344)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:578)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)

at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)

at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)

at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:327)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255)

at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243)

at com.wedul.wedulpos.WedulposApplication.main(WedulposApplication.java:12)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)

at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)

at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)

at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'UserService': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper' defined in URL [jar:file:/wedulpos.jar!/BOOT-INF/classes!/com/wedul/wedulpos/user/dao/UserMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/wedul/common/config/RootApplicationContextConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:587)

at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:373)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1344)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:578)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)

... 27 common frames omitted

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userMapper' defined in URL [jar:file:/wedulpos.jar!/BOOT-INF/classes!/com/wedul/wedulpos/user/dao/UserMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/wedul/common/config/RootApplicationContextConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1439)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1326)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:578)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)

at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)

... 40 common frames omitted

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/wedul/common/config/RootApplicationContextConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:587)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1250)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1099)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:541)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1424)

... 51 common frames omitted

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)

at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:579)

... 63 common frames omitted

Caused by: org.springframework.core.NestedIOException: Failed to parse mapping resource: 'class path resource [mapper/user/UserMapper.xml]'; nested exception is org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:523)

at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:380)

at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:547)

at com.wedul.common.config.RootApplicationContextConfig.sqlSessionFactory(RootApplicationContextConfig.java:85)

at com.wedul.common.config.RootApplicationContextConfig$$EnhancerBySpringCGLIB$$66b8a7c7.CGLIB$sqlSessionFactory$1(<generated>)

at com.wedul.common.config.RootApplicationContextConfig$$EnhancerBySpringCGLIB$$66b8a7c7$$FastClassBySpringCGLIB$$93beeadf.invoke(<generated>)

at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)

at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)

at com.wedul.common.config.RootApplicationContextConfig$$EnhancerBySpringCGLIB$$66b8a7c7.sqlSessionFactory(<generated>)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)

... 64 common frames omitted

Caused by: org.apache.ibatis.builder.BuilderException: Error parsing Mapper XML. The XML location is 'class path resource [mapper/user/UserMapper.xml]'. Cause: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:120)

at org.apache.ibatis.builder.xml.XMLMapperBuilder.parse(XMLMapperBuilder.java:92)

at org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:521)

... 77 common frames omitted

Caused by: org.apache.ibatis.builder.BuilderException: Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.apache.ibatis.builder.BaseBuilder.resolveClass(BaseBuilder.java:118)

at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:68)

at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:135)

at org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext(XMLMapperBuilder.java:128)

at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:118)

... 79 common frames omitted

Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias 'UserDto'.  Cause: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.apache.ibatis.type.TypeAliasRegistry.resolveAlias(TypeAliasRegistry.java:120)

at org.apache.ibatis.builder.BaseBuilder.resolveAlias(BaseBuilder.java:149)

at org.apache.ibatis.builder.BaseBuilder.resolveClass(BaseBuilder.java:116)

... 83 common frames omitted

Caused by: java.lang.ClassNotFoundException: Cannot find class: UserDto

at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:200)

at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:89)

at org.apache.ibatis.io.Resources.classForName(Resources.java:261)

at org.apache.ibatis.type.TypeAliasRegistry.resolveAlias(TypeAliasRegistry.java:116)

… 85 common frames omitted


그래서 이를 해결하기 위해 구글링 하던 도중에 SqlSessionBean 클래스에 VFS를 등록해주지 않아서 발생한 문제라는 글을 발견하였다.

생각보다 쉽게 해결되었는데 이는 Mybatis에 버그인거 같다.


VFS는 Virtual File System이라는데 아직 잘 모르겠다. 


참고 URL

https://github.com/mybatis/spring-boot-starter/issues/38

https://github.com/mybatis/spring-boot-starter/issues/38

댓글()

Spring boot에서 Spring security를 사용하여 로그인 하기

web/Spring|2018. 5. 27. 19:44

스프링 부트를 공부하면서 스프링에서 제공하는 spring security를 통해 로그인 기능을 편리하게 구현할 수 있다는 것을 알았다.

기존에 회사에서는 interceptor 기능을 통해서 로그인 기능을 만들어서 사용했다. 둘다 좋은 방식이지만 spring security를 사용하면 csrf 공격도 막을 수 있어서 한번 사용해봐도 좋을 것 같다.

1. 라이브러리 pom.xml



1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
cs




2. SpringSecurityAdaptor


-> 스프링 시큐리티에 필요한 내용을 정의하는 configuration을 생성해야한다.
-> WebSecurityConfigurerAdapter 클래스를 상속받아서 configure 메서드를 재정의 해야한다.
-> @EnableWebSecurity, @EnableGlobalAuthentication와 같은 애노테이션을 사용하여 스프링 시큐리티 사용을 정의 해야 한다.
-> public void configure(WebSecurity web) throws Exception 메서드를 재정의하여 로그인 상관 없이 허용을 해줘야할 리소스 위치를 정의한다.
-> protected void configure(HttpSecurity http) throws Exception  메소드를 재정의하여 로그인 URL, 권한분리, logout URL 등등을 설정할 수 있다. (자세한 설명은 메서드에 주석으로 확인)




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.wedul.common.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 
import com.wedul.wedulpos.user.serviceImpl.AuthProvider;
 
/**
 * Security Configuration
 * 
 * @author wedul
 *
 */
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication
@ComponentScan(basePackages = {"com.wedul.*"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    AuthProvider authProvider;
    
    @Autowired
    AuthFailureHandler authFailureHandler;
 
    @Autowired
    AuthSuccessHandler authSuccessHandler;
 
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 허용되어야 할 경로들
        web.ignoring().antMatchers("/resources/**"
                                   "/dist/**"
                                   "/weather"
                                   "/user/password/find",
                                   "/user/join",
                                   "/user/email",
                                   "/user/send/temppw",
                                   "/findpw"
                                   "/user/findpw",
                                   "/user/cert/check",
                                   "/join"
                                   "/getLanguage/**",
                                   "/getMessage"); // #3
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
 
        // 로그인 설정
        http.authorizeRequests()
            // ROLE_USER, ROLE_ADMIN으로 권한 분리 유알엘 정의
            .antMatchers("/""/user/login""/error**").permitAll()
            .antMatchers("/**").access("ROLE_USER")
            .antMatchers("/**").access("ROLE_ADMIN")
            .antMatchers("/admin/**").access("ROLE_ADMIN")
            .antMatchers("/**").authenticated()
        .and()
            // 로그인 페이지 및 성공 url, handler 그리고 로그인 시 사용되는 id, password 파라미터 정의
            .formLogin()
            .loginPage("/user/login")
            .defaultSuccessUrl("/")
            .failureHandler(authFailureHandler)
            .successHandler(authSuccessHandler)
            .usernameParameter("id")
            .passwordParameter("password")
        .and()    
            // 로그아웃 관련 설정
            .logout().logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
            .logoutSuccessUrl("/")
            .invalidateHttpSession(true)
        .and()
            // csrf 사용유무 설정
            // csrf 설정을 사용하면 모든 request에 csrf 값을 함께 전달해야한다.
            .csrf()
        .and()
            // 로그인 프로세스가 진행될 provider
            .authenticationProvider(authProvider);
    }
 
}
cs



3. 로그인 검증을 진행하는 AuthProvider
-> AuthenticationProvider 인터페이스를 구현



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.wedulpos.user.serviceImpl;
 
import java.util.ArrayList;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
 
import com.wedul.common.util.Constant;
import com.wedul.common.util.HashUtil;
import com.wedul.wedulpos.user.dto.MyAuthenticaion;
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
/**
 * 인증 프로바이더
 * 로그인시 사용자가 입력한 아이디와 비밀번호를 확인하고 해당 권한을 주는 클래스
 * 
 * @author wedul
 *
 */
@Component("authProvider")
public class AuthProvider implements AuthenticationProvider  {
    
    @Autowired
    UserService userService;
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String id = authentication.getName();
        String password = HashUtil.sha256(authentication.getCredentials().toString());
        
        UserDto user = userService.selectUser(new UserDto(id));
        
        // email에 맞는 user가 없거나 비밀번호가 맞지 않는 경우.
        if (null == user || !user.getPassword().equals(password)) {
            return null;
        }
        
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        
        // 로그인한 계정에게 권한 부여
        if (user.isIsadmin()) {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_ADMIN.toString()));
        } else {
            grantedAuthorityList.add(new SimpleGrantedAuthority(Constant.ROLE_TYPE.ROLE_USER.toString()));
        }
 
        // 로그인 성공시 로그인 사용자 정보 반환
        return new MyAuthenticaion(id, password, grantedAuthorityList, user);
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
 
}
cs




4. 로그인 성공, 실패 후 진행되는 handler 클래스
-> 로그인 성공 그리고 실패 후 진행될 클래스 정의



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.wedul.common.config;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 실패 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 실패 시 response를 json 형태로 결과값 전달
        response.getWriter().print("{\"success\": false}");
        response.getWriter().flush();
    }
}
 
 
package com.wedul.common.config;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
 
/**
 * 로그인 성공 핸들러
 * 
 * @author wedul
 *
 */
@Component
public class AuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
 
        response.setStatus(HttpServletResponse.SC_OK);
        // 성공 시 response를 json형태로 반환
        response.getWriter().print("{\"success\": true}");
        response.getWriter().flush();
    }
 
}
cs




스프링 시큐리티를 처음 적용해 볼때는 어려움도 많았지만 적용하니 엄청 간단하게 사용할 수 있어서서 좋았다. 적용된 소스코드에 대한 정확한 정보는 https://github.com/weduls/wedulpos_boot 여기서 확인 할 수 있다.



댓글()
  1. 지나가는사람 2018.12.20 11:01 댓글주소  수정/삭제  댓글쓰기

    MyAuthenticaion 클래스랑 . userService 클래스랑 ,Constant.ROLE_TYPE.ROLE_ADMIN 이부분 의 소스 UserDTO부분좀 요청 드려도될까요 ??

  2. Favicon of https://daesoop.tistory.com BlogIcon 김대숲 2019.06.17 13:32 신고 댓글주소  수정/삭제  댓글쓰기

    글 너무 잘 읽었습니다.
    읽던 도중 깃헙 확인 중에 core부분이 gitignore 되어 있는것 같은데 혹시 저도 위에 분과 같이 소스좀 요청드려도 괜찮을까요?

    • Favicon of https://wedul.site BlogIcon 위들 wedul 2019.06.17 20:57 신고 댓글주소  수정/삭제

      안녕하세요.
      우선 도움이 되었다니 감사합니다.

      https://github.com/weduls/wedulpos_boot/blob/master/core/src/main/java/com/wedul/wedulpos/core/user/dto/MyAuthenticaion.java

      필요하신 코드가 어떤 코드인지 모르겠으나, 다 있는것 같아요.

      혹시 필요한 별도 클래스가 있으시다면 찾아드리겠습니다.

  3. 지나가는사람2 2019.10.16 20:45 댓글주소  수정/삭제  댓글쓰기

    pom.xml이 pom.sml로 오타 난 것 아닌가요?

  4. Favicon of https://requiem-blog.tistory.com BlogIcon Requiem0615 2019.11.06 12:24 신고 댓글주소  수정/삭제  댓글쓰기

    MyAuthenticaion 클래스 어디에있나요? 깃허브 확인해도 찾을 수가 없네요 ...

    • Favicon of https://wedul.site BlogIcon 위들 wedul 2019.11.06 16:21 신고 댓글주소  수정/삭제

      안녕하세요. 위에 어떤분도 말씀하셔서 댓글로 안내드렸습니다.

      이 위치에 있습니다.
      https://github.com/weduls/wedulpos_boot/blob/master/core/src/main/java/com/wedul/wedulpos/core/user/dto/MyAuthenticaion.java

      감사합니다.

  5. 주니어 2020.03.27 15:46 댓글주소  수정/삭제  댓글쓰기

    안녕하세요 시간이 많이지나서 git repository가 남아있는지 모르겠지만.. 이름이 혹시 바뀐건가요 ㅠㅠ

  6. 2020.06.25 01:06 댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

spring boot에 https 접속 적용하기

web/Spring|2018. 5. 27. 19:40

구글에서 http에 대한 모든 접속을 보안 경고를 표시하게 되면서, 모든 접속에 https 접속이 필수가 되었다.

스프링 부트를 접해보면서 스프링에서는 조금 과정이 귀찮았던 https 접속을 적용하는게 얼마나 편해졌는지 알아보았다.


1. application.properties 설정
#ssl 설정
server.port=13443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=test
server.ssl.key-password=test

2. keystore 만들기
-> ssl을 적용하려면 자가 서명 키스토어가 필요하다. 만약 인증기관에서 인증서를 발급받은 경우 그냥 그 파일을 사용하면 된다.







위의 명령어를 사용하여 src/main/resources에 키스토어를 만든다. 
비밀번호는 application.properties에 설정한 내용대로 해야한다.


3. 결과화면






댓글()

spring boot에서 jsp사용하기

web/Spring|2018. 5. 27. 19:35

spring framework만 사용하다가 요근래 대다수에 회사에서 spring-boot를 사용하는 것을 확인하고 spring-boot에 대해 공부를 해보았다.

확실히 spring-boot를 사용하면 설정이 엄청 간단해서 개발자가 설정에 집중하는게 아니라 비즈니스 로직에만 딱 고민할 수 있도록 되어있어서 진짜 편한것 같다.

그래서 기존에 내가 하고 있는 wedulpos 프로젝트를 spring-boot로 마이그레이션 하는 작업을 거쳤는데 그 사이에 어려운 부분이 있었다.

스프링에서는 jsp를 사용하는 것이 어렵지 않았으나, 기본적으로 spring-boot에서는 jsp 사용이 기본이 아니다.

그래서 설정을 위해서는 몇가지 변경을 해주어야한다.



1. pom설정
- jstl 사용을 위한 라이브러리 추가


1
2
3
4
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
</dependency>
cs



- 기본적으로 spring-boot에서 제공하는 톰캣에서는 jsp를 실행시킬 수 없기에 별도의 라이브러리 추가필요


1
2
3
4
5
<dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
  </dependency>
cs





2. application.properties 값 수정

- jsp의 위치의 prefix 값과 suffix 설정 추가
spring.mvc.view.prefix=/WEB-INF/views/**/
spring.mvc.view.suffix=.jsp








이렇게 설정해주니 잘 된다 굿굿!!



댓글()

spring boot 재시작 없이 frontend(html, js..) 변경내용 사용하기

web/Spring|2018. 5. 27. 19:31

spring boot를 공부하면서 html요소가 자동으로 변경되지 않아서.. 
계속 껏다켜야해서 너무 불편했다.

그래서 구글링 하던중 방법을 알게되어 적어본다.

1. pom.xml 추가


1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
cs


2. debug 모드로 실행

3. eclipse 설정 중 Build Automatically 설정 여부 확인 (기본으로 사용으로 설정되어 있음)


댓글()