AJAX Login with Spring Security

Posted at 2013. 12. 12. 02:52 | Posted in Framework/Spring Security
반응형

Introduction


스프링 시큐리티를 이용하여 로그인을 처리할 때에 AJAX 방식으로 로그인 하는 방법이다.


크게 2가지로 볼 수 있겠다. ㅎㅎ




Using Handler


기본적인 10단계의 필터 체인 중에 UsernamePasswordAuthencationFilter 단계의 "authentication-success-handler-ref"와 "authentication-success-handler-ref" 를 이용하는 방법이다.


	<security:http auto-config="true" disable-url-rewriting="true" use-expressions="true">
		<security:intercept-url pattern="/login.*" access="permitAll" />		
		<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
		<security:form-login login-page="/login.html"
			username-parameter="id"
			password-parameter="password"
			login-processing-url="/login.ajax" default-target-url="/index.html"
			authentication-success-handler-ref="loginSuccessHandler"
			authentication-failure-handler-ref="loginFailureHandler" />
		<security:logout invalidate-session="true"
			logout-url="/logout"
			logout-success-url="/" />
	</security:http>
	
	<!-- 로그인 성공시 핸들러 -->
	<bean id="loginSuccessHandler" class="ajax.login.security.LoginSuccessHandler" />
	<!-- 로그인 실패시 핸들러 -->
	<bean id="loginFailureHandler" class="ajax.login.security.LoginFailureHandler" />


클래스 구조를 보면 아래와 같다.




아래와 같은 필터 체인의 순서롤 작동하게 된다.




구현 방법은 각각의 핸들러에서 응답을 JSON[각주:1]이나 원하는 포멧으로 만들어서 출력하면 된다.


