728x90
300x250
[Spring Framework] 33. AOP(Aspect-Oriented-Programming) - XML 방식


XML 기반으로 작성된 AOP 구현 방법에 대해서 소개하려고 한다.

AOP의 관점에 대한 5가지 방식을 모두 적용해보았다.


[태스트 환경]

* IDE: Eclipse 2020-06

* Spring Framework 4.2.4.RELEASE
* Aspectjweaver 1.6.10
* Spring-aop 4.2.4.RELEASE
* JUnit 5




1. 프로젝트 구성도


프로젝트 구성도이다.



그림 1, 그림 2. 프로젝트 구성도




2. 프로젝트 생성


Spring Legacy Project로 프로젝트를 생성한다.

참고로 Spring MVC Project로 선택하고 생성해야 한다.



3. Build Path, Java Compiler, Project Factes 버전 맞춰주기


* Build Path: JRE 버전을 1.8로 변경해준다.
               - Add Library로 JUnit 5를 등록해준다.

* Java Compiler: Compiler compliance level - 1.8로 변경해준다.

* Project Factes: Java 버전을 1.8로 변경해준다.




4. POM.xml 설정하기


(중략)
 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>

~~~

(중략)
    <!-- AspectJ -->
    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjrt</artifactId>
     <version>${org.aspectj-version}</version>
    </dependency>
   
    <!-- AspectJWeaver -->
    <dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>${org.aspectj-version}</version>
    </dependency>
   
    <!-- Spring AOP 추가(Java) --> 
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${org.springframework-version}</version>
    </dependency> 
(중략)






5. applicationContext.xml (src/main/resources/applicationContext.xml)


XML 방식의 핵심이다. 이 세팅이 잘못되면 불러왔을 때 오류가 발생한다.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <!-- 방법1 - JAVA -->
 <!-- JAVA 방식(어노테이션)의 AOP - AspectJ Weaver -->
 <!--  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  어노테이션 작업시 필수 선언해야 함. -->
 
 <!--  XML 방식의 AOP - AsepectJ Weaver -->
 <bean id="resultAOP" class="com.website.example.test.ResultAOP"></bean>
 <bean id="txAdviceXML" class="com.website.example.aop.LogAdvisorXML"></bean>

 <aop:config>
  <!-- txAdviceXML 하나 영역임 -->
  <aop:aspect ref='txAdviceXML'>

   <!-- 진입영역 -->
   <!-- 1개만 가능함 -->
   <!-- <aop:pointcut id="point1" expression="execution(* com.website.example.test..*())"/> -->
   <aop:pointcut id="point1" expression="execution(* com.website.example.test.ResultAOP..*())"/>           
          
          <!-- before -->
          <aop:before method="beforeAdvice" pointcut-ref="point1"/>
          <!-- after -->
          <aop:after method="afterAdvice" pointcut-ref="point1"/>
          
          <!-- around(메서드 자체를 가로채기) -->
          <aop:around method="aroundAdvice" pointcut-ref="point1"/>
          
          <!-- afterThrowing -->
          <aop:after-throwing method="afterThrowing" pointcut-ref="point1"/>
          
          <!-- afterReturning -->
          <aop:after-returning method="afterReturning" pointcut-ref="point1"/>
          
        </aop:aspect>
       
        <!-- 2번째 선언자 -->
 </aop:config>
 
</beans>


파일명: applicationContext.xml


[첨부(Attachments)]

applicationContext.zip



6. ResultAOP.java - com.website.example.text


핵심 로직에 해당되는 부분이다. 이 부분을 비즈니스 로직이라고 표현하기도 한다.


package com.website.example.test;


public class ResultAOP {


     public void method1() {
           System.out.println("[중간]:");
  
           // afterThrowing 유발 코드
           // int d = 2/0;
  
           System.out.println("결과: 메서드");
     }
 
}


파일명: ResultAOP.java


[첨부(Attachments)]

ResultAOP.zip


applicationContext.zip



7. LogAdvisor.java - com.website.example.aop


인터페이스 정의이다.

package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public interface LogAdvisor {

        public void beforeAdvice();
        public void afterAdvice();
        public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable;
        public void afterThrowing();
        public void afterReturning();
 
}


파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip




8. LogAdvisorXML.java - com.website.example.aop


package com.website.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;


public class LogAdvisorXML implements LogAdvisor{
  
       // around advice
       @Override
       public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
  
               System.out.println("2단계A?:");
               System.out.println("aroundMethod 호출 1");
  
               // 원래의 메소드를 호출한다.
               Object obj = pjp.proceed();

               System.out.println("2단계B?:");
               System.out.println("aroundMethod 호출 2");
  
               return obj;
       }


       // before advice
       @Override
       public void beforeAdvice() {
              System.out.println("1단계:");
              System.out.println("beforeMethod 호출");
  
       }


       // after
       @Override
       public void afterAdvice() {
              System.out.println("5단계:");
              System.out.println("afterMethod 호출");
  
       }


       // afterThrowing
      @Override
      public void afterThrowing() {
              System.out.println("4단계:");
              System.out.println("afterThrowing 호출");
      }

      // afterReturning
      @Override
      public void afterReturning() {
              System.out.println("3단계:");
              System.out.println("afterReturning 호출");
  
      }
 
}


파일명: LogAdvisorXML.java


[첨부(Attachments)]

LogAdvisorXML.zip


LogAdvisor.zip




9. TestMain.java - com.website.example.unit


package com.website.example.unit;


import org.junit.jupiter.api.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.website.example.test.ResultAOP;


public class TestMain {

      
       // 사용방법1
       @Test
       public void sample() {

                AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationContext.xml");
   
                ResultAOP rAOP = (ResultAOP) factory.getBean("resultAOP");

                rAOP.method1();
  
                factory.close();
       }
 

       // 사용방법2
       @Test
       public void sample2() {

               ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
               ResultAOP rAOP = ctx.getBean("resultAOP", ResultAOP.class);
               //rAOP.method1();
  
               ctx.close();
       }
 
}


파일명: TestMain.java


[첨부(Attachments)]

TestMain.zip




10. 동작 결과


구현한 코드의 동작 결과이다.



그림 3. 출력 결과 1



그림 4. 출력 결과 2




11. 맺음글(Conclusion)


직관적으로 AOP를 아주 쉽게 이해할 수 있도록 작성하였다.

반응형
728x90
300x250
[Spring Framework] 32. AOP(Aspect-Oriented-Programming) - Java 방식2(Context.xml)


Spring Framework에서 사용되는 AOP 구현방식을 소개하려고 한다.

AOP를 다루고자 하는 사람들은 조금 방법이 다양하게 있으니, 원하는 방식 하나 택해서 사용하면 될듯 싶다.


[배경 입문 지식]

1. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (1), 2020-10-04

- https://yyman.tistory.com/1447

2. [Spring-Framework] 30. AOP(Aspect-Oriented-Programming) 관점지향 프로그래밍 (Java셋팅) (2), 2020-10-04

- https://yyman.tistory.com/1448


셋팅 방법에서 다르다.

동일하게 생각하고 작성하면, 오류를 경험할 수 있다.


AOP를 다루는 건 큰 틀에서는 같은 일인데 불구하고, Service 어노테이션 정의부터 다소 셋팅이 달라져버릴 수가 있어서 신중하게 사용하는 것을 권장한다. 셋팅을 먼저 한 경우라면, 거기에 맞춰서 흐름을 타주는 방식으로 가야 하겠다.


XML, 순수한 Java 방식, Context.xml 및 Java 방식(어노테이션) 크게 3가지 방법으로 AOP를 설정할 수 있다.

이론적으로는 같으나 실질적인 구현에서는 호환이 안 되니 주의하여 사용할 것을 권한다.


3가지 방법 중 하나를 택해서 AOP 대상을 두었으면 그 방법을 따라가는 형태로 진행하는 것을 권장한다.

(구현 관점에서보면, 표준화가 진행이 덜된 느낌이 있다.) 시간이 나면, 소개해주겠다.


[태스트 환경]

* IDE: Eclipse 2020-06 (Spring Tools 3 Addon.....)
* Spring Framework Version: 4.2.4.RELEASE

* JUnit 5(태스트 실행)




1. 프로젝트 생성


-> Spring Legacy Project -> Spring MVC Project로 진행하였음.


프로젝트 생성 및 셋팅 방법에 대해서는 "이전 글들"을 참고하면 된다. (동일하게 진행함.)




2. pom.xml


매우 중요한 부분이다. 노란색 불은 들어오는데 빌드하면 오류가 생긴 경우도 있을 수도 있다.

셋팅을 잘해야 한다.


-> Spring Tool-Suites 4.7로 작업하면 14버전을 사용해도 무방하다. 다만, 이전 버전의 Eclipse 등에서 프로젝트 구성에서 오류가 생길 수도 있다.

지금 pom 셋팅은 공통적인 작업이다.


(중략)

 <properties>
  <java-version>1.8</java-version>
  <org.springframework-version>4.2.4.RELEASE</org.springframework-version>
  <org.aspectj-version>1.6.10</org.aspectj-version>
  <org.slf4j-version>1.6.6</org.slf4j-version>
 </properties>

(중략)
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>${org.aspectj-version}</version>
  </dependency> 
  
  <!-- AspectJWeaver -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.8.8</version>
  </dependency>
  
  <!-- Spring AOP 추가(Java) -->  
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${org.springframework-version}</version>
  </dependency>

