크라우드 펀딩 사이트 '텀블벅' - 기존 jsp 프로젝트를 Spring Project로 변환하고 Spring Security 및 매니저 기능을 추가한 모델 2방식 웹사이트 구현 (매니저 기능 추가)
- 제작기간 & 참여인원
- 사용 기술
- 프로젝트 개요
- 요구분석 : User-Diagram
- DB 변동사항
- 핵심 기능 코딩
- Spring Security 적용
- Spring Tiles 적용
- 핵심 트러블 슈팅
- 기타 트러블 슈팅
- 시연 영상
- 프로젝트 회고
*기능코딩은 직접 구현한 부분만 작성
- 👩👩👦 팀 프로젝트 (7인)
- 📆 2023 6월 21일 ~ 7월 11일 (21일)
- 맡은 역할 : 테크 리더 및 기획
- 구현한 기능 🖥 프론트엔드 : ⌨ 백엔드 :
- 📆 2023 6월 21일 ~ 7월 11일 (21일)
- Java 11
- Spring Framework 2.3
- Spring Security
- Maven
- Oracle
- Tomcat
- html/css
- java script
- jquery
- GitHub
- AWS
이 프로젝트는 지난 jsp 프로젝트(링크)에서 고전 방식으로 구현한 웹 MVC 패턴을 Spring legacy 개발환경 체재로 컨벌팅하는 웹 프로젝트이다. 추가된 핵심 기능으론 Spring Security를 활용한 인증/인가 기능과 (로그인/회원가입, 페이지별 접근권한 설정 등) 관리자 페이지(프로젝트 심사 기능), Spring Tiles를 활용한 header와 footer의 분리이다.
Spring Boot를 본격적으로 공부하기 전 웹 및 스프링 컨테이너 개발환경을 직접 설정하며 기반이 되는 Spirng framework의 동작원리와 구조를 파악하고 Spring Security을 통해 웹 인가/인증 기능을 이해할 수 있는 기회가 되었다.
개발단계 및 일정
- 6월 21일 ~ 6월 27일 : 개발환경 구축 및 프로젝트 설계
- 6월 28일 ~ 7월 03일 : 기존 프로젝트 컨벌팅
- 7월 04일 ~ 7월 07일 : 회원가입/로그인 기능 + Spring Security 적용
- 7월 08일 ~ 7월 11일 : 관리자 기능 + spring security 적용
Spring Security을 적용하기 위해 기존의 db 설계를 다음과 같이 수정했다.
변동사항 펼치기
✔ 회원 테이블
- 패스워드 암호화 위해 m_password 의 데이터값 길이 변경 필요
: 넉넉하게 varchar2(100)으로 수정
ALTER TABLE member MODIFY m_password VARCHAR2(100);- member 테이블의 enabled 컬럼 추가
ALTER TABLE member
ADD ( enabled char(1) default '1' );- email 칼럼에 제약조건 부여
ALTER TABLE member ADD CONSTRAINT unique_email UNIQUE(m_email);✔권한 테이블
- member_authorities 테이블 생성
CREATE TABLE member_authorities(
username varchar2(100) not null
, authority varchar2(100) not null
, constraint fk_member_authorities_username
FOREIGN KEY(username)
REFERENCES member(m_email)
);- 매니저, 관리자 권한 부여
INSERT INTO member_authorities VALUES ( 'hong@naver.com' , 'ROLE_MANAGER' );
INSERT INTO member_authorities VALUES ( 'hong@naver.com' , 'ROLE_ADMIN' );
// 회원 이메일, 권한 값 주면 됩니다.6.1. 프로젝트 조회하기 - 상세보기 - WIKI 이동
- 프로젝트 목록보기
- 프로젝트 검색하기
- 프로젝스 상세보기
6.2. 후원하기 - 상세보기 - WIKI 이동
- 후원 요청
- 후원 승인(성공)
6.3. 프로젝트 올리기 Page - 상세보기 - WIKI 이동
- 프로젝트 생성하기
- 요금제 선택/변경
- 내가만든 프로젝트 조회
- 프로젝트 삭제하기
6.4. 프로젝트 관리하기 Page - 상세보기 - WIKI 이동
- 프로젝트 관리 페이지
- 기본정보 작성
- 펀딩계획 작성
- 선물구성
- 프로젝트 계획 작성
- 창작자 정보 작성
- 신뢰와 안전 작성
- 심사 요청
6.5. 관리자 Page - 상세보기 - WIKI 이동
- 상태별 프로젝트 목록 조회 (전체, 승인됨, 반려됨)
- 프로젝트 승인/반려
이 프로젝트의 핵심은 스프링 시큐리티를 적용한 기능들입니다.
의존 추가 - pom.xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${org.springframework-version}</version>
</dependency>핵심 기능 설명 펼치기
-
회원가입 요청 시 매개변수로 넘어온 '이름, 이메일, 비밀번호'을 받아 회원가입에 필요한 정보를 정리해둔 joinRequest 객체 생성
-
PasswordEncoder를 구현한 BCryptPasswordEncoder를 빈객체로 생성해 사용자가 입력한 비밀번호를 암호화해 서버에서도 확인하지 못하도록 보안 강화
-
Mapper 파일에 프로시저를 활용해 회원가입 동시에 ROLE_USER (회원권한) 부여
-
회원가입 성공시 메인페이지로 이동
security-context.xml
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> MemberController.java
@Setter(onMethod=@__({@Autowired}))
private PasswordEncoder passwordEncoder;
// 회원가입 POST 요청
@PostMapping("/join.do")
public String join( JoinRequest joinRequest ) throws Exception{
String pwd = joinRequest.getPassword();
joinRequest.setPassword(this.passwordEncoder.encode(pwd));
this.memberMapper.memberInsert(joinRequest);
// this.memberMapper.payMethodInsert();
return "redirect:../tumblbug/main.do";
}memberMapper.xml
<insert id="memberInsert" parameterType="map">
{CALL
DECLARE
BEGIN
INSERT into member(m_cd, m_email, m_password, m_name, M_url, M_privacy, m_message, m_up, m_pro, m_liked, m_follow, m_mk)
VALUES ('MEM'||SEQ_MEMBER.NEXTVAL, #{email}, #{password}, #{name}, DBMS_RANDOM.STRING('L', 16), '1', '1', '1', '1', '1', '1', '0');
INSERT INTO member_authorities VALUES ( #{email} , 'ROLE_USER' );
END
}
</insert>- 사용자가 post 메서드로 로그인 요청을 보내면
authentication-manager에 정의된authentication-provider를 통해 사용자 서비스(customUserDetailsService)를 호출하여 사용자 정보를 가져온다.- customUserDetailsService에서 자동 생성된 User객체의 userName을 사용해 해당 userName으로 MemberMapper의 read함수를 사용해 회원정보 조회 후 그 회원의 userName, password외 다른 정보도 함께 Member 객체로 받아옴
- 해당 Member 객체로 CustomUser 객체 생성자에 넣어줌
- CustomUser: Member 객체(회원정보)와 권한 정보(리스트)를 갖고 있는 객체
- 가져온 사용자 정보와 입력받은 비밀번호를
password-encoder(bCryptPasswordEncoder)를 사용하여 비교합니다. - 인증에 성공하면 Security를 활용해 사용자 정의 로그인 페이지 등록 : login-page의 로그인 성공시 처리할 핸들러로 설정한 customLoginSuccessHandler 호출
- customLoginSuccessHandler 에서 인증받은 사용자의 권한 정보에 따라 각각 다른 요청 url로 리다이렉트 처리
- 로그인 실패시 error 매개변수와 함께 다시 로그인 페이지로 리다이렉트
- 해당 매개변수 전달 시 리다이렉트 된 login 페이지에선 로그인 실패 알림창 띄움
- 로그아웃 상태면 로그인/회원가입 버튼이, 로그인 상태면 회원이 접속할 수 있는 여러가지 페이지들을 모든 페이지 헤더에 드롭다운으로 구현
security-context.xml
<!-- 실제 인증을 처리하는 객체 -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="customUserDetailsService">
<security:password-encoder ref="bCryptPasswordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
<!-- 사용자 정의 로그인 페이지 등록 속성 : login-page -->
<security:form-login
login-page="/tumblbug/login.do"
authentication-success-handler-ref="customLoginSuccessHandler"
authentication-failure-url="/tumblbug/login.do?error=true"
/>CustomUserDetailsService.java
@Component("customUserDetailsService")
@Log4j
public class CustomUserDetailsService implements UserDetailsService{
@Setter(onMethod=@__({@Autowired}))
private MemberMapper memberMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.warn("> Load User By UserName : " + username);
// vo 객체에는 회원정보 + 권한정보(authList)
Member vo = this.memberMapper.read(username);
log.warn("> Queiried by Member mapper : " + vo);
// UserDetails <- 변환X vo
// User 구현 org.doit.ik.security.damin.CustomUser
return vo == null ? null : new CustomUser(vo);
}
}MemberMapper.xml
<!-- m_email, m_password, m_name, enabled,authority -->
<select id="read" resultMap="memberMap">
SELECT m_cd, m_email, m_password, m_name, enabled,authority
FROM member m LEFT JOIN member_authorities auth ON m.m_email = auth.username
WHERE m_email = #{m_email}
</select>CustomUser.java
@Getter
public class CustomUser extends User {
private static final long serialVersionUID = 8215844917794450806L;
private Member member; // member 필드명 기억
public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
// 회원정보 + 권한정보(authList)
public CustomUser(Member vo) {
super(
vo.getM_email()
, vo.getM_password()
// List<AuthVO> -> Collection<>
, vo.getAuthList().stream().map( auth->new SimpleGrantedAuthority( auth.getAuthority() ) ).collect( Collectors.toList() )
);
this.member = vo;
}
}CustomLoginSuccessHandler
@Component("customLoginSuccessHandler")
@Log4j
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication //인증받은 사용자의 정보(권한)
) throws IOException, ServletException {
log.warn("> Login Success...");
List<String> roleNames = new ArrayList<String>();
authentication.getAuthorities().forEach( auth -> {
roleNames.add(auth.getAuthority());
});
log.warn("> ROLE NAMES : " + roleNames );
if ( roleNames.contains("ROLE_ADMIN")) {
response.sendRedirect("/tumblbug/manager.do?pro_status=test");
return;
} else if( roleNames.contains("ROLE_MANAGER")){
response.sendRedirect("/tumblbug/manager.do?pro_status=test");
return;
}
else if( roleNames.contains("ROLE_USER")){
response.sendRedirect("/tumblbug/main.do");
return;
}else {
response.sendRedirect("/tumblbug/main.do");
}
}
}loginForm.jsp
<form action="/login" autocomplete="on" class="style__Form-mir8lt-4 cFNky" name="form" method="post">
<!-- 입력 받기 -->
</form>
<c:if test="${ param.error }">
<script>
alert("로그인 실패했습니다. 다시 시도해주세요. ")
</script>
</c:if> header.jsp
<sec:authorize access="isAuthenticated()">
<div class="style__UserButton-zxsodr-10 csOHNF" id="memberMenu">
<!-- 프로필 이미지 -->
<div style="margin: 0; padding: 0"
class="style__UserAvatar-zxsodr-8 bKpcjX">
<span style="margin: 0; padding: 0"
class="ProfileImg__StyledProfileImg-sc-1vio56c-0 gXKtKb"></span>
</div>
<!-- 회원이름 **** 수정해야함 -->
<div class="style__UserText-zxsodr-11 fXtfpK">
<sec:authentication property="principal.member.m_name"/>
</div>
<%-- <div class="style__UserText-zxsodr-11 fXtfpK"> ${pinfo.member.m_name} </div> --%>
</div>
<!-- 회원메뉴 -->
<div class="SNB__Wrapper-wpjnaw-1 VIJCO">
<div class="SNB__MenuList-wpjnaw-2 dBwYuQ">
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">프로필</div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">응원권</div>
<div class="SNB__MenuItemDivider-wpjnaw-4 Tzsws"></div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">후원현황</div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">관심 프로젝트</div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">팔로우</div>
<div class="SNB__MenuItemDivider-wpjnaw-4 Tzsws"></div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">알림</div>
<div class="SNB__MenuItem-wpjnaw-3 fBfUv">메시지</div> </div>
</div>
</sec:authorize>
<sec:authorize access="isAnonymous()">
<div class="style__UserText-zxsodr-11 fXtfpK" id="loginButton">로그인/회원가입</div>
</sec:authorize>- css, js, image는 접근 제어 대상이 아니기에 이러한 페이지로의 요청은 보안필터 체인을 적용하지 않는다
- 프로젝트 후원하기, 만들기는 로그인 해야한(회원 권한을 가져야만) 접속할 수 있도록 권한 설정
- 관리자 페이지는 관리자 권한을 가져야만 접속할 수 있도록 권한 설정
- 권한을 갖지 못한 사용자가 관리자 페이지로 접속하려하면 customAccessDeniedHandler 호출
- customAccessDeniedHandler 를 호출하여 403 페이지 대신 따로 제작한 접근 불가 에러 페이지가 뜨도록함
security-context.xml
<!-- css, js, image는 접근 제어 대상이 아니기에 이러한 페이지로의 요청은 보안필터 체인을 적용하지 않는다 -->
<security:http pattern="/static/**" security="none"></security:http>
<security:http pattern="/design/**" security="none"></security:http>
<security:http pattern="/css/**" security="none" />
<security:http pattern="/js/**" security="none" />
<security:http use-expressions="true">
<security:csrf disabled="true"/>
<!-- 접근권한 설정 태그 -->
<security:intercept-url pattern="/tumblbug/manager.do" access="hasRole('ROLE_MANAGER')"/>
<security:intercept-url pattern="/tumblbug/makeProject.do" access="isAuthenticated()"/>
<security:intercept-url pattern="/tumblbug/pay.do" access="isAuthenticated()"/>
<!-- <security:intercept-url pattern="/customer/noticeDel.htm" access="hasRole('ROLE_MANAGER')"/> -->
<security:intercept-url pattern="/**" access="permitAll"/>
<!-- 에러 메시지 보다는 접근 금지에 대한 특정 페이지로 이동하도록 지정
<security:access-denied-handler error-page="/common/accessError.do" />
-->
<security:access-denied-handler ref="customAccessDeniedHandler" />CustomAccessDeniedHandler.java
@Component("customAccessDeniedHandler")
@Log4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error("> Access Denied Handler");
log.error("> Redirect...");
response.sendRedirect("/tumblbug/accessError.do");
}
}Spring Tiles를 활용하여 주요 페이지(메인 페이지, 검색 페이지, 상세 페이지)의 header와 footer를 한 jsp 페이지에서 작업해 삽입했습니다.
의존 추가: pom.xml
<!-- Tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-extras</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>${org.apache.tiles-version}</version>
</dependency>적용 원리 설명 펼치기
tiles.xml
<!-- main.tiles -->
<definition name="main.tiles" template="/WEB-INF/views/inc/layoutMain.jsp"> <!--도화지-->
<put-attribute name="header" value="/WEB-INF/views/inc/header.jsp"/> <!-- 물감 1 -->
<put-attribute name="content" value="/WEB-INF/views/tumblbug/main.jsp"/><!-- 물감 2 -->
<put-attribute name="footer" value="/WEB-INF/views/inc/footer.jsp"/> <!-- 물감 3 -->
</definition>
<!-- search.tiles -->
<definition name="search.tiles" template="/WEB-INF/views/inc/layoutSearch.jsp">
<put-attribute name="header" value="/WEB-INF/views/inc/header.jsp"/>
<put-attribute name="content" value="/WEB-INF/views/tumblbug/search.jsp"/>
<put-attribute name="footer" value="/WEB-INF/views/inc/footer.jsp"/>
</definition>
<!-- view.tiles -->
<definition name="view.tiles" template="/WEB-INF/views/inc/layoutView.jsp">
<put-attribute name="header" value="/WEB-INF/views/inc/header.jsp"/>
<put-attribute name="content" value="/WEB-INF/views/tumblbug/view.jsp"/>
</definition> layoutMain.jsp (main.tiles)
<body>
<div id="react-view" class="tbb-only-ff">
<!-- header 위치 지정 -->
<tiles:insertAttribute name="header"/> <!-- 그림 그리기 1 -->
<div class="style__Container-sc-7of8vt-0 gmYOwM">
<!-- content 위치 지정 -->
<tiles:insertAttribute name="content"/> <!-- 그림 그리기 2 -->
<!-- footer 위치 지정 -->
<tiles:insertAttribute name="footer"/> <!-- 그림 그리기 3 -->
</div>
</div>
</body>ProjectController.java
@GetMapping("/main.do")
public String main(Model model) {
log.info("> /main GET");
List<ProjectCard> CardList = this.mainProjectService.getCardList();
List<ProjectCard> popCardList = this.mainProjectService.getPopCardList();
model.addAttribute("CardList", CardList);
model.addAttribute("popCardList", popCardList);
return "main.tiles"; // layoutMain.jsp 호출
}9.1. 회원정보 출력문제
-
User 객체로 회원정보를 조회해 Member 객체 생성, 이를 회원정보와 권한정보를 가진 CustomUser 객체로 변환해 세션에 등록하는 코드를 찾아 사용
- 자세한 코드 설명은 '7. Spring Security 적용'에 기술
-
많은 파일을 수정해 단순 구글링으론 원인 파악 힘듬
=> 정확한 흐름 및 코드 동작 원리에 대한 이해를 바탕으로 원인을 찾아야 함
: 잘못된 객체 참조
- 테스트를 통해 정상적으로 로그인은 되었으나 출력부분에 문제가 있어 회원 정보가 출력되지 않음 확인
- var 속성이 CustomUser가 아닌 Authentication 객체를 가르켰고, 해당 객체엔 Member 프로퍼티가 없어 오류 발생
<sec:authentication property="principal" var="pinfo"/> ${pinfo.member.m_name} - sec 태그 밖이 안인 태그 내에서 세션을 직접 참조하도록 수정
<sec:authentication property="principal.member.m_name"/>Spring은 편리한 프레임워크지만 이 편리함은 문제가 발생했을 때 개발자가 어떻게 대처해야 할지 모르게 만듬
▶ 에러메세지 구글링만으로는 프레임워크를 제대로 활용할 수 없으며, 이해를 바탕으로 적절한 해결 방법을 도출해야함
9.2. Premature end of file 에러
Spring Project 진행 중 WEB-INF 폴더 밖에 있는 jsp 파일을 실행해도 404 에러로 페이지가 실행되지 않음
Cause by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 39; Premature end of file- AXParseException
- remature end of file
XML을 파싱하는 방식 중 하나로 XML문서를 순차적으로 읽어 들이면서 오류를 발생시키는 방식,
xml 파일을 읽어들이는 과정에서 문제가 있으면 발생하는 오류이다. 쉽게 말하면 xml 문법 오류!
📌 서버를 갔다오지 않은 publicWeb 실행시점에서 오류가 나는 이유
- 자주하는 실수
- 선언부에 공백이 있거나
- 잘못된 위치에 주석이 있거나
- 부등호를 <> 로 인식했거나 => 해결책 : CDATA!!! : 데이터 자체를 그냥 문자 그대로 받아들이는 것
- xml 구성요소의 순서 및 위치가 잘못되었거나
등 정말 다양한 이유로 발생하는 에러
: 해당 xml 파일에
<xml version="1.0" encoding="UTF-8"?>만 작성되있어 <!DOCTYPE> 가 없어 어떤 문서타입인지? 몰라서 생기는 문제인지
매핑이 안되서 생기는 문제인지 정확히 파악은 안됨
<xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.doit.ik.mapper.MemberMapper">
</mapper>로 수정해 각 매퍼파일들 매핑 시켜주니 정상적으로 실행됨
- MemberMapper 를 ProjectMapper 로 바꾸니 또다시 404 에러
- Mapper 파일 중 하나 지워도 정상적으로 실행
이렇게 정말 사소한, 다양한 원인으로 발생할 수 있는 문제는 구글링으로 명확한 원인을 파악하기 어려움
→ 코딩이 얼마되지 않은 시점에선 정상적으로 실행되는 시점부터 하나씩 진행해나가면서 실행
→ 안되는 부분 캐치해 문제 해결
→ 애초에 코딩할 때 부터 계속 테스트 -> 코딩 -> 테스트 -> 코딩을 반복 (중간중간 계속 테스트)
→ 만약 코딩이 많이 진행된 상태라면.... ?
9.3. 부적합한 열 유형 : 1111 에러
Spring Project 진행 중 특정 프로젝트의 상세조회 요청 중 에러 발생
MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property=’pro_cd’, mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId=’null’, jdbcTypeName=’null’, expression=’null’}. Cause: org.apache.ibatis.type.TypeException: Error setting null for parameter #1 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 부적합한 열 유형: 1111]을(를) 발생시켰습니다.-
mybatis 오류 , 즉 mapper.xml 에 매개변수로 들어와야할 ‘pro_cd’가 들어오지 않아 발생하는 문제
▶ parameter로 들어와야 할 값이 안들어와서 오류가 발생
📌 그렇다면 어느 시점부터 들어오지 않는걸까?
- 컨트롤러 (핸들러함수) : 컨트롤러의 핸들러 함수까진 무사히 들어옴
- 서비스 : f5 버튼으로 함수 안으로 들어옴 => pro_cd값 잘 전달되는거 확인
- 문제 : 여기서 this는 projectController.view() 를 나타내기 때문에 밖에 선언되어있는 viewProjectService를 호출 할 수 없음
▶ this.viewProjectService를 viewProjectService로 수정
- 매퍼
여기서 f5누르니까 내가 따로 만들어준적 없는 invoke 란 함수로 들어감