public class LoginSuccessHandler implements AuthenticationSuccessHandler {
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
		HttpServletResponse response, Authentication authentication) throws IOException,
		ServletException {

		ObjectMapper om = new ObjectMapper();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("success", true);
		map.put("returnUrl", getReturnUrl(request, response));

		// {"success" : true, "returnUrl" : "..."}
		String jsonString = om.writeValueAsString(map);

		OutputStream out = response.getOutputStream();
		out.write(jsonString.getBytes());
	}

	/**
	 * 로그인 하기 전의 요청했던 URL을 알아낸다.
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	private String getReturnUrl(HttpServletRequest request, HttpServletResponse response) {
		RequestCache requestCache = new HttpSessionRequestCache();
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest == null) {
			return request.getSession().getServletContext().getContextPath();
		}
		return savedRequest.getRedirectUrl();
	}

}

public class LoginFailureHandler implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request,
		HttpServletResponse response, AuthenticationException exception)
		throws IOException, ServletException {

		ObjectMapper om = new ObjectMapper();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("success", false);
		map.put("message", exception.getMessage());

		// {"success" : false, "message" : "..."}
		String jsonString = om.writeValueAsString(map);

		OutputStream out = response.getOutputStream();
		out.write(jsonString.getBytes());

	}

}


ajax.login.1.war


ajax.login.1.zip





Using @MVC


다른 방법으로 필터체인을 사용하지 않고 컨트롤러에서 직접 인증을 처리하는 방법이 있다.


개인적으로 이 두번째 방법을 선호한다. 왜냐하면 요청을 "application/json" 으로 유동적으로 한다던가 입출력관련 AOP 등등을 내 맘대로 사용할 수 있다. 그냥 컨트롤러니까~


@Controller
public class LoginController {

	@RequestMapping(value = "/login.html", method = RequestMethod.GET)
	public void loginPage(ModelAndView mav) {

	}

	@Autowired
	AuthenticationManager authenticationManager;
	@Autowired
	SecurityContextRepository repository;

	@RequestMapping(value = "/login.ajax", method = RequestMethod.POST)
	@ResponseBody
	public ModelMap login(HttpServletRequest request, HttpServletResponse response,
		@RequestParam(value = "id") String username,
		@RequestParam(value = "password") String password) {

		ModelMap map = new ModelMap();

		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
			username, password);

		try {
			// 로그인
			Authentication auth = authenticationManager.authenticate(token);
			SecurityContextHolder.getContext().setAuthentication(auth);
			repository.saveContext(SecurityContextHolder.getContext(), request, response);

			map.put("success", true);
			map.put("returnUrl", getReturnUrl(request, response));
		} catch (BadCredentialsException e) {
			map.put("success", false);
			map.put("message", e.getMessage());
		}

		return map;
	}

	/**
	 * 로그인 하기 전의 요청했던 URL을 알아낸다.
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	private String getReturnUrl(HttpServletRequest request, HttpServletResponse response) {
		RequestCache requestCache = new HttpSessionRequestCache();
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest == null) {
			return request.getSession().getServletContext().getContextPath();
		}
		return savedRequest.getRedirectUrl();
	}

}


스프링 시큐리티 설정 하는 부분에서 그냥 <security:http> 를 써도 되지만 사용하지 않는 UsernamePasswordAuthenticationFilter 같은 필터 체인을 사용하지 않기 위해 수동으로 구성해 보았다. 꽤 어렵... -_-;; (자세한 내용은 context-security.xml 참조!)


ajax.login.2.war


ajax.login.2.zip





그 밖에도 요청 헤더를 까서 요청이 AJAX 인지 아닌지를 판단해서 다른 처리를 하는 것도 있고, 구현할 수 있는 방법은 무궁무진하다.


  1. JSON(제이슨, JavaScript Object Notation)은, 인터넷에서 자료를 주고받을 때 그 자료를 표현하는 방법이다. 자료의 종류에 큰 제한은 없으며, 특히 컴퓨터 프로그램의 변수값을 표현하는 데 적합하다. 그 형식은 자바스크립트의 구문 형식을 따르지만, 프로그래밍 언어나 플랫폼에 독립적이므로 C, C++, C#, 자바, 자바스크립트, 펄, 파이썬 등 많은 언어에서 이용할 수 있다. [본문으로]
반응형

'Framework > Spring Security' 카테고리의 다른 글

Spring Security Session Destroy  (4) 2013.10.27
MySql Password Encoder  (0) 2013.09.21
//

JSTL Custom Tag using Spring Beans

Posted at 2013. 12. 1. 17:37 | Posted in Java+/Example
반응형

Introduction


JSTL[각주:1] 태그를 만들어 사용할 때에 스프링 빈을 가져다 쓰는 방법이다.




Using Spring Beans


너무나도 간단하다. 


일반적으로 TagSupport 클래스를 상속 받아서 구현 하면 되는데, 스프링 빈을 사용하고 싶으면 RequestContextAwareTag 클래스를 상속 받아서 구현하면 된다.



사용법은 아래와 같다.


import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.tags.RequestContextAwareTag;

public class XXXTag extends RequestContextAwareTag {

	@Override
	protected int doStartTagInternal() throws Exception {
		// WebApplicationContext 를 얻는다.
		WebApplicationContext ctx = getRequestContext().getWebApplicationContext();
		// 빈을 가져와서 처리
		return SKIP_BODY;
	}

}




Example Source


WAR 파일 (소스 포함)


webapp.war


프로젝트 파일 (메이븐 구조)


webapp.zip




  1. 자바 서버 페이지 표준 태그 라이브러리(JavaServer Pages Standard Tag Library, 약칭 JSTL)은 Java EE 기반의 웹 애플리케이션 개발 플랫폼을 위한 컴포넌트 모음이다. [본문으로]
반응형

'Java+ > Example' 카테고리의 다른 글

LOGBack Configurator with JMX  (0) 2014.07.20
현지어로 언어명 보여주기  (0) 2014.02.09
2014년 도로명 주소 사용에 따른 우편번호 준비  (2) 2013.12.22
Spring Message Source from Database  (1) 2013.03.03
Infinite Routing DataSource  (1) 2013.01.13
Mybatis Type Handler  (1) 2012.09.30
Using AUTO_INCREMENT keys  (0) 2011.03.01
//

Spring Security Session Destroy

Posted at 2013. 10. 27. 12:39 | Posted in Framework/Spring Security
반응형

Intoduction


Spring Security 사용 중에 "사용자의 로그인 시간과 로그아웃 시간을 기록해야 한다" 라는 임무가 떨어졌다 -_-


문제는 이 로그아웃 이라는게 약간 골치 아프다.. 사용자가 직접 로그아웃 버튼을 클릭해서 로그아웃을 한다면 나이스 하지만...


대부분은 용무가 끝나면 그냥 브라우저를 끄거다 바로 다른 사이트로 넘어가게 된다. 이 경우에는 어떻게 하지?


개발자의 눈으로 보면 로그아웃 == 세션 만료로 볼 수 있다.


다른 사이트로 가거나 브라우저를 꺼버리게 되면 WAS의 기준으로 일정 시간이 지나세션이 만료되게 된다.


이 세션 만료를 캐취해서 처리를 하는 방법을 알아보자.




Using HttpSessionListener


서블릿의 세션 리스너를 이용한 방법이다.


아래와 같이 HttpSessionListener 인터페이스를 구현한 클래스를 만들고 web.xml 에 등록하면 된다.


스프링 시큐리티를 사용하지 않는다면 아래의 방법으로 처리하면 된다.


java


package session.destory.servlet;

import java.util.Date;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.springframework.context.ApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.web.context.support.WebApplicationContextUtils;

import session.destory.entity.LoginHistory;
import session.destory.security.LoginToken;
import session.destory.service.LoginHistoryService;

public class SessionManagerListener implements HttpSessionListener {

	@Override
	public void sessionCreated(HttpSessionEvent se) {
		// nothing
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent se) {
		// session
		HttpSession session = se.getSession();
		// spring web application context
		ApplicationContext context = WebApplicationContextUtils
			.getWebApplicationContext(se.getSession().getServletContext());
		// security context
		SecurityContext sc = (SecurityContext) session
			.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
		if (sc != null) {
			// authentication
			Authentication auth = sc.getAuthentication();
			// login history token
			LoginToken loginToken = (LoginToken) auth.getPrincipal();
			LoginHistory lh = loginToken.getHistory();
			lh.setLogoutDate(new Date());
			// update
			LoginHistoryService loginHistoryService = context
				.getBean(LoginHistoryService.class);
			loginHistoryService.modify(lh);
		}

	}

}


web.xml


<!-- servlet listener --> <listener> <listener-class>session.destory.servlet.SessionManagerListener</listener-class> </listener>


리스너에서 수동적으로 스프링 컨택스트와 시큐리티 컨텍스를 꺼내는게 맘에 안드는군... -_-/




Using HttpSessionEventPublisher


이번에는 스프링 시큐리티에서 지원하는 방법으로 해보겠다~!


ApplicationListener<SessionDestroyedEvent> 인터페이스를 구현한 클래스를 만들고 web.xml 에는 HttpSessionEventPublisher 클래스를 등록한다.


java


public class SessionDestoryListener implements ApplicationListener<SessionDestroyedEvent> {

	private LoginHistoryService loginHistoryService;

	@Autowired
	public void setLoginHistoryService(LoginHistoryService loginHistoryService) {
		this.loginHistoryService = loginHistoryService;
	}

	@Override
	public void onApplicationEvent(SessionDestroyedEvent event) {

		List<SecurityContext> contexts = event.getSecurityContexts();
		if (contexts.isEmpty() == false) {
			for (SecurityContext ctx : contexts) {
				Authentication atuh = ctx.getAuthentication().getPrincipal();
				// ...
			}
		}

	}

}

 

spring.xml

 

<beans ...>
	<bean id="sessionDestoryListener" class="session.destory.security.SessionDestoryListener" />
</beans>

 

web.xml


<!-- spring security event -->
<listener>
	<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>


뭔가 스프링스러워진 것 같다 -_-v


- 로그인 처리



- 로그아웃 처리






아래는 샘플 소스다. Tomcat 7, WebLogic 12c 에서 테스트 해봄.


war - 실행해 볼 수 있는 war 파일. 7-zip 으로 분할 압축함.


session.destory.zip.001


session.destory.zip.002



src.zip - Maven 구조로 만들어진 소스 압축함.


session.destory.src.zip


 

... 소스 만들고 나니까 오타가 있네.... Destory → Destroy


반응형

'Framework > Spring Security' 카테고리의 다른 글

AJAX Login with Spring Security  (7) 2013.12.12
MySql Password Encoder  (0) 2013.09.21
//
반응형

이제서야 OSGi 에 관심이 생겨서 뭔가를 만들어보기 위해서 OSGi + Tomcat 6 + SpringDM[각주:1] 개발 환경을 구성해 봅니다.


Eclipse Indigo + Maven Integration for Eclipse





Create Project


File - New - Other... → Maven - Maven Project




[maven-archetype-quickstart] 선택합니다.



[Group Id], [Artifact Id] 아무거나 입력 합니다. -_-;;


[Package] 는 필요가 없습니다.





Create Target Definition


File - New - Other... → Plug-in Development - Target Definition



[Parent Folder]는 전에 만든 "spring-dm-platform" 프로젝트를 선택합니다.


File name: spring-dm-tomcat.target

Initalize the target definition with: Nothing



lib 폴더를 하나 생성합니다.


target 폴더는 메이븐 라이브러리가 복사되는 곳이고, lib 폴더는 메이븐으로 구하지 못한 라이브러를 넣어둘 것입니다.



spring-dm-tomcat.target 파일을 열어 편집합니다.


Target: SpringDM with Tomcat


오른쪽 Add... 버튼을 클릭해서 Locations 을 추가합니다.



Directory 선택



Variables.. 를 이용해서 현재 프로젝트 경로(project_loc)를 얻어내고 하위 폴더 target 을 입력합니다. (그냥 입력해도 됨..)



같은 방법으로 lib 폴더도 선택합니다.



파일 저장 후 Windows - Preferences - Plug-in Development - Target Platform


SpringDM with Tomcat 을 선택합니다.






Running OSGi Framework


이제 한번 실행시켜 봅시다.


Run - Run Configurations...



왼쪽 메뉴 중에 OSGi Framework 에서 오른쪽 버튼 클릭 - New



이름 적당히 지어 주시고 Run 을 클릭해 봅시다.



org.eclipse.osgi 플러그인이 없다고 에러가 납니다.





Setting Osgi Framework on Maven


이제 메이븐으로 OSGi Framework 라이브러리를 세팅합시다.


pom.xml 파일을 열어 아래와 같이 수정 합니다.



메이븐에 대해서 설명하지는 않겠습니다. -_-;;


위의 라이브러리 저장소(repository)는 SpringSource Repository(FAQ)에서 확인할 수 있습니다.


이제 SpringSource Repository에서 번들(라이브러리)를 하나씩 찾아서 넣어야 합니다. ㅠ_ㅠ;;


검색하는 방법은 [Advanced Search]나 오른쪽 상단 [Quick Search] 을 이용해서 검색하면 됩니다.



일단 OSGi Framework 실행 했을 때 없다고한 플러그인 라이브러리를 검색해봅시다.


검색해서 나온 버전 중 가장 최신 버전을 선택합니다.



중간에 보면 Maven 부분이 있습니다. 이걸 복사해서 <dependencies> </dependencies> 사이에 붙여넣기 하면 됩니다. ㅎㅎ



<dependencies>

<!-- 라이브러리 설정 -->

<dependency>

<groupId>org.eclipse.osgi</groupId>

<artifactId>org.eclipse.osgi</artifactId>

<version>3.7.1.R37x_v20110808-1106</version>

</dependency>

</dependencies>


저장 후 pom.xml 파일에서 오른쪽버튼 클릭 후 Run As - Maven install 클릭 합니다.



그러면 target 폴더에 jar 파일이 복사가 됩니다.



이제 spring-dm-tomcat.target 파일을 열어봅시다.


아래와 같이 번들이 추가 되어있습니다.


※ 메이븐을 이용해서 빠르게 라이브러리를 바꿔치기 하다보면 번들(라이브러리)이 갱신이 안될 때가 있는데 그냥 파일 닫았다가 다시 열어주면 갱신이 되더군요....



맨위의 타이틀 오른쪽에 "Set as Target Platform"을 클릭해야 실제 적용이 됩니다. 주의!



이제 다시 Run Configurations 로 가봅시다. 추가한 번들이 올라와 있습니다. 선택 후 Run!



콘솔 창을 보면 OSGi Framework 가 실행되었습니다. ㅠ_ㅠ



이제 OSGi 할 준비가 된 것입니다. ㅋㅋ


톰켓이나 웹로직 같이 끄려면 빨간 버튼(Terminate)을 이용해서 끄면 됩니다.


나중에 톰켓를 띄우게 될텐데 OSGi Framework 한번 실행한 상태에서 한번 실행하면 톰켓 포트 충돌로 인한 에러를 볼 수 있습니다.




Setting SpringDM on Maven


바로 SpringDM을 적용해 봅시다!!


SpringSource Repository 에서 "spring", "spring-osgi", "slf4j" 를 검색해서 최신 버전을 적용 합니다.



이제 pom.xml 에서 마우스 오른쪽 버튼 - Run As - Maven install 해서 jar 파일을 target 에 생기게 합니다.


그리고 spring-dm-tomcat.target 파일을 열어서 ${project_loc}/target 폴더에 플러그인 갯수가 늘어났는지 확인합니다.


만약 파일은 존재하는데 변경이 되지 안았다면 spring-dm-tomcat.target 파일 닫고 바로 다시 열어주면 카운트가 변경 되어 있을 겁니다.



상단 오른쪽의 "Set as Target Platform" 링크를 클릭하여 라이브러리를 적용시켜 줍니다.



Run Configurations 창으로 가서 추가된 번들(또는 라이브러리)를 선택합니다.


그 후 오른쪽 아래 "Validate Bundles" 버튼을 클릭합니다. 버튼명 그대로 번들들의 유효성을 검사하게 됩니다.



아래와 같이 어떤 라이브러리에서 필요한 라이브러리가 빠져있는지 나옵니다.



웹에 관련된 부분은 바로 이어서 할 것이므로 일단 이 번들은 빼고 구동(Run)합니다.



아래와 같이 나오면 성공! 웹쪽을 뺀 SpringDM 까지 올라갔습니다.


osgi> log4j:WARN No appenders could be found for logger (org.springframework.osgi.extender.internal.activator.ContextLoaderListener).

log4j:WARN Please initialize the log4j system properly.

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


osgi> ss


Framework is launched.


id State       Bundle

0 ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106

1 ACTIVE      org.springframework.transaction_3.2.1.RELEASE

2 ACTIVE      org.springframework.osgi.core_1.2.1

3 ACTIVE      org.springframework.osgi.extensions.annotations_1.2.1

4 ACTIVE      com.springsource.org.apache.log4j_1.2.16

5 ACTIVE      org.springframework.jdbc_3.2.1.RELEASE

6 ACTIVE      org.springframework.oxm_3.2.1.RELEASE

7 ACTIVE      org.springframework.aspects_3.2.1.RELEASE

8 ACTIVE      com.springsource.org.apache.commons.logging_1.1.1

9 ACTIVE      org.springframework.context.support_3.2.1.RELEASE

10 ACTIVE      org.springframework.core_3.2.1.RELEASE

11 ACTIVE      org.springframework.beans_3.2.1.RELEASE

12 ACTIVE      org.springframework.context_3.2.1.RELEASE

13 ACTIVE      org.springframework.aop_3.2.1.RELEASE

14 RESOLVED    com.springsource.slf4j.log4j_1.6.1

           Master=17

15 ACTIVE      org.springframework.expression_3.2.1.RELEASE

16 ACTIVE      org.springframework.orm_3.2.1.RELEASE

17 ACTIVE      com.springsource.slf4j.api_1.6.1

           Fragments=14

18 ACTIVE      com.springsource.org.aopalliance_1.0.0

19 ACTIVE      org.springframework.osgi.extender_1.2.1

20 ACTIVE      org.springframework.osgi.io_1.2.1


osgi> 




Configuration Log4j


OSGi Framework 를 실행 하면 아래와 같은 경고가 나오게 되는데 흔하디 흔한 log4j.properties / log4j.xml 파일 못 찾는 경고입니다.


log4j:WARN No appenders could be found for logger (org.springframework.osgi.extender.internal.activator.ContextLoaderListener).

log4j:WARN Please initialize the log4j system properly.

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.


log4j.xml


첨부한 log4j.xml 파일을 프로젝트 폴더에 복사합니다.



Run Configurations 창의 Arguments 탭으로 이동합니다.


VM arguments 부분에 -Dlog4j.debug=true -Dlog4j.configuration=file:log4j.xml 를 추가합니다.


Working directory 부분은 현재 자기의 프로젝트 경로로 바꿔줍니다.



Workspace... 버튼 클릭 후 spring-dm-platform 프로젝트 폴더를 선택하면 됩니다.



이제 OSGi Framework 를 실행하면 로그가 출력 됩니다.


osgi> log4j: Using URL [file:log4j.xml] for automatic log4j configuration.

log4j: Preferred configurator class: org.apache.log4j.xml.DOMConfigurator

log4j: System property is :null

log4j: Standard DocumentBuilderFactory search succeded.

log4j: DocumentBuilderFactory is: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

log4j: debug attribute= "null".

log4j: Ignoring debug attribute.

log4j: reset attribute= "false".

log4j: Threshold ="null".

log4j: Retreiving an instance of org.apache.log4j.Logger.

log4j: Setting [org.springframework.osgi.web.tomcat.internal] additivity to [false].

log4j: Level value for org.springframework.osgi.web.tomcat.internal is  [debug].

log4j: org.springframework.osgi.web.tomcat.internal level set to DEBUG

log4j: Class name: [org.apache.log4j.ConsoleAppender]

log4j: Parsing layout of class: "org.apache.log4j.PatternLayout"

log4j: Setting property [conversionPattern] to [%d _ %t _ %p _ %c _ %m%n].

log4j: Adding appender named [console] to category [org.springframework.osgi.web.tomcat.internal].

log4j: Retreiving an instance of org.apache.log4j.Logger.

log4j: Setting [org.springframework] additivity to [false].

log4j: Level value for org.springframework is  [info].

log4j: org.springframework level set to INFO

log4j: Adding appender named [console] to category [org.springframework].

log4j: Level value for root is  [warn].

log4j: root level set to WARN

log4j: Adding appender named [console] to category [root].

2013-03-27 00:54:43,505 _ Start Level Event Dispatcher _ INFO

_ org.springframework.osgi.extender.internal.activator.ContextLoaderListener

_ Starting [org.springframework.osgi.extender] bundle v.[1.2.1]

2013-03-27 00:54:43,545 _ Start Level Event Dispatcher _ INFO

_ org.springframework.osgi.extender.internal.support.ExtenderConfiguration

_ No custom extender configuration detected; using defaults...

2013-03-27 00:54:43,550 _ Start Level Event Dispatcher _ INFO

_ org.springframework.scheduling.timer.TimerTaskExecutor _ Initializing Timer


osgi> 


여기까지 했으면 이제 웹을 제외한 SpringDM을 사용할 준비가 되었습니다.




Setting Web Platform


이제 거의 다 왔습니다.... 후우~.. 이제 웹도 되게 합시다!



Run Configurations 창에서 전 단계에서 선택하지 않았던 4개의 웹 관련 번들을 선택 후 검사합니다. (Validate Bundles 버튼)



아래에 나온 없는 패키지가 포함된 번들을 찾아줘야 합니다.



일단 서블릿 2.4 버전을 찾아서 넣어줍시다.



이렇게 추가된 라이브러리를 적용한 후 다시 Run Configurations 창을 엽니다.


Add Required Bundles 버튼을 클릭하면 필요한 라이브러리를 자동으로 선택 해줍니다. (만약 있다면..)



이제 번들 유효성 검사를 해보면 아무 이상이 없다고 합니다.



실행! 잘 되는듯 하다가 에러가 나는군요...


2013-03-28 23:20:49,878 _ WebExtender-Init _ INFO

_ org.springframework.osgi.web.extender.internal.activator.WarListenerConfiguration

_ No custom extender configuration detected; using defaults...

Exception in thread "WebExtender-Init" java.lang.NoClassDefFoundError: org/apache/catalina/Loader

at o.s.osgi.web.extender.internal.activator.WarListenerConfiguration.createDefaultWarDeployer(WarListenerConfiguration.java:194)

at org.springframework.osgi.web.extender.internal.activator.WarListenerConfiguration.<init>(WarListenerConfiguration.java:105)

at org.springframework.osgi.web.extender.internal.activator.WarLoaderListener$1.run(WarLoaderListener.java:361)

at java.lang.Thread.run(Unknown Source)

Caused by: java.lang.ClassNotFoundException: org.apache.catalina.Loader

at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:513)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:429)

at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)

at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)

at java.lang.ClassLoader.loadClass(Unknown Source)

... 4 more


톰켓 번들을 설치합시다.


내용이 너무 길어저 pom.xml 만 남깁니다.



번들(라이브러리) 적용 후 우효성 검사를 하면 이상이 없다고 합니다.


보면 서블릿 번들이이 2.4/2.5 두개가 있는데 이중에 한가지만 선택하면 되겠습니다.



이제 실행해 봅시다.


WebExtender-Init _ ERROR _ o.s.osgi.web.deployer.tomcat.TomcatWarDeployer _ No Catalina Service found, bailing out

o.s.osgi.service.ServiceUnavailableException: service matching filter=[(objectClass=org.apache.catalina.Service)] unavailable


WebExtender-Init _ ERROR _ o.s.osgi.web.extender.internal.activator.WarLoaderListener _ Cannot property start Spring DM WebExtender; stopping bundle...

o.s.osgi.OsgiException: Cannot create Tomcat deployer

Caused by: o.s.osgi.service.ServiceUnavailableException: service matching filter=[(objectClass=org.apache.catalina.Service)] unavailable


WebExtender-Init _ INFO _ org.springframework.scheduling.timer.TimerTaskExecutor _ Cancelling Timer


org.apache.catalina.Service 서비스를 찾을 수 없다. 즉 톰켓이 안 떠있다는 겁니다.


catalina.start.osgi-6.0.16-20080327.121306-4.jar


첨부한 번들을 lib 폴더에 복사해주고 적용 시킵니다. (이건 메이븐에 없더군요...)



마지막으로 실행!


Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Starting Apache Tomcat/6.0.32 ...

Tomcat Catalina Start Thread _ DEBUG _ o.s.osgi.web.tomcat.internal.Activator _ Reading default server configuration from  bundleresource://45.fwk1110027070/conf/embedded-server-defaults.properties

2013. 3. 28 오후 11:40:53 org.apache.catalina.startup.Embedded start

정보: Starting tomcat server

2013. 3. 28 오후 11:40:53 org.apache.catalina.core.StandardEngine start

정보: Starting Servlet Engine: Apache Tomcat/6.0.32

2013. 3. 28 오후 11:40:53 org.apache.coyote.http11.Http11Protocol init

정보: Initializing Coyote HTTP/1.1 on http-127.0.0.1-8080

2013. 3. 28 오후 11:40:53 org.apache.coyote.http11.Http11Protocol start

정보: Starting Coyote HTTP/1.1 on http-127.0.0.1-8080

Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Succesfully started Apache Tomcat/6.0.32

WebExtender-Init _ INFO _ o.s.osgi.service.importer.support.OsgiServiceProxyFactoryBean _ Found mandatory OSGi service for bean []

Tomcat Catalina Start Thread _ INFO _ o.s.osgi.web.tomcat.internal.Activator _ Published Apache Tomcat/6.0.32 as an OSGi service

WebExtender-Init _ INFO _ o.s.osgi.web.deployer.tomcat.TomcatWarDeployer _ Found service Catalina


후우... 성공!


web.xml


마지막으로 첨부한 web.xml 파일을 conf/web.xml 에 복사합니다. (그냥 톰켓에 있는 web.xml 입니다)



이 파일이 있어야 나중에 웹어플 배치했을 때 정상적으로 됩니다.


안그러면 배치했을 때 아래 로그와 함께 영원히 404를 볼 수 있습니다.


[ INFO - WebExtender-Init] org.springframework.osgi.web.extender.internal.activator.WarLoaderListener - hello.web (hello.web) is a WAR, scheduling war deployment on context path [/hello.web] (web.xml found at [bundleentry://11.fwk854535264/WEB-INF/web.xml])

2013. 3. 30 오전 2:03:35 org.apache.catalina.startup.ContextConfig defaultWebConfig

정보: No default web.xml

[ INFO - Timer-0] org.springframework.osgi.web.deployer.tomcat.TomcatWarDeployer - Successfully deployed bundle [hello.web (hello.web)] at [/hello.web] on server org.apache.catalina.startup.Embedded/1.0




Trouble Shooting


설정을 바꿨는데도 안바뀐다면?


Run Configurations 의 Settings 탭에서 "Clear the configuration area before launching"를 체크해주세요.



중간 중간 아래와 같은 에러가 난다면? 하라는 대로 하면 됩니다. -_-;;





마지막으로 최종 pom.xml 파일 첨부합니다.


pom.xml



  1. 구) Spring OSGi / 신) Spring Dynamic Modules [본문으로]
반응형

'Framework > Spring OSGi' 카테고리의 다른 글

SpringDM Test Project - hello.osgi  (0) 2013.04.07
//

Spring Message Source from Database

Posted at 2013. 3. 3. 05:19 | Posted in Java+/Example
반응형

스프링을 이용한 다국어 처리를 데이터베이스를 이용해 어떻게 할까 이것저것 고민하다가 구현해 봤습니다.


개발 환경

 - Eclipse Indigo + Maven Integration (Sonatype, Inc.)

 - WebLogic 12c (Tomcat 7)


테이블은 아래와 같이 구성하였습니다.




특별한 것은 없고 여느 책에서나 나오는 스프링에서 MessageSource를 사용하여 다국어 메세지를 가져오는데 AbstractMessageSource 클래스를 상속 받아 따로 클래스를 만들어 봤습니다. (com.tistory.antop.framework.support.DatabaseMessageResource)




구동해 볼 수 있는 샘플을 만들어 봤습니다.


실제 메세지 리소스 부분만 구현하는 시간은 금방인데 하나의 완성품을 만들면서 더 많이 배우는군요~


Spring Framework 3.2.1

Mybatis 3.2 + Ehcache 2.6.3

Tiles 2.2.2 + Dynamic Tiles 1.2.1

HSQLDB 2.2.9

 

http://antop.nerv.kr/multi-lang/

 

데이터를 제외한 모든 부분을 다국어 처리 해봤습니다영어(기본), 한국어, 일어 데이터를 만들어 놨습니다. 구글 번역!


언어를 한글로 했을 때의 화면입니다.




아래는 언어를 영문으로 했을 때




자바스크립트 메세지 부분도 다국어 처리 해봤습니다. (/js/locale.js)






이클립스 프로젝트 파일입니다. 모든 파일 인코딩은 UTF-8 입니다.


multi-lang.zip


war 파일입니다. 16메가의 용량이 다 라이브러리군요... -_-/


multi-lang.zip.001

multi-lang.zip.002



반응형

'Java+ > Example' 카테고리의 다른 글

LOGBack Configurator with JMX  (0) 2014.07.20
현지어로 언어명 보여주기  (0) 2014.02.09
2014년 도로명 주소 사용에 따른 우편번호 준비  (2) 2013.12.22
JSTL Custom Tag using Spring Beans  (0) 2013.12.01
Infinite Routing DataSource  (1) 2013.01.13
Mybatis Type Handler  (1) 2012.09.30
Using AUTO_INCREMENT keys  (0) 2011.03.01
//

Infinite Routing DataSource

Posted at 2013. 1. 13. 18:54 | Posted in Java+/Example
반응형

스프링을 이용하여 몇개의 데이터소스를 정해놓고 라우팅을 하는 경우 AbstractRoutingDataSource를 이용할 수 있었습니다.


하지만 경우에 따라 접속해야하는 데이터베이스가 무한대일 경우?



그래서 이것저것 생각해 봤는데 -_-.... 


로그인시에 [고객 코드], [아이디], [비밀번호] 3가지를 입력 받습니다.


[고객코드]로 고객을 찾은 후 고객의 정보로 디비 정보를 만든 후 ThreadLocal에 저장합니다.


그 후 로그인 처리를 합니다.


로그인 시에 데이터소스를 사용할 때 ThreadLocal에 있는 디비 정보(DbInfo 객체)로 데이터소스를 만들거나 캐쉬에서 가져오게 합니다.


로그인 때에 처음 그 데이터베이스에 접속을 하게 되니까 RoutingDataSource 부분에서 데이터소스를 처음 만들게 될 것입니다.



로그인 처리 이후에는 인터셉터를 이용하여 세션에 있는 디비 정보를 ThreadLocal에 넣고 사용하면 되겠습니다.



이렇게 하면 똑같은 쿼리를 사용하면서 [코객코드]에 따라 다른 디비에 붙어서 사용할 수 있겠네요 ㅠ_ㅠ




Spring + Spring Security + Mybatis + SQLite 를 이용해서 샘플을 만들어 봤습니다.


아래와 같이 3개의 고객 스키마가 있습니다.



아래 테이블은 manager 스키마의 고객 관리 테이블 입니다.



비밀번호는 SALT 와의 조합으로 ARIA 알고리즘으로 저장됩니다.


SQLite는 파일 디비라서 직접적으로 아이디와 비밀번호는 사용하지 않습니다.




아래 테이블은 각각 고개 스키마에 있는 사용자 관리 테이블입니다. 암호는 MySQL 의 password() 알고리즘으로 저장됩니다.



계정은 각각 아래와 같이 들어 있습니다. 스키마명은 고객코드와 동일하게 했습니다.


cust001 (비밀번호: 12345 , welcome1!)



cust002 (비밀번호: jsnot)



cust003 (비밀번호: 11111)





핵심 소스 부분 몇 개 살펴보면...


package com.tistory.antop.service.common.impl;


public class LoginSoImpl implements LoginSo {


private AccountMapper accountMapper;


@Override

public Authentication login(String accountId, String username,

String password) {

// 고객 정보 조회

Account account = accountMapper.selectById(accountId);


// 없거나 사용 중지인 경우 예외

if (account == null || account.isUse() == false) {

throw new AccountNotFoundException();

}


// 디비 정보를 만듬

DbInfo dbInfo = new DbInfo();

dbInfo.setUsername(account.getId());

dbInfo.setEncPass(account.getEncPass());

dbInfo.setSalt(account.getSalt());


// ThreadLocal 에 저장

DbInfoContextHolder.set(dbInfo);


// Spring Security 인증

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(

username, password);

token.setDetails(account);


Authentication auth = authenticationManager.authenticate(token);


return auth;

}

}



아래 소스는 o.s.jdbc.datasource.lookup.AbstraceRoutingDataSource 클래스를 상속받아 구현한 RoutingDataSource 입니다.


캐쉬 역할을 하는 객체는 동기화를 위해 Hashtable을 사용하였습니다.


package com.tistory.antop.framework.datasource;


abstract public class RoutingDataSource extends AbstractRoutingDataSource {


private String url;

private String driverClassName;

private PasswordDecoder passwordDecoder;


private Map<DbInfo, DataSource> dataSources = new Hashtable<DbInfo, DataSource>();


@Override

protected DbInfo determineCurrentLookupKey() {

return DbInfoContextHolder.get();

}


@Override

protected DataSource determineTargetDataSource() {


DbInfo dbInfo = determineCurrentLookupKey();


if (dbInfo == null) {

throw new RoutingDataSourceException("데이터베이스 정보가 없습니다.");

}


try {

// 데이터소스가 없으면 생성

if (!contains(dbInfo)) {

// 비밀번호 복호화

String password = passwordDecoder.decodePassword(

dbInfo.getEncPass(), dbInfo.getSalt());

DataSource ds = createDataSource(dbInfo.getUsername(), password);

add(dbInfo, ds);

}


// 데이터소스 리턴

return get(dbInfo);

} catch (Exception e) {

throw new RoutingDataSourceException(e);

}


}


// 데이터 소스를 만드는 부분은 상속받은 객체가 함

abstract public DataSource createDataSource(String username, String password);

)




WAR 파일


바로 배치 가능한 WAR 파일입니다.


routing_datasource.zip.001


routing_datasource.zip.002



소스 ZIP 파일


이클립스 메이븐 구조의 프로젝트 소스입니다.


routing_datasource.zip



개발 테스트 : 톰켓 7, 웹로직 12c


배포 테스트 : 웹로직 12c




※ 질문!!!


웹로직에서 사용할 때 컨텍스트루트 다음에 "/" 가 붙느냐 안붙느냐에 따라서 스프링 시큐리티 필터를 타고 안타고 다른 결과가 나옵니다...


 URL  정상 처리 여부

 http://localhost:7001/routing_datasource/

 정상 처리 됨
 http://localhost:7001/routing_datasource

 스프링 시큐리티 필터를 타지 못하고 그냥 넘어가서 에러


톰켓에서는 "/" 상관없이 정상적으로 됨.... 이 부분 해결법 아시는분.. ㅠㅠ



반응형
//

Proftpd Manager

Posted at 2012. 12. 10. 05:11 | Posted in Mini Project
반응형

드디어 오랜기간의 방황을 깨고 마무리가 되었습니다. ㅠ_ㅠ


빠진 기능도 있고 버그도 있겠지만(?) 다음 과제(?)를 위해서 마무리~




소개...

 

Ubuntu 에 Proftpd 와 Mysql 을 이용해서 사용자 관리와 파일 전송 기록을 남길 수가 있습니다.

 

http://www.sysadminworld.com/2011/install-proftpd-with-mysql-backend-on-debian-ubuntu/

 

 

위와 같은 테이블로 관리를 하게 됩니다.

 

유저와 그룹을 관리하고 쌓이기만 하는 전송 기록을 볼 수 있는 웹어플을 한번 만들어 봤습니다.

 

※ 원래 데이터베이스는 MySQL 인데 샘플을 위해서 SQLite 로 데이터를 옮겼습니다.

 

 


 

사용한 기능...

 

Spring Framework 3

  - Core, AOP, MVC

 

Spring Security 3

  - AuthenticationManager 를 이용한 인증 처리

  - @PreAuthorize 어노테이션을 사용한 서비스단 메소드 보안

  - MySQL 의 password() 로직을 사용한 PasswordEncoder

 

MyBatis 3

  - List<String> ↔ 콤마 구분 문자열 타입 핸들러

  - Date ↔ datetime 타입 핸들러

 

Ext JS 4

  - 컴포넌트 동적 로딩 (필요한 클래스 js 파일을 그때그때 AJAX 로 로드)

  - 페이징 가능한 Tree Grid

  - 플러그인

    - FilterBar, MultiSorting, PagingToolbarResizer 등... -_-;




화면 설명... 


최초 페이지 로딩 후 전송 이력(History)에서 시작합니다. 데이터를 불러오려다가 로그인이 안되어 있어서 인증창이 뜹니다.



로그인이 성공하게 되면 데이터를 불러옵니다.


파일명과 접속 아이피는 문제가 될 소지가 있어서 이상하게 변환시켜 놨습니다 -_-;;;


 

아래는 사용자 관리 화면입니다. 사용자를 추가/수정/삭제 할수 있습니다.



사용자의 권한이 "관리자"만 그룹/사용자 정보를 관리할 수 있습니다. 그냥 "사용자"는 변경하려고 하면 권한이 없다고 나옵니다.



아래는 그룹 관리 화면입니다. 그룹을 추가/수정/삭제 하고, 사용자를 그룹에 포함/제외 시킬 수 있습니다.





소스 다운로드...


proftpd-maven.zip


위 파일은 이클립스 프로젝트 파일입니다. 메이븐 구조로 되어있습니다.


proftpd.war.zip.001


proftpd.war.zip.002



위 2개 파일은 war 파일을 7-zip으로 분할 압축 해놓은 겁니다.


압축 해제 후 나온 war 파일을 바로 배치 후 볼 수 있습니다. (소스 파일들도 들어있슴)

반응형

'Mini Project' 카테고리의 다른 글

Image Pyramid  (0) 2014.01.30
게시판 2.0  (5) 2011.07.27
Sudoku  (0) 2011.06.09
간단한 JAVA 실행기(?)  (1) 2011.03.21
Forecast Repository  (8) 2010.11.30
Sales History API  (2) 2010.05.27
Order Entry API  (0) 2010.05.13
Human Resources API  (0) 2010.05.10
게시판 1.0  (1) 2010.03.11
비밀번호 생성기(Generate Password)  (1) 2010.02.10
//

Mybatis Type Handler

Posted at 2012. 9. 30. 12:49 | Posted in Java+/Example
반응형

마이바티스를 이용하여 자바 객체와 데이터베이스 테이블을 매핑할 때 서로 완전히 다른 타입의 것들을 사용할 때가 있습니다.

 

그 예로 대표적인 예가 자바의 Boolean 과 데이터베이스의 플래그 문자입니다.

 

 

오라클 같은경우 boolean 타입이 없기 때문에 보통 CHAR(1) 잡고 Y/N 값을 많이 사용합니다.

 

다른 경우는 1 or 0 을 사용하여 참/거짓, 사용/미사용, 차단/허용 같은 on/off 플래그 값을 사용합니다.

 

자바 

데이터베이스 

boolean

CHAR(1)

INTEGER  

참 / 사용

true

Y

 1 

거짓 / 미사용

false

N

 0 

 

org.apache.ibatis.type.TypeHandler 인터페이스를 이용해서 서로 다른 타입을 연결할 수 있습니다.

 

package com.tistory.antop.mybatis.handler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.log4j.Logger;

public class StringYnTypeHandler implements TypeHandler<Boolean> {

    private Logger logger = Logger.getLogger(getClass());

    public Boolean getResult(ResultSet rs, String columnName) throws SQLException {
        String s = rs.getString(columnName);

        return parseBoolean(s);
    }

    public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException {
        String s = rs.getString(columnIndex);

        return parseBoolean(s);
    }

    public Boolean getResult(CallableStatement cs, int columnIndex)
        throws SQLException {
        String s = cs.getString(columnIndex);

        return parseBoolean(s);
    }

    public void setParameter(PreparedStatement ps, int i, Boolean bool,
        JdbcType jdbcType) throws SQLException {

        ps.setString(i, parseString(bool));
    }

    // "Y" or "N" -> true or false
    private boolean parseBoolean(String s) {
        boolean bool = false;

        if (s == null) {
            return false;
        }

        s = s.trim().toUpperCase();

        if (s.length() == 0) {
            return false;
        }

        // allow "Y" or "N"
        if ("Y".equals(s) == false && "N".equals(s) == false) {
            throw new PersistenceException("value must be \"Y\" or \"N\".");
        }

        bool = "Y".equals(s);

        if (logger.isDebugEnabled()) {
            logger.debug("\"" + s + "\" -> " + bool);
        }

        return bool;
    }

    // true or false -> "Y" or "N"
    private String parseString(Boolean bool) {
        String s = (bool != null && bool == true) ? "Y" : "N";

        if (logger.isDebugEnabled()) {
            logger.debug(bool + " -> " + "\"" + s + "\"");
        }

        return s;
    }
}

 

이 클래스를 이용하면 자바에서는 boolean 타입을 사용하면 데이터베이스에서는 Y or N 값이 들어가게 됩니다.

 

mybatis.typehandler.zip

 

위 샘플은 메이븐 프로젝트 구조입니다.

 

샘플을 만드는 도중에 sqlite3 는 이상한게 TIMESTAMP 타입을 java.util.Date 클래스와 매핑 하면 들어갈 때는 그대로 들어가는데 조회할 때는 java.util.String 이 나오는군요....

 

그래서 TIMESTAMP 에 대한 핸들러 하나 더 만들었습니다.

 

테스트는 com.tistory.antop.mybatis.TypeHandlerTest 클래스 입니다. JUnit 입니다.

 

실행시 Run Configurations 중 VM 옵션에 이래와 같이 "-Dlog4j.configuration=config/properties/log4j.properties" 를 추가해 줘야 로그를 볼 수 있습니다.

 

 

 

반응형
//