(중략)




3. 미리 살펴보는 프로젝트 구성도


이번 작업에서는 JUnit5로 TDD 방식으로 구동한 프로젝트이다.



그림 1. 프로젝트 구성도

작업할 프로젝트 구성이다.




4. 프로젝트 환경설정(Build Path, Java Compiler, Project Factes)


Build Path의 Libraries에 들어가서 버전을 1.8로 설정한다.

JUnit 5도 추가해준다.


참고로 JUnit 5은 자바 1.8버전에서부터 지원한다.



그림 2. 프로젝트 구성도


Build Path를 설정해준다. "Add Library"를 클릭해서 JUnit 5을 추가할 수 있다.




그림 3. 자바 컴파일러 환경설정


JDK를 잘 설치하였음에도 1.8버전 등으로 변경하였을 때 이클립스에서 오류로 간주해버리는 경우가 있다.

오류가 생기는 이유는 Project Factes 옵션을 조작할 때 발생한다.


JDK Compliance의 Compiler compliance level 버전을 맞춰주면 오류가 사라지는 것을 확인할 수 있다.



그림 4. Project Factes


Java의 버전을 1.8으로 변경한다.




5. applicationJavaAOP.xml (src/main/resources/applicationJavaAOP.xml)


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <aop:aspectj-autoproxy />
 <context:component-scan base-package="com.website.example" />
 <bean id="loggingAspect" class="com.website.example.aop.LogAdvisorJava" />
 
</beans>



AOP 스키마 정의는 servlet-context.xml에서 간단한 조작으로 추가하였다.


그림 5. aop 환경설정해주기 - 네임스페이스 정의


양식 틀은 /src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml에서 복사 붙여넣기하였다.


작성한 코드는 <aop>.....</bean> 이 정도이다.


* 파일명: applicationJavaAOP.xml


[첨부(Attachments)]

applicationJavaAOP.zip




6. ResultAOPJava.java (com.website.example.text)


package com.website.example.test;

import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;


@Service
public class ResultAOPJava {
 
     @Pointcut("execution(* com.website.example.test.*(..))")
      public void method1() {
              System.out.println("결과: 메서드");
      }
 
}


* 파일명: ResultAOPJava.java


[첨부(Attachments)]

ResultAOPJava.zip




7. LogAdvisor.java (com.website.example.aop)


관점에 대한 인터페이스이다.
객체의 다형성을 생각하면 좋을 것으로 보인다.


package com.website.example.aop;

import org.aspectj.lang.annotation.Aspect;


@Aspect
public interface LogAdvisor {

       public void beforeAdvice();
       public void afterAdvice();
       public void aroundAdvice();
 
}


* 파일명: LogAdvisor.java


[첨부(Attachments)]

LogAdvisor.zip

ResultAOPJava.zip




8. LogAdvisorJava.java (com.website.example.aop)


클래스명을 Impl로 해도 되었을 법한데 태스트용이니 생략하도록 하자.


package com.website.example.aop;


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;


// 어노테이션 방식
@Aspect
public class LogAdvisorJava implements LogAdvisor{


         @Before("execution(* com.website.example.test.ResultAOPJava.*(..))")
          public void beforeAdvice() {
                 System.out.println("전 단계");
         }

         @After("execution(* com.website.example.test.ResultAOPJava.*(..))")
         public void afterAdvice() {
                 System.out.println("후 단계");
         }


         public void aroundAdvice() {
   
          }
 
}


* 파일명: LogAdvisorJava.java


[첨부(Attachments)]

LogAdvisorJava.zip


LogAdvisor.zip

ResultAOPJava.zip



9. TestMain.java (com.website.example.test)


JUnit 태스트 도구로 실행 영역을 구현하였다.


package com.website.example.test;

import org.junit.Test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class TestMain {


     @Test
     public void test() {


              // 방법 1
             @SuppressWarnings("resource")
             AbstractApplicationContext factory = new GenericXmlApplicationContext("applicationJavaAOP.xml");
  
             ResultAOPJava cal = factory.getBean(ResultAOPJava.class);
             cal.method1();
  
     }
 
}


* 파일명: TestMain.java


[첨부(Attachments)]

TestMain.zip



10. 출력 결과


아래의 그림처럼 AOP가 잘 출력되는 것을 확인할 수 있다.



그림 6. AOP 화면 출력 결과


정상적으로 AOP 환경설정이 이뤄진 것을 확인할 수 있다.



* 참고자료(References)


1. Spring AOP – AspectJ Annotation Config Example - HowToDoInJava, https://howtodoinjava.com/spring-aop/spring-aop-aspectj-example-tutorial-using-annotation-config/, Accessed by 2020-10-09, Last Modified 2015-02.


-> 추천(25점): 구현에 대해서 많은 도움을 주었다.


2. 스프링 부트(Spring Boot) - AOP와 트랜잭션(Transaction) 설정하기 [개발을 시작해봐요!], https://congsong.tistory.com/25, Accessed by 2020-10-09, Last Modified 2020-04-28.

-> 추천(25점): 스프링 부트로 작성되었지만, AOP 개발 방법의 흐름을 살펴볼 수 있었음.

반응형
728x90
300x250

[Spring-Framework] Spring Framework 5.4, MyBatis 3.5 연동 방법 - 실험 결과


아마 이런 실험을 시도한 인터넷 글은 최초가 되지 않을까 싶다.

현재에 떠돌아 다니는 정보들을 충분한 시간을 가지고 실험한 결과이다.


필자가 구독자들에게 한 가지 약속한다면, 검증을 확실히 완료하고 작성한 글이다.

이 글을 어떻게 전달해야 할지 고민을 충분히 하고 작성하게 되었다.


@Autowired 방식과 @Beans 방식을 사용하는 유저가 있다면, 사용하지 않아야 한다.



1. 결과


엄격한 태스트를 거쳐서 완료한 결과이다. 개발 환경을 구축하는데 있어서 삽질을 하지 않았으면 하는 바람이다.



그림 1. 실험 결과


[첨부(Attachments)]

MyBatis-Complete-summary.zip




2. 5가지 방법 - 정리


정리가 완료되는데로 공개하도록 하겠다.


1. [Spring-F.] 25. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법1(web.xml-Beans)            // 결과: 미지원, 2020-10-02

   - https://yyman.tistory.com/1438

2. [Spring-F.] 26. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법2(SqlMap-XML-XML 맵핑)   // 결과: 지원, 2020-10-02

   - https://yyman.tistory.com/1439

3. [Spring-F.] 27. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법3(SqlMap-XML-Class)    // 결과: 지원, 2020-10-02

   - https://yyman.tistory.com/1440

4. [Spring-F.] 28. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법4(SqlMap Spring전용)   // 결과: 지원, 2020-10-02

   - https://yyman.tistory.com/1441

5. [Spring-F.] 29. MyBatis 3.5, Spring Framework 5.4, Oracle 19g - 방법5(SqlMap Spring전용)  // 결과: 지원, 2020-10-02

   - https://yyman.tistory.com/1442


= (기타: 표준 지원에 대한 4, 5번 소스코드도 주석으로 포함 처리함.)




3. 실험 조건


실험 조건은 다음과 같다.


* IDE: Spring Tool-Suites 4-4.7.2 (Releases)

* Library:

- Spring-Framework 5.2.9.RELEASE (2020-09-15)

javax.servlet-api 4.0.1 (2018-04-20)

   - https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api/4.0.1

- mybatis-spring (2020-06-05) // Spring Framework만 사용가능함.

   - https://mvnrepository.com/artifact/org.mybatis/mybatis-spring/2.0.5

- mybatis 3.5.5 (2020-06-04)

   - https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.5

- Spring JDBC 

   - https://mvnrepository.com/artifact/org.springframework/spring-jdbc/5.2.9.RELEASE

- ojdbc8 (ojdbc8)

- mariadb-java-client 2.7.0 (2020-09-24)

   - https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client/2.7.0

- mysql-connector-java 8.0.21 (2020-07-12)


* DB:

- Oracle Databases 19g




4. 실험 결과 - 동작 영상


입증하기 위해서 영상을 준비하였다.



영상 1. Spring Framework 5.4, Oracle 19g, Mybatis 3.5 연동하기



영상 2. Spring Framework 5.4, Oracle 19g, Mybatis 3.5 연동하기(실험 1 - 영상 [예])


이런 형태로 프로젝트를 구성한 것이다.



5. 공통 - 프로젝트 생성 방법


5가지 방법에 관한 프로젝트 생성에 관한 것이다.

1~5번의 셋팅은 Build Path, Project Factes, POM.xml은 동일하다.



그림 1. 프로젝트 생성하기(1)


File->New->Spring Legacy Project를 클릭한다.



그림 2. 프로젝트 생성하기(2)


Spring MVC Project를 선택한다.

Next를 클릭한다.



그림 3. 프로젝트 생성하기(3)


top-level package 경로를 입력한 후 Finish를 누른다.



5-1. Build-Path, Project Factes 설정하기


자바 버전을 변경해줄 것이다.

초기 Spring Legacy Project로 Spring MVC Project를 생성하면, 1.6 버전으로 셋팅되어 생성될 것이다.

이러한 것을 변경해줄 것이다.



그림 4. 프로젝트 속성 들어가기


프로젝트를 선택한 후 마우스 오른쪽 버튼을 클릭한다.

Properties를 클릭한다.