▶ 즉 projectMapper 인터페이스 안까진 정상적으로 들어갔으니 남은건 Mapper.xml 문제
▶ 디버깅은 자바코드까지만 할 수 있으므로 이후의 xml 파일은 로그의 에러메세지 보면서 수정해야함
- 디버깅의 의의 : 문제가 이후 과정의 mapper.xml 파일인걸 알 수 있었음
- 원인 발견
mapper.xml 파일에 pro_cd가 매개변수로 들어와야하는데
그 함수 안으로 pro_cd를 설정해주지 않아서 (+넘겨주지 않아서) 발생하는 문제 !
return this.projectMapper.getProject(String pro_cd) ; 프로젝트 코드를 매개변수로 넣도록 해당 매퍼 함수 수정
=> 호출부인 viewServiceImpl.java 에서 매개변수 전달
- 코드가 복잡해질 수록 '디버깅' 기능을 잘 다루는게 중요해짐
- 많은 연습 필요
이미지 슬라이드 이미지 개수 조회 못함
<script>
var size = ${imageFiles}.size()
showSlides(0);
</script> - 원인 : 자바 스크립트에선 el 인식 불가
- 해결: 스크립트 릿으로 넘어온 객체 참조 - <%= ( (ArrayList)request.getAttribute("imageFiles") ).size()%>
프로젝트 검색 오류
- 심사가 완료되지 않은 프로젝트들도 목록으로 함께 출력됨
- 해결: DB 조회시 프로젝트 상태가 '진행 중'인 프로젝트만 목록으로 조회
<select id="selProject" resultType="org.doit.ik.domain.Project">
<![CDATA[
SELECT *
FROM project
WHERE PRO_STATUS = '진행중'
ORDER BY TO_NUMBER(SUBSTR(pro_cd,4)) ASC
]]>
</select>동일한 자료형의 매개변수 인식불가
- Mapper 파일에서 동일한 자료형의 매개변수를 받으면 오류 발생
- @Param을 사용해 각 매개변수 지정
int examine(String pro_cd, String searchCondition);
int examine(@Param("pro_cd") String pro_cd, @Param("searchCondition") String searchCondition);security:intercept-url 의 url 설정 문제
- 문제 : intercept-url은 매개변수를 함께 지정시 인식하지 못함
<security:intercept-url pattern="/manager.do?pro_status=writing" access="hasRole('ROLE_ADMIN')"/>- 해결 : 매개변수 삭제
<security:intercept-url pattern="/tumblbug/manager.do" access="hasRole('ROLE_MANAGER')"/>AccessDeniedHandler 호출 불가
@GetMapping("/accessError.do")
public String accessDenied(Model model, Authentication auth) throws Exception{
log.info("> /common/accessEror.htm...Get") ;
model.addAttribute("msg", "Access Denied");
return "/common/accessError" ;
} // accessDenied - 단순히 해당 핸들러를 호출하는 컨트롤러 코딩이 없어 문제 발생
u 태그 인식 불가
- 기존 jsp 프로젝트에서 사용한 u 태그 스프링 프로젝트에선 sec 태그로 수정
contextPath 경로 오류
- 기존 코드의 이미지 및 요청 url엔 contextPath 가 자동 부여되 /tumblbug이 앞에 붙음
- 해결 : 파일들을 tumblbug 폴더 안으로 이동시켜 요청 경로 맞춰줌
HTTP delete Request시 개발자도구의 XHR(XMLHttpRequest )에서 delete요청이 2번씩 찍히는 이유
-
When you try to send a XMLHttpRequest to a different domain than the page is hosted, you are violating the same-origin policy. However, this situation became somewhat common, many technics are introduced. CORS is one of them.
In short, server that you are sending the DELETE request allows cross domain requests. In the process, there should be a preflight call and that is the HTTP OPTION call.
So, you are having two responses for the OPTION and DELETE call.
see MDN page for CORS.