그림 5. Build-path 버전 바꾸기


Java-Build Path를 클릭한다.

JRE System Library을 선택한다.

Edit 클릭하고 변경하면 된다.

(JRE -> JavaSE 14로 변경할 것)

Apply를 누른다.



그림 6. Project - Factes 설정하기


Project Factes를 클릭한다.

Java 버전을 14로 변경해준다.


Apply를 누른다.



5-2. Pom.xml 설정하기


pom.xml 설정에 관한 것이다.



그림 7. pom.xml 설정하기(1)


<org.springframework-version>을 5.2.9.RELEASE로 변경하였다.



그림 8. pom.xml 설정하기(2)


javax.servlet-api 버전을 4.0.1로 변경하였다.




그림 9. pom.xml 설정하기(3)


mybatis-spring, mybatis를 추가하였다.




그림 10. pom.xml - 오라클이 설치된 경우에만 따라하기(1)


pom.xml을 마우스 오른쪽 버튼으로 클릭한다.

Maven->Add Dependency를 클릭한다.




그림 11. pom.xml - 오라클이 설치된 경우에만 따라하기(2)


oracle이라고 검색한다.

com.oracle.database.jdbc | ojdbc8을 선택한다.

OK를 누른다.




그림 12. pom.xml - mariadb, mysql-connector 추가하기


추가로 몇 개의 DB 드라이버(MariaDB, MySQL-Connector)를 부착시켰다. 


<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>

<artifactId>spbatis</artifactId>

<name>Spring-MVC-mybatis</name>

<packaging>war</packaging>

<version>1.0.0-BUILD-SNAPSHOT</version>

<properties>

<java-version>1.6</java-version>

<org.springframework-version>5.2.9.RELEASE</org.springframework-version>

<org.aspectj-version>1.6.10</org.aspectj-version>

<org.slf4j-version>1.6.6</org.slf4j-version>

</properties>

<dependencies>

<!-- Spring -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-context</artifactId>

<version>${org.springframework-version}</version>

<exclusions>

<!-- Exclude Commons Logging in favor of SLF4j -->

<exclusion>

<groupId>commons-logging</groupId>

<artifactId>commons-logging</artifactId>

</exclusion>

</exclusions>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${org.springframework-version}</version>

</dependency>

<!-- AspectJ -->

<dependency>

<groupId>org.aspectj</groupId>

<artifactId>aspectjrt</artifactId>

<version>${org.aspectj-version}</version>

</dependency>

<!-- Logging -->

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${org.slf4j-version}</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>jcl-over-slf4j</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${org.slf4j-version}</version>

<scope>runtime</scope>

</dependency>

<dependency>

<groupId>log4j</groupId>

<artifactId>log4j</artifactId>

<version>1.2.15</version>

<exclusions>

<exclusion>

<groupId>javax.mail</groupId>

<artifactId>mail</artifactId>

</exclusion>

<exclusion>

<groupId>javax.jms</groupId>

<artifactId>jms</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jdmk</groupId>

<artifactId>jmxtools</artifactId>

</exclusion>

<exclusion>

<groupId>com.sun.jmx</groupId>

<artifactId>jmxri</artifactId>

</exclusion>

</exclusions>

<scope>runtime</scope>

</dependency>


<!-- @Inject -->

<dependency>

<groupId>javax.inject</groupId>

<artifactId>javax.inject</artifactId>

<version>1</version>

</dependency>

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->

<dependency>

    <groupId>javax.servlet</groupId>

    <artifactId>javax.servlet-api</artifactId>

    <version>4.0.1</version>

    <scope>provided</scope>

</dependency>


<dependency>

<groupId>javax.servlet.jsp</groupId>

<artifactId>jsp-api</artifactId>

<version>2.1</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<version>1.2</version>

</dependency>

<!-- Test -->

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.7</version>

<scope>test</scope>

</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->

<dependency>

    <groupId>org.mybatis</groupId>

    <artifactId>mybatis-spring</artifactId>

    <version>2.0.5</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->

<dependency>

    <groupId>org.mybatis</groupId>

    <artifactId>mybatis</artifactId>

    <version>3.5.5</version>

</dependency>

<!-- 스프링에서 JDBC 를 사용하기 위한 라이브러리 입니다. -->

<dependency>

   <groupId>org.springframework</groupId>

   <artifactId>spring-jdbc</artifactId>

   <version>${org.springframework-version}</version>

</dependency>


<dependency>

<groupId>com.oracle.database.jdbc</groupId>

<artifactId>ojdbc8</artifactId>

<version>19.7.0.0</version>

</dependency>

<!-- https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client -->

<dependency>

    <groupId>org.mariadb.jdbc</groupId>

    <artifactId>mariadb-java-client</artifactId>

    <version>2.7.0</version>

</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->

<dependency>

    <groupId>mysql</groupId>

    <artifactId>mysql-connector-java</artifactId>

    <version>8.0.21</version>

</dependency>

</dependencies>

    <build>

        <plugins>

            <plugin>

                <artifactId>maven-eclipse-plugin</artifactId>

                <version>2.9</version>

                <configuration>

                    <additionalProjectnatures>

                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>

                    </additionalProjectnatures>

                    <additionalBuildcommands>

                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>

                    </additionalBuildcommands>

                    <downloadSources>true</downloadSources>

                    <downloadJavadocs>true</downloadJavadocs>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>2.5.1</version>

                <configuration>

                    <source>1.6</source>

                    <target>1.6</target>

                    <compilerArgument>-Xlint:all</compilerArgument>

                    <showWarnings>true</showWarnings>

                    <showDeprecation>true</showDeprecation>

                </configuration>

            </plugin>

            <plugin>

                <groupId>org.codehaus.mojo</groupId>

                <artifactId>exec-maven-plugin</artifactId>

                <version>1.2.1</version>

                <configuration>

                    <mainClass>org.test.int1.Main</mainClass>

                </configuration>

            </plugin>

        </plugins>

    </build>

</project>



파일명: pom.xml


[첨부(Attachments)]

pom.zip



6. DB - Table 설계


Spring Security에서 사용자 계정의 테이블 설계를 사용하였다.



그림 13. 테이블 모습


CREATE TABLE comp_users (

username VARCHAR(50) NOT NULL,

password VARCHAR(300) NOT NULL,

enabled INT NOT NULL,

PRIMARY KEY (username)

);


-- 계정

INSERT INTO comp_users (username, password, enabled) VALUES ('user', '$2a$10$x04djNV2e9rpcPPRyXoLk.rMm6iZe2/vYdzpqHQcLeNSYdt7kc30O', 1);

INSERT INTO comp_users (username, password, enabled) VALUES ('admin', '$2a$10$QUddY3O/6ZgkYCR6MFlv9.nqA501Fm0cc/ZxQHX5pwb1o0CYCTiIS', 1);


파일명: Comp_users.sql


[첨부(Attachments)]

comp_user.zip





6-1. Model - CompUsers.java


공통으로 1~5 프로젝트에서 사용되는 부분이다.


package com.example.spbatis.model;


public class CompUsers {

private String username;

private String password;

private int enabled;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public int getEnabled() {

return enabled;

}

public void setEnabled(int enabled) {

this.enabled = enabled;

}


}



파일명: CompUsers.java


[첨부(Attachments)]

CompUsers.zip




* 맺음글(Conclusion)


1~5개의 글을 추가로 살펴보면 되겠다.

반응형
728x90
300x250

[Spring-Framework] 23(번외). STS 4.4 - Spring Boot Starter - 배포(Jar, War방식)


이전의 글을 참고하여 진행하면 될 것 같다.

Spring Boot Starter의 배포 방식에는 크게 "Jar", "War"로 구성된다.

Spring Boot Starter의 Jar파일 배포는 maven Build를 통해서 이뤄진다.

내장 톰캣 웹서버까지 함께 배포해버리는 단점이 있다.

차이점이 Spring Legacy Project로 생성한 Spring Framework는 일반적인 배포에서는 간단하게 War 배포를 할 수 있다.


- IDE: Spring Tool-Suite 4-4.7.2 Releases (2020-06-29 기준 최신 버전) // 대략



1. Spring Boot Starter - 배포 방법 JAR


Jar 방식으로 배포하는 방법에 대해서 소개하겠다.



그림 1. Run에서 Maven Build하기


(작업하고 있는) Spring Boot 프로젝트를 선택한다.

Run 메뉴를 클릭한다.

Run As에서 5 Maven Build...를 클릭한다. (은근히 햇갈리게 되어 있음.)



그림 2. Run에서 Maven Build하기

Goals에 package

Profiles은 비워둔다.

Run 버튼을 누른다.




그림 3. 빌드 기다리는 모습(1)


시간이 걸린다. 다운로드 과정 등이 있다. 알아서 배포 작업이 잘 되고 있는지 태스트 작업도 하고 그렇다.



그림 4. 빌드 기다리는 모습(2)



그림 5. 빌드 완료된 모습(1)


BUILD SUCCESS라고 뜨면 잘 된 것이다. 빨강색 뜨면 안 좋은 것이다. (오류도 잡아야 하고 그렇다. ㅠㅠ)



그림 6. 빌드 완료된 모습(2)


console 대화창 내용을 클릭한다.

Ctrl + F를 누른다.

jar를 검색한다.


무슨 파일명으로 배포되었는지 알기 위해서이다.



그림 7. 빌드 완료된 모습(3)


프로젝트 창을 F5키로 새로고침한다.

target 폴더를 클릭해보면, 배포된 파일을 살펴볼 수 있다.



그림 8. 빌드 완료된 모습(4)


쉬워보여도 쉬운 과정이 아니다. 또 실제 서버에 빌드해서 태스트를 해야 한다.

또 안 되면, 다시 Eclipse 작업창에서 삽질을 해야 한다.



2. Spring Boot Starter - 배포 방법 War


war 배포 방법은 다소 어렵다. 코드 몇 줄이긴 하지만, 작성해줘야 한다.

코드 부분도 소개하도록 하겠다.



2-1. war 배포 - pom.xml


pom.xml 수정에 대한 사항이다.



그림 9. pom.xml 파일 수정(1) - 변경해야 할 내용



그림 10. pom.xml 파일 수정(2) - 변경해야 할 내용



그림 11. pom.xml 파일 수정(2) - 변경해야 할 내용


아래의 내용을 열어보면, 확인할 수 있다. (굵기 표시로 변경 작업을 한 내역을 표시하였음.)





2-2. War 배포 - SpringBootMvcDemoApplication.java


사소해보이지만, 코드 변경이 중요하다.

이거 안 해주면, pom.xml 설정 잘 되어 있어도 오류가 뜬다.



그림 12. pom.xml 파일 수정(2) - 변경해야 할 내용


package com.springmvc.home;


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.builder.SpringApplicationBuilder;

import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;


@SpringBootApplication

public class SpringBootMvcDemoApplication extends SpringBootServletInitializer {


public static void main(String[] args) {

//SpringApplication.run(SpringBootMvcDemoApplication.class, args);

SpringApplication app = new SpringApplication(SpringBootMvcDemoApplication.class);

app.run(args);

}


@Override

protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {

return builder.sources(SpringBootMvcDemoApplication.class);

}


}



파일명: SpringBootMvcDemoApplication.java


[첨부(Attachments)]

SpringBootMvcDemoApplication.zip





2-3. Run As로 Maven Build하기 - War 배포


지금부터 작업은 배포를 하는 것이다.



그림 13. Run의 Maven build 메뉴 모습


Run 메뉴를 클릭한다.

Run As의 Maven build를 클릭한다.




그림 14. Select Configuration


SpringBootMVCDemo (1) : package를 선택하고 OK를 누른다.




그림 15. 빌드가 완료된 모습


빌드하는 데 시간이 다소 소요된다.

기다리면 된다.


BUILD SUCCESS라고 녹색 대화 글이 나오면 잘 된 것이다.



2-4. 톰캣 서버에 올리기 - war 배포


웹 서버에 실제로 배포하는 방법에 대해서 소개하겠다.








그림 16. 빌드가 완료된 파일 (1)


demo-0.0.1-SNAPSHOT.war 파일을 선택한다.

복사한다. (Ctrl + C)




그림 17. 톰캣 서버의 webapps 폴더에 넣어주기 (2)


톰캣 서버 설치 경로의 webapps에 들어간다.

붙여넣기를 한다. (Ctrl + V)




그림 18. 톰캣 서버의 bin - startup 실행해주기


bin폴더에서 startup.bat을 더블 클릭한다.

서버를 실행해준다.




그림 19. 서버가 실행되고 나면, 압축해제가 된 모습


정상적으로 폴더가 생성되었다면, 배포가 잘 된 것이다.




그림 20. 서버 실행 모습


Tomcat 서버에 보면, 익숙한 모습으로 Spring Security 생성 암호가 있다.

Using generated security password:의 내용을 복사한다.


Username: User

Password: 복사한 내용의 비밀번호(암호화된 랜덤 형태의 암호이다)




그림 21. 로그인 후 배포된 모습


정상적으로 화면이 보인다면, 배포가 성공적으로 이뤄진 것이다.



3. 결론(Conclusion)


서버 배포 작업도 개발자라면 경험해봐야 할 중요한 부분이라고 생각된다.

다소 스프링 부트는 배포 부분에 있어서는 쉽지만 않다.


서버 배포에 대해서 살펴보았고, Jar파일로 배포하는 방법에 대해서도 소개하였다.



* 참고자료(References)


1. [스프링 부트] war 파일과 외부 톰캣을 이용한 서버 배포, https://steady-hello.tistory.com/53, Accessed by 2020-09-30, Last Modified 2019-10-30.

반응형
728x90
300x250

[Spring-Framework] 17. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (2)


이번에는 이전 글에 이어서 Spring-Security를 구현해보려고 한다.

조금 이번 글부터는 난이도가 있어지니깐 개발 전략을 잘 숙지해서 작업하면 좋겠다.


1. [Spring-Framework] 16. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (1), 2020-09-27

https://yyman.tistory.com/1422



14. 개발 전략


하나 만들면 다 되는 게 아니다. 계속 연속해서 복합적으로 수정작업을 해줘야 한다.

그래서 개발 작업이 힘이 든다. 쉽지만 않다.


그림 24. 개발 전략도


SecurityWebApplicationInitializer.java, SecurityConfig.java는 Spring Security 구현에 있어서 핵심이라고 해도 무방하다.

두 개를 잘 구현한다면, 셈플 로그인 페이지는 볼 수 있다.


문제는 자바 버전으로 구현했을 때 보안 토큰 절차가 xml방식에 비해서 매우 까다롭게 반응한다는 것이다.

그래서 간단한 코드로 태스트를 해보기도 전에 DB 설계를 할 수 밖에 없었다.


이유는 토큰 인증 때문에 그렇다.


SqlMapSessionFactory도 계속 반복해서 다양한 영역에서 재사용될 것이다.



15. config의 SecurityWebApplicationInitializer.java (필수 파일)


이 파일을 보면, 제일 황당한 생각이 들 수 밖에 없는 이유가 코드는 몇 줄 안 되는데, 없으면 동작이 안 된다는 것이다.


package com.springMVC.javaSecurity5.config;


import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;


public class SecurityWebApplicationInitializer 

                        extends AbstractSecurityWebApplicationInitializer {


}


파일명: SecurityWebApplicationInitializer.java


[첨부(Attachments)]

SecurityWebApplicationInitializer.zip



16. config의 SecurityConfig.java (필수 파일)


"SecurityConfig.java" 이 파일도 없으면 Spring Security with 자바 버전이 동작되지 않는다.


패키지 경로: com.springMVC.javaSecurity5.config


package com.springMVC.javaSecurity5.config;


import javax.sql.DataSource;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.crypto.password.PasswordEncoder;

import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import org.springframework.security.web.csrf.CsrfFilter;

import org.springframework.web.filter.CharacterEncodingFilter;


import com.springMVC.javaSecurity5.db.SqlMapSessionFactory;

 

@Configuration

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

 

    @Autowired

    PasswordEncoder passwordEncoder;

    

    @Autowired

    private CustomAuthenticationProvider authProvider;

    

    protected void configure(AuthenticationManagerBuilder auth, HttpSecurity http) throws Exception {

      

    CharacterEncodingFilter filter = new CharacterEncodingFilter();


    /* UTF-8 한글 보완 */

        filter.setEncoding("UTF-8");

        filter.setForceEncoding(true);

        http.addFilterBefore(filter,CsrfFilter.class);

   

        auth

        .authenticationProvider(authProvider);

        

    /* 현재 - 임시

        auth.inMemoryAuthentication()

        .passwordEncoder(passwordEncoder)

        .withUser("user").password(passwordEncoder.encode("1234")).roles("RULE_USER")

        .and()

        .withUser("admin").password(passwordEncoder.encode("1234")).roles("RULE_USER", "RULE_ADMIN");

        

         */

        

        // withUser("admin").password(passwordEncoder.encode("1234")).roles("USER", "ADMIN");

      

        /* 임시

   

    UserBuilder users = User.withDefaultPasswordEncoder();

      auth.inMemoryAuthentication()

        .withUser(users.username("admin").password("1234").roles("USER"))

        .withUser(users.username("user").password("1234").roles("ADMIN"));

    */

    }

 

 

    @Override

    protected void configure(HttpSecurity http) throws Exception {


        http.authorizeRequests()

        

        // index

        .antMatchers("/")

            .permitAll()


         // 접근 오류

     .antMatchers("/member/accessDenied")

         .permitAll()

         

         .antMatchers("/member/accessDeniedView")

         .permitAll()    


         // 회원 로그인 기능

     .antMatchers("/member/loginForm")

         .permitAll()

         

     // 관리자 페이지 기능

        .antMatchers("/admin/**")

        .hasRole("ADMIN")

        // "RULE_ADMIN이라고 DB에 입력되어 있다면, RULE_은 제거하고 입력해야 인식함."

        

        // 폼 로그인 명세

        .and()

            .formLogin()

        .permitAll()

                .loginPage("/member/loginForm")

                .failureForwardUrl("/member/loginForm?error")

                .defaultSuccessUrl("/")

                .usernameParameter("id")

                .passwordParameter("password")

       

        // 로그아웃 처리

.and()

                .logout()

                .logoutUrl("/logout")

                .logoutSuccessUrl("/")

                .invalidateHttpSession(true)

                .deleteCookies("JSESSION_ID")

                .deleteCookies("remember-me")

        // 로그인 

            .and()

            .rememberMe()

            .tokenValiditySeconds(604800)

            .tokenRepository(persistentTokenRepository())

            // 예외처리(

            .and()

        .exceptionHandling()

        .accessDeniedPage("/member/accessDenied")

        // csrf 설정

            .and()

                .csrf().disable();

       

        

            

        

    /*

        http.authorizeRequests()

        .antMatchers("/login")

            .permitAll()

        .antMatchers("/**")

            .hasAnyRole("ADMIN", "USER")

            .hasAnyAuthority("RULE_ADMIN", "RULE_USER")

        .and()

            .formLogin()

            .loginPage("/login")

            .defaultSuccessUrl("/")

            .failureUrl("/login?error=true")

            .permitAll()

            

            .loginPage("/login")

            .usernameParameter("email")

            .passwordParameter("password")

            .successHandler(successHandler())      

            .failureHandler(failureHandler())

            .permitAll();

            

        .and()

            .logout()

            .logoutSuccessUrl("/login?logout=true")

            .invalidateHttpSession(true)

            .permitAll()

        .and()

            .csrf()

            .disable();

    */

        

    }

    

    // 로그아웃 Persistent_Logins에 관한 설정   (주석 해도 무방)...

    @Bean 

public PersistentTokenRepository persistentTokenRepository() {

JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();

DataSource usrDS = getDataSource();

db.setDataSource(usrDS);

return db;

}

    


    // DataSource 불러오기    (주석 해도 무방)

@Bean

public DataSource getDataSource() {

       // BasicDataSource dataSource = new BasicDataSource(); - Apache DBCP2

SqlMapSessionFactory factory = SqlMapSessionFactory.getInstance();

       return factory.getOracleDataSource(); // 오라클 적용함.

}

    

// 비밀번호 생성 - 암호(BCryptPasswordEncoder)

    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

        

    }

    

}


파일명: SecurityConfig.java


[첨부(Attachments)]

SecurityConfig.zip



비고: 주석 잘 쳐서 정리해서 빌드해보면, 내장 로그인 페이지를 볼 수 있다.
       - SecurityWebApplicationInitializer.java, SecurityConfig.java 두 개 파일의 힘이 얼마나 큰지 실감 해볼 수 있다.

         Spring-Framework 설정 전체를 제어해버린다고 해도 된다.




17. 로그인 인증 - Spring Security (CustomAuthenticationProvider.java)


이 코드 부분은 찾아보려고 해도 쉽게 나오지 않는다. 어려운 부분 중 하나이다.

공개하는 이유는 삽질을 적게 하라는 의미이다.


패키지 경로: com.springMVC.javaSecurity5.config


package com.springMVC.javaSecurity5.config;


import java.util.List;


import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.stereotype.Component;


import com.springMVC.javaSecurity5.service.CustomUserDetailsService;


@Component

public class CustomAuthenticationProvider implements AuthenticationProvider {

    

    private UserDetailsService userDeSer;

 

    @Override

    public Authentication authenticate(Authentication authentication) {

        

        String username = (String) authentication.getPrincipal();

        String password = (String) authentication.getCredentials();

        

        if ( username.equals("fail")) {

        System.out.println("(에러)아이디: 실패");

        return null;

        }

        

        // DB 정보 읽기

        userDeSer = new CustomUserDetailsService();

        UserDetails userDetail = userDeSer.loadUserByUsername(username);

        

        @SuppressWarnings("unchecked")

  List<GrantedAuthority> roles = (List<GrantedAuthority>) userDetail.getAuthorities();

        

        // 권한

        System.out.println("DB불러오기-권한:" + userDetail.getAuthorities());

        System.out.println("DB불러오기-비밀번호:" + userDetail.getPassword());

        System.out.println("roles:" + roles.get(0));

        

        if ( !matchPassword(password, userDetail.getPassword())) {

        System.out.println("(에러)비밀번호: 불일치" + password);

        return null;

        }

        

        UsernamePasswordAuthenticationToken result =

        new UsernamePasswordAuthenticationToken(username, password, roles);

        

        result.setDetails(userDetail);

        

        return result;

    }

 

    @Override

    public boolean supports(Class<?> authentication) {

        return true;

    }

    

    private boolean matchPassword(String loginPwd, String password) {

   

        BCryptPasswordEncoder secure = new BCryptPasswordEncoder();

        return secure.matches(loginPwd, password);

    }

 

}


파일명: CustomAuthenicationProvider.java


[첨부(Attachments)]

CustomAuthenticationProvider.zip


어디에 구체적으로 사용되는 부분인가?


사용하는 영역은 SecurityConfig.java에 http의 auth의 .authenicationProvider()에 사용된다.



그림 25. SecurityConfig.java


왜 이 코드를 사용하는지 소개해본다면,

"No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken"

이 문제가 디버그 오류창에 뜨는 것을 볼 수 있다.


기본 내장형 계정 생성 등으로 인증을 시도하면 xml방식에서는 처리해줬는데, java방식에서는 인증받지 못한다.


     /* 현재 - 임시


        auth.inMemoryAuthentication()

        .passwordEncoder(passwordEncoder)

        .withUser("user").password(passwordEncoder.encode("1234")).roles("RULE_USER")

        .and()

        .withUser("admin").password(passwordEncoder.encode("1234")).roles("RULE_USER", "RULE_ADMIN");

        

        */


[문제가 발생되는 기본형 - 코드]


이 코드로 작업하면, xml에서는 동작되었던 부분이 동작되질 않는다.


토큰 인증도 해결할 겸 "CustomAuthenicationProvider.java를 설계해서 문제를 해결한 것이다.




18. SQL (Factory) - SqlMapSessionFactory.java


나중에 CP(Connection Pool, 커넥션 풀)이라고 불리는 것으로 구현해봐도 좋을 듯 싶다.

iBatis를 남용해서 프로젝트에 기본마냥 소개하는 책들이 무척 많은데 기본은 순수한 DB를 사용하는 것부터 출발하는 것이다.


iBatis는 SQL 코드 개발 등에서 생산성이 좋아지는 도구 중 하나이지만, 필수 사항은 아니라고 본다.

차라리 필수 사항을 꼽아본다면, 커넥션 풀을 하나 추천해보고 싶다.


아무튼 커넥션 풀 주제가 아니기 때문에 생략한다.


패키지 경로: com.springMVC.javaSecurity5.db


package com.springMVC.javaSecurity5.db;


import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.Properties;


import javax.sql.DataSource;


import oracle.jdbc.pool.OracleDataSource;


public class SqlMapSessionFactory {

private static SqlMapSessionFactory factory = new SqlMapSessionFactory();


private SqlMapSessionFactory() {}


private final String driverName = "oracle.jdbc.driver.OracleDriver";

private final String dbUrl = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";

private final String userName = "{사용자계정명}";

private final String userPassword = "{비밀번호}";

public static SqlMapSessionFactory getInstance() {

return factory;

}


/*

*     public static DataSource getMySQLDataSource() {

        Properties props = new Properties();

        FileInputStream fis = null;

        MysqlDataSource mysqlDS = null;

        try {

            fis = new FileInputStream("db.properties");

            props.load(fis);

            mysqlDS = new MysqlDataSource();

            mysqlDS.setURL(props.getProperty("MYSQL_DB_URL"));

            mysqlDS.setUser(props.getProperty("MYSQL_DB_USERNAME"));

            mysqlDS.setPassword(props.getProperty("MYSQL_DB_PASSWORD"));

        } catch (IOException e) {

            e.printStackTrace();

        }

        return mysqlDS;

    }

    */

    public DataSource getOracleDataSource(){

        

    OracleDataSource oracleDS = null;

        

    try {

            oracleDS = new OracleDataSource();

            oracleDS.setURL(dbUrl);

            oracleDS.setUser(userName);

            oracleDS.setPassword(userPassword);

        } catch (SQLException e) {

            e.printStackTrace();

        }

        return oracleDS;

        

    }

public Connection connect() {


Connection conn = null;


try {

Class.forName(driverName);

conn = DriverManager.getConnection(dbUrl, userName, userPassword);

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


return conn;


}


public void close(Connection conn, PreparedStatement ps, ResultSet rs) {


if ( rs != null ) {


try {

rs.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}


close(conn, ps); // Recursive 구조 응용(재귀 함수)


} // end of if


}


public void close(Connection conn, PreparedStatement ps) {


if (ps != null ) {

try {

ps.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


if (conn != null ) {

try {

conn.close();

}

catch(Exception ex) {

System.out.println("오류 발생: " + ex);

}

} // end of if


}

}



파일명: SqlMapSessionFactory.java


[첨부(Attachments)]

SqlMapSessionFactory.zip



비고: 재사용이 가능한 형태로 설계하였다.



19. Model - CustomUserDetails.java


순수한 Model 형태는 아니고, 부분 개량하였다.

Spring-Security에서 제공하는 UserDetails(인터페이스)를 Model 클래스에 구현해야 한다.

List<role> 기능 문제 등으로 인해서 String authorities를 List<GrantedAuthority>로 변경하였다.


@Override 된 부분들이 UserDetails에 정의된 내용이다.


패키지 경로: com.springMVC.javaSecurity5.model


package com.springMVC.javaSecurity5.model;


import java.util.ArrayList;

import java.util.Collection;

import java.util.List;


import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import org.springframework.security.core.userdetails.UserDetails;


public class CustomUserDetails implements UserDetails{


private static final long serialVersionUID = 1L;

private String username;

    private String password;

    // 개량함. (다중 권한 고려)

    private List<GrantedAuthority> authorities;

    private boolean enabled;

        

public String getUsername() {

return username;

}


public void setUsername(String username) {

this.username = username;

}


public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

     public void setAuthority(String authority) {

// 권한 객체 생성

if ( authorities == null ) {

authorities = new ArrayList<GrantedAuthority>();

}

// 권한 추가

SimpleGrantedAuthority grantObj = new SimpleGrantedAuthority(authority);

authorities.add(grantObj);

     }


public boolean getEnabled() {

return enabled;

}

public void setEnabled(boolean enabled) {

this.enabled = enabled;

}


@Override

public Collection<? extends GrantedAuthority> getAuthorities() {


        return authorities;


}


@Override

public boolean isAccountNonExpired() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isAccountNonLocked() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isCredentialsNonExpired() {

// TODO Auto-generated method stub

return false;

}


@Override

public boolean isEnabled() {

// TODO Auto-generated method stub

return false;

}

    

}


파일명: CustomUserDetails.java


[첨부(Attachments)]

CustomUserDetails.zip





20. Service - CustomUserDetailsService.java


CustomUserDetailsService는 Spring-Security의 "UserDetailsService(인터페이스)"로 정의된 내용을 구현하는 것이다.

인터페이스의 영향도 있지만, DB를 실제로 불러올 때 사용자 관점에서 처리되는 부분이라고 본다.


package com.springMVC.javaSecurity5.service;


import org.springframework.security.core.userdetails.UserDetailsService;

import org.springframework.security.core.userdetails.UsernameNotFoundException;


import com.springMVC.javaSecurity5.dao.SqlSessionTemplate;

import com.springMVC.javaSecurity5.model.CustomUserDetails;


public class CustomUserDetailsService implements UserDetailsService {

    

    private SqlSessionTemplate sqlSession = SqlSessionTemplate.getInstance();

 

    public CustomUserDetails getUserById(String username) {

        return sqlSession.selectOne("user.selectUserById", username);

    }

 

    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {


    CustomUserDetails user = sqlSession.selectOne("null", username);

        

        if(user==null) {

            throw new UsernameNotFoundException(username);

        }

        return user;

    }

    

}


파일명: CustomUserDetailsService.java


[첨부(Attachments)]

CustomUserDetailsService.zip




21. DAO - SqlSessionTemplate.java


실제 DB를 구현하는 부분이다.


package com.springMVC.javaSecurity5.dao;


import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;


import com.springMVC.javaSecurity5.db.SqlMapSessionFactory;

import com.springMVC.javaSecurity5.model.CustomUserDetails;


public class SqlSessionTemplate {


private SqlSessionTemplate() {}

private static SqlSessionTemplate sqlTemplate;

    private static SqlMapSessionFactory session; 

    

    public static SqlSessionTemplate getInstance(){

   

        if(sqlTemplate == null){

        sqlTemplate = new SqlSessionTemplate();

            session = SqlMapSessionFactory.getInstance();

        }


        return sqlTemplate;

    }

    

// 추후 iBatis 고려

public CustomUserDetails selectOne(String id, String username) {

Connection conn = null;

    PreparedStatement pstmt = null;

    ResultSet rs = null;

   

    CustomUserDetails node = null;


    String sql = "select g1.username, g1.password, g2.authority, " + 

       "g1.enabled from comp_users g1, comp_authorities g2 where g1.username = g2.username " +

       "and g1.username = ?";


    System.out.println(sql);


    try {


    conn = session.connect();


    pstmt = conn.prepareStatement(sql);

    pstmt.setString(1, username);

   

    rs = pstmt.executeQuery();


    while ( rs.next() ) {

   

    // 데이터가 존재할 때, 노드 생성

    node = new CustomUserDetails();

    node.setUsername(rs.getNString(1));

    node.setPassword(rs.getNString(2));

    node.setAuthority(rs.getNString(3));

    System.out.println("rs:" + rs.getNString(3));

    node.setEnabled(rs.getBoolean(4));

    }


    }catch(Exception ex) {

    System.out.println("오류 발생: " + ex);

    }

    finally {

    session.close(conn, pstmt, rs);

    }


    return node;

}

}



파일명: SqlSessionTemplate.java


[첨부(Attachments)]

SqlSessionTemplate.zip



* 3부에서는 View에 대해서 구현하는 방법을 소개하겠다.


3부에서는 "jsp 파일" 등 사용자 인터페이스 화면에 대해서 소개하겠다.


1. [Spring-Framework] 17. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-Java) (3), 2020-09-27

https://yyman.tistory.com/1424


반응형
728x90
300x250

[Spring-Framework] 14. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (2)


1부에 이어서 글을 작성하도록 하겠다.


1. [Spring-Framework] 14. Spring MVC, Spring Security 5.4, Oracle - 보안처리(로그인-XML) (1), 2020-09-26

https://yyman.tistory.com/1419




11. Controller - HomeController.java


HomeController.java에 관한 것이다.

파일은 처음 프로젝트(이하 "Spring MVC Project")를 생성하면, 자동으로 만들어진다.


package com.web.springsecurity5.controller;


import java.text.DateFormat;

import java.util.Date;

import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.security.core.context.SecurityContextHolder;

import org.springframework.security.core.userdetails.UserDetails;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class HomeController {

private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

/**

* Simply selects the home view to render by returning its name.

*/

@RequestMapping(value = "/", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome home! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

String username = null;

String password = null;

try {

UserDetails userDetails = (UserDetails)principal;

username = userDetails.getUsername();

password = userDetails.getPassword();

}catch(Exception e) {

e.getStackTrace();

}


model.addAttribute("username", username);

model.addAttribute("password", password);

model.addAttribute("serverTime", formattedDate );

return "home";

}

}



파일명: HomeController.java


[첨부(Attachments)]

HomeController.zip


비고: 의외로 Controller 코드는 간단하게 되어있는 것을 알 수 있다.




12. Controller - MemberController.java


MemberController에 관한 것이다.

자세히 코드를 살펴보면, "@RequestMapping"을 변경해봐도 되는 부분이 있다. (크게 무방하다.)

함수에 직접 경로를 입력한 형태로 servlet을 구성하였다.


package com.web.springsecurity5.controller;


import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class MemberController {

private static final Logger logger = LoggerFactory.getLogger(MemberController.class);

@RequestMapping(value = "/member/loginForm", method = RequestMethod.GET)

public String loginForm(Locale locale, Model model) {

logger.info("안녕 - 로그인 폼(Hello - Login Form");

// model.addAttribute("serverTime", formattedDate );

return "member/loginForm";

}


@RequestMapping(value = "/member/accessDenied")

public String accessDenied(Locale locale, Model model) {


logger.info("접근 금지 - 이동(Accessed Denied)");

// model.addAttribute("serverTime", formattedDate );

return "redirect:/member/accessDeniedView";

}

@RequestMapping(value = "/member/accessDeniedView")

public String accessDeniedView(Locale locale, Model model) {


logger.info("접근 금지 - 출력(Accessed Denied)");

// model.addAttribute("serverTime", formattedDate );

return "member/accessDenied";


}

}


파일명: MemberController.java


[첨부(Attachments)]

MemberController.zip




13. Controller - AdminController.java


관리자 페이지에 관한 Controller이다.


package com.web.springsecurity5.controller;


import java.util.Locale;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


@Controller

public class AdminController {

private static final Logger logger = LoggerFactory.getLogger(AdminController.class);

@RequestMapping(value = "/admin/home", method = RequestMethod.GET)

public String home(Locale locale, Model model) {

logger.info("Welcome - 관리자 페이지(Admin Home)!");

return "admin/home";

}

}


파일명: AdminController.java


[첨부(Attachments)]

AdminController.zip




14. View - 구성도


출력되는 화면에 관한 프로젝트 구성도이다.



그림 25. view 프로젝트 구성도





15. View - home.jsp


코드와 실제 디자인 화면을 동시에 소개하겠다.


경로: src/main/java/webapp/WEB-INF/views


그림 26. 로그인 페이지(home.jsp) - 로그인 전



그림 27. 로그인 페이지(home.jsp) - 로그인 후





그림 27. 로그인 페이지(home.jsp) - 로그인 후 - 비밀번호 생성 페이지


비고: UI(이하 "User Interface")설계하는데 도움이 되었으면 한다.



<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>


<%@ page session="false" %>

<html>

<head>

<title>Spring-Security 5 (XML 방식)</title>

<meta charset="UTF-8">

<style>

body{

font-family:'Arial';

font-size:12px;

}

a{

text-decoration:none;

color:#666;

}

</style>

</head>

<body>

<h1>

Hello world!(Spring-Security 5(XML 방식)) - DB연동(Oracle)

</h1>

<hr />

<sec:authorize access="isAnonymous()">

<!-- 로그인 전 -->

<p>

<a href="<c:url value="/member/loginForm" />">로그인</a>

</p>

</sec:authorize> 


<sec:authorize access="isAuthenticated()">

<!-- 로그인 성공 -->

<form:form action="${pageContext.request.contextPath}/logout" method="POST">

<input type="submit" value="로그아웃" />

</form:form>

<p>

${username} &nbsp; ${password}

</p>

</sec:authorize> 


<h3>

<a href="<c:url value="/admin/home" />">관리자 홈</a>&nbsp;&nbsp;

<a href="<c:url value="/encode-password?password=pass" />">비밀번호</a>

</h3>


<!-- 비밀번호 생성기 -->

<c:set var="gene_pwd" value="${encode}" />

<c:if test="${gene_pwd != null}">

    <c:out value="${gene_pwd}" />

</c:if>


</body>

</html>



파일명: home.jsp


[첨부(Attachments)]

home.zip



16. View - admin/home.jsp


관리자 페이지에 관한 것이다.


경로: src/main/java/webapp/WEB-INF/views/admin



그림 28. 관리자 홈 화면 - (User-Interface) 설계


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<!DOCTYPE html>

<html lang="ko">

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <title>관리자(ROLE_ADMIN) - 홈</title>

    <style>

    body{

    font-family:'Arial';

    font-size:12px;

    }

   

    a{

    text-decoration:none;

    color:#666;

    }

    </style>

</head>


<body>


<h1>관리자 홈 화면!</h1>

<hr />


<h3>[<a href="<c:url value="/" />">홈으로(Home)</a>]</h3>


</body>

</html>


파일명: home.jsp


[첨부(Attachments)]

home.zip




17. View - member/loginForm.jsp


로그인 페이지에 대한 것이다.


경로: src/main/java/webapp/WEB-INF/views/member



그림 29. 로그인 폼 - 화면



그림 30. 로그인 폼 - 계정 불일치(아이디 또는 비밀번호)



<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>

<html lang="ko">

<head>

<meta charset="UTF-8">

    <title>로그인 - 페이지(Login - Page)</title>

    <style>

    body{

    font-family:'Arial';

    font-size:12px;

    }

   

    a{

    text-decoration:none;

    color:#666;

    }

   

    </style>

</head>


<body>


<h1>아이디와 비밀번호를 입력해주세요.</h1>

<hr />


<c:url value="/login" var="loginUrl" />


<form:form name="f" action="${loginUrl}" method="POST">

    <p>

        <label for="username">아이디</label>

        <input type="text" id="id" name="id" />

    </p>

    <p>

        <label for="password">비밀번호</label>

        <input type="password" id="password" name="password"/>

    </p>

    <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>

    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

    <button type="submit" class="btn">로그인</button>

    

    <!-- 에러 메시지 영역 -->

    <c:if test="${param.error != null}">

        <p>아이디와 비밀번호가 잘못되었습니다.</p>

    </c:if>

    <c:if test="${param.logout != null}">

        <p>로그아웃 하였습니다.</p>

    </c:if>

    

</form:form>

<h3>[<a href="<c:url value="/" />">홈으로(Home)</a>]</h3>


</body>

</html>



파일명: loginForm.jsp


[첨부(Attachments)]

loginForm.zip




18. View - member/accessDenied.jsp


인가되지 않은 화면 또는 오류 처리에 대한 것이다.



그림 31. Access Denied 페이지


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE html>

<html lang="ko">

<head>

    <meta charset="UTF-8">

    <title>Access Denied</title>

</head>


<body>


<h1>Access Denied!</h1>


<h3>[<a href="<c:url value="/" />">홈</a>]</h3>


</body>

</html>



파일명: accessDenied.jsp


[첨부(Attachments)]

accessDenied.zip



19. 공식 사이트에서 배포하고 있는 Spring-Boot 기반 - 예제


https://github.com/spring-projects/spring-security/tree/5.4.0/samples/boot/helloworld


Spring Security 5.4를 자바 파일 환경 설정 방식으로 구현한 것이다.

(구현 방식에는 크게 "XML 방식"과 "Java 파일" 방식이 있다.)

-> 구현하는데 있어서, 차이점이 있다.



그림 32. github - Spring Security Official 사이트



* 맺음글(Conclusion)


아무쪼록 Spring-Security 5 적용에 있어서 어려움이 해소되길 진심으로 기원한다.



* 참고자료(References)


1. 스프링프레임웍 - Spring Security(2) : 커스텀 로그인 화면 및 권한에 따른 접근 제어, https://offbyone.tistory.com/91, Accessed by 2020-09-26, Last Modified 2018-04-08.

-> 추천(50점): 잘 되어 있는 블로그 중 하나이다.


2. 스프링 시큐리티 5 - There is no PasswordEncoder mapped for the id "null", https://meaownworld.tistory.com/129, Accessed by 2020-09-26, Last Modified 2018-03-11.


3. [spring security/스프링 시큐리티] 비밀번호 암호화와 로그인 하기, https://simsimjae.tistory.com/36, Accessed by 2020-09-26, Last Modified 2017-08-28.


4. BCryptPasswordEncoder : 암호 해시, http://www.devkuma.com/books/pages/1124, Accessed by 2020-09-26, Last Modified 2017-12-30.

-> 비고: Spring Security 암호 처리에 대해서 잘 소개하고 있음.


5. Spring Security JdbcDaoImpl Example, https://www.concretepage.com/spring-5/spring-security-jdbcdaoimpl#XML, Accessed by 2020-09-26, Last Modified 2019-12-11.

-> 추천(35점): Java방법론, XML방법론 두 타입을 자세하고 잘 설명하고 있음.


6. [Spring Security] 현재 로그인한 사용자 정보 가져오기, https://itstory.tk/entry/Spring-Security-%ED%98%84%EC%9E%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EB%B3%B4-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0, Accessed by 2020-09-26, Last Modified 2017-09-07.


7. [spring] 시큐리티 비밀번호 bcrypt 간단한 암호화, https://blog.naver.com/PostView.nhn?blogId=ljpark6&logNo=221578990976, Accessed by 2020-09-26, Last Modified 2019-07-06.


9. Spring Web MVC Security Basic Example Part 1 with XML Configuration, https://www.codejava.net/frameworks/spring/spring-web-mvc-security-basic-example-part-1-with-xml-configuration, Accessed by 2020-09-26, Last Modified 2019-06-24.

-> 비고: Spring Security 4를 기준으로 작성하였음. 

Spring-Framework 3.2~4.x 이전의 경우에는 내장 로그인 인터페이스가 해당 사이트와 동일하거나 흡사함.

이전 인터페이스보다는 Spring-Framework 4.x~5 이후부터는 디자인이 있는 형태의 내장 로그인 인터페이스로 출력됨.


10. [SPRING] Controller에서 redirect하기, https://sime.tistory.com/119, Accessed by 2020-09-26, Last Modified 2016-11-17.

-> 비고: Spring-Security의 Access-error-page처리에 있어서 메시지만 출력하고, accessDenied.jsp화면이 출력되지 않는 문제를 해결하는데

          redirect 방법을 참고하여 적용하였음.


11. Spring Security 5.4 - Docs, https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/, Accessed by 2020-09-26, Last Modified 2020-09-09.

-> 비고: 공식 사이트이긴 하나, spring-boot을 주력으로 작성되어 있어서 크게 도움이 되지 않을 수도 있음.


12. Spring Security 5.4 - API, https://docs.spring.io/spring-security/site/docs/5.4.0/api/, Accessed by 2020-09-26, Last Modified .

-> 비고: API 사이트이니 구현해놓고 분석할 때 참고하면 될 것으로 보인다.


13. spring-security/samples/boot/helloworld at 5.4.0 · spring-projects/spring-security · GitHub, https://github.com/spring-projects/spring-security/tree/5.4.0/samples/boot/helloworld, Accessed by 2020-09-26, Last Modified .

-> 추천(15점): 간결하게 Java 환경설정 방식으로 예제를 소개하고 있다. (Spring-boot로 작성), Spring Security에서 공식 운영하는 github사이트이다.

반응형
728x90
300x250

[Spring-Framework] 9. MVC웹 - POST, GET 방식, c태그 목록 출력


웹 게시판 작성에서 필요한 POST, GET 방식 처리에 대해서 Spring-Framework를 통해서 소개하려고 한다.

자료구조(Data Structure)인 List, Hash-Map에 대한 내용은 생략하겠다.


일반 범용 "게시판"에서 사용될 수 있는 것 위주로 작성하였다.


* IDE: Spring Tool-Suite 4(Eclipse)

-> Spring Legacy Project의 Spring MVC Project를 기반으로 작성함.





1. 결과부터 살펴보는 POST, GET 방식 처리


크게 데이터를 전송하는 방식에는 POST, GET 방식이 있다.

기능으로 요약하면, 외부에 노출되서 전송되는 방식은 GET이고, 숨겨서 전송하는 방식은 POST방식이다.


이 프로젝트의 특징은 추후에 MyBatis 등의 라이브러리까지 고민하여 작성하였다.




그림 1. 글쓰기 양식



그림 2. POST 방식 처리 - Map 목록 출력




그림 3. GET 방식 처리 - List 목록 출력





2. 프로젝트


Controller, VO(Value Object)는 다음처럼 해준다.



그림 4. 프로젝트 구성(Controller와 VO)



View 영역은 아래처럼 구성해준다.



그림 5. 프로젝트 구성(view영역의 jsp 파일)





3. 코드(MemberVO)


MemberVO는 매우 간단하게 작성하였다.


package com.springMVC1.web.vo;


public class MemberVO {


private String id;

private String passwd;

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getPasswd() {

return passwd;

}

public void setPasswd(String passwd) {

this.passwd = passwd;

}

}



파일명: MemberVO.java




4. 코드(Controller - BoardController)


BoardController를 구성해보았다.


package com.springMVC1.web.controller;


import java.text.DateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Locale;

import java.util.Map;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;


import com.springMVC1.web.vo.MemberVO;



@Controller

@RequestMapping(value="/board")

public class BoardController {

private static final Logger logger = LoggerFactory.getLogger(BoardController.class);

@RequestMapping(value = "/list", method = RequestMethod.GET)

public String list(Locale locale, Model model) {

logger.info("Welcome board - list! The client locale is {}.", locale);

Date date = new Date();

DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

String formattedDate = dateFormat.format(date);

model.addAttribute("serverTime", formattedDate );

return "/board/list";

}

// GET, POST 방식 모두 가능

@RequestMapping(value = "insert")

public String insert(Locale locale, Model model) {

return "/board/insert";

}

// POST방식만 사용

@RequestMapping(value = "insertResult", method=RequestMethod.POST)

public String insertResult(Model model, MemberVO memberVO) {

model.addAttribute("serverTime", "1군");


logger.info("로거 사용"); // 로거

HashMap<String, Object> map = new HashMap<String, Object>();

MemberVO node1 = new MemberVO();

// 1번 회원

node1.setId("user1");

node1.setPasswd("1234");

map.put("1", node1);

// 2번 회원

node1 = new MemberVO();

node1.setId("user2");

node1.setPasswd("9876");

map.put("2", node1);

// model 속성으로 등록

model.addAttribute("map", map);

return "/board/insert_result_post";

}

// GET방식만 사용 - 편법(매개변수 하나 더 추가) 

@RequestMapping(value = "insertResult", method=RequestMethod.GET)

public String insertResult(Model model, MemberVO memberVO, String s) {

model.addAttribute("serverTime", "2군");


logger.info("로거 사용"); // 로거

// 다중 회원 목록 출력 실험

List<MemberVO> member = new ArrayList<MemberVO>();

MemberVO node1 = new MemberVO();

// 1번 회원

node1.setId("user1");

node1.setPasswd("1234");

member.add(node1);

// 2번 회원

node1 = new MemberVO();

node1.setId("user2");

node1.setPasswd("9876");

member.add(node1);

model.addAttribute("list", member);

return "/board/insert_result_get";

}

}


파일명: BoardController.java


이 프로젝트에서 POST, GET을 하나의 RequestMapping Value값 "insertResult"로 처리하는 방법에 대해서 소개하고 있다.




5. 코드(View - JSP 파일)


insert.jsp 파일을 기반으로 POST, GET 방식 실험을 동시에 할 수 있도록 설계하였다.


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>글쓰기 - 양식</title>

</head>

<body>


<%

String context = request.getContextPath();

%>

<h3>POST 방식</h3>

<form action="<%=context%>/board/insertResult" method="POST">

ID: <input type="text" name="id"><br/>

비밀번호: <input type="password" name="passwd"><br/>

<input type="submit" value="전송">

</form>

<br />

<h3>GET 방식</h3>

<form action="<%=context%>/board/insertResult" method="GET">

ID: <input type="text" name="id"><br/>

비밀번호: <input type="password" name="passwd"><br/>

<input type="submit" value="전송">

</form>


</body>

</html>


파일: /board/insert.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>등록 결과 - GET방식(실험)</title>

</head>

<body>


<!-- 결과 -->

<h3>GET 방식 - 실험</h3>

<h5>GET으로 전송받은 데이터</h5>

<table border="1" style="width:500px">

<tr>

<td>서버시간</td>

<td>${serverTime }</td>

</tr>

<tr>

<td>ID</td>

<td>${memberVO.id}</td>

</tr>

<tr>

<td>비밀번호</td>

<td>${memberVO.passwd }</td>

</tr>

</table>

<br />

<h5>ArrayList 결과 출력하기</h5>

<!-- 다중 회원 출력 -->

<table border="1" style="width:500px">


<c:forEach items="${list}" var="list">

<tr>

<td>ID</td>

<td>${list.id}</td>

<td>비밀번호</td>

<td>${list.passwd }</td>

</tr>

</c:forEach>

</table>


</body>

</html>


파일: /board/insert_result_get.jsp


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>


<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>등록 결과 - Post방식(실험)</title>

</head>

<body>


<!-- 결과 -->

<h3>POST 방식 - 실험</h3>

<br/>

<table border="1" style="width:500px">

<tr>

<td>서버시간</td>

<td>${serverTime }</td>

</tr>

<tr>

<td>ID</td>

<td>${memberVO.id}</td>

</tr>

<tr>

<td>비밀번호</td>

<td>${memberVO.passwd }</td>

</tr>

</table>


<h5>Map 결과 출력하기</h5>

<!-- 다중 회원 출력 -->

<table border="1" style="width:700px">


<c:forEach items="${map}" var="i">

<tr>

<td style="width:50px">1. Key값</td>

<td style="width:150px">${i.key}</td>

<td style="width:50px">2. 객체주소</td>

<td style="width:200px">${i.value}</td>

<%

%>

<td style="width:50px">3. 객체값(ID)</td>

<td style="width:100px">${i.value.id}</td>

<td style="width:50px">3. 객체값(Passwd)</td>

<td style="width:100px">${i.value.passwd}</td>

</tr>

</c:forEach>

</table>


</body>

</html>


파일: /board/insert_result_post.jsp


* 형식문자

-> 년도 출력

<fmt:formatNumber value="${변수명}" pattern="yyyy-mm-dd" />

<fmt:formatNumber value="${객체명.멤버변수명}" pattern="yyyy-mm-dd" />



* 맺음글(Conclusion)


게시판 구현에 대해서 조금 더 쉽게 접근할 수 있는 방법에 대해서 소개하였다.

복잡한 기능들 다 제거해보았더니, 나름대로 Spring-Framework도 쉽고 빠르게 접근할 수 있다고 본다.

반응형
728x90
300x250

[Spring-Framework] 1. 간단한 소개, STS 4 설치와 실행


오랜만에 글을 작성한다.

스프링 프레임워크에 대해서 간단하게 소개하려고 한다.

Java 기반으로 만든 프레임워크라고 이해하면 되겠다.

과거의 전자정부 프레임워크의 기반이 되는 원래 프레임워크는 Spring Framework Project라고 보면 되겠다.


https://egovframe.go.kr/   (이 게시글에서는 기억할 필요가 없음. 초간단하게 작성하기 위함)

http://spring.io


기본적인 셋팅은 간단하다고 보면 된다.

주로 어디에 사용되는 프레임워크냐면, 국내에서는 웹 개발 프로젝트에 많이 사용되는 것으로 알려져 있다.


물론 순수하게 jsp model1기반으로 작성된 웹 페이지도 있다.




[1. Spring Framework 사이트에서 프로젝트 내려받기]


https://spring.io/tools



그림 1. Spring Tools 4 for Eclipse 사이트(2020-09-15)


Projects에서 Development Tools의 Spring Tools 4를 클릭한다.


그림 2. Spring Tools 4 for Eclipse 사이트(2020-09-15)


버전은 크게 리눅스(Linux 64-BIT), MACOS 64-BIT, 윈도우 64비트(Windows 64bit) 버전으로 3가지로 구성된다.

운영체제에 맞는 걸로 내려받으면 된다.



2. 압축 풀기


spring-tool-suite-4-4.7.2.RELEASE-e4.16.0-win32.win32.x86_64.self-extracting.zip을 압축풀면 내부 폴더에 contents.zip이라는 파일이 있다.

이 파일도 압축을 풀어준다.



그림 3. 프로그램 설치 완료


이렇게 되어있으면 설치는 완료된 것이라고 보면 되겠다.



4. 프로그램 실행


Spring Tools Suites는 SpringToolSuite4.exe 파일을 실행시키면 된다.



그림 4. SpringToolSuite4 - 위치 찾기


IDE 사용자체를 놓고 본다면, Eclipse 기반이므로 친숙하게 적응할 수 있다.
(MVC패턴, 프레임워크 사용방법 등 생략하고 IDE만 놓고 보면)



그림 5. SpringToolSuites 4-4.7.2 (2020-09-15)


신 버전과 이전 버전의 차이점
신 버전에서는 Legacy Project 등을 사용하려면, Help->Eclipse MarketPlace에서 STS를 검색하여 Add-On을 설치해야 한다.




5. MarketPlace의 STS 검색 후 Add-On 설치하기


Spring Tools 3 Add-On for Spring Tools 4 3.9.14.CI를 찾으면 된다. (Legacy Project 등 이전 버전의 부가기능을 사용하고자 할 때)



그림 6. Eclipse Marketplaces의 모습(2020-09-15)


"Install"을 클릭한다.



그림 7. Install 모습


Confirm을 눌러서 다음 과정을 진행한다.



그림 8. 라이선스에 관한 것


오픈소스 라이선스에 관한 것이다. I accept the terms of the.....을 체크하고 Finish를 누른다.



그림 9. 설치중 모습(2020-09-15)


기다리면 된다.



그림 10. Restart Now 누르기 전 모습 - Eclipse(STS)


Restart Now를 누르면 STS 프로그램이 다시 시작된다.



6. Spring Project -> 새 프로젝트 확인해보기


이전 "STS3"버전에서 사용했던 부가 기능이 존재하는지 확인할 수 있다.



그림 11. File -> New -> Project 모습


File -> New -> Other를 클릭한다.



그림 12. Spring 프로젝트 내에 이전의 부가 기능


STS3 하위 버전을 설치하지 않아도 부가 기능 추가로 다시 활용할 수도 있다.

반응형

+ Recent posts