Mockito.thenThrow() 주의사항!

Posted at 2021. 9. 25. 03:23 | Posted in Framework/Spring
반응형

예외를 던지도록하는 테스트를 할 때 Exception 안에 메시지를 활용할 경우 Exception.class 를 사용하지말 고 new Exception()을 사용하자.

 

일단 예외 클래스를 보자

/**
 * 이미 종료된 대회일 때 예외
 *
 * @author jammini
 */
public class AlreadyContestEndException extends BadRequestException {
    public AlreadyContestEndException() {
        super("이미 종료된 대회입니다.");
    }
}

 

서비스를 모킹해서 예외를 던지도록 할 것이다.

@WebMvcTest(ContestInfoApi.class)
class ContestInfoApiTest extends WebMvcBase {
    @MockBean
    private ContestService contestService;
    
    @Test
    void alreadyContestEnd() throws Exception {
        // when
        // AlreadyContestEndException.class
        when(contestService.modify(anyLong(), any())).thenThrow(AlreadyContestEndException.class);
        
        // ...
    }
    
 }

 

@ControllerAdvice 를 이용해 예외 클래스 안의 message 속성을 사용할 경우!

/**
 * 웹 예외 핸들러<br>
 * 시스템상에 발생하는 예외를 잡아서 공통으로 처리한다.
 *
 * @author antop
 */
@Slf4j
@RestControllerAdvice
public class ErrorAdvisor {

    /**
     * 400 Bad Request 예외 처리
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BadRequestException.class)
    ErrorMessage badRequest(Exception e) {
        log.debug("message = {}", e.getMessage());
        return ErrorMessage.badRequest(e.getMessage());
    }
    
  }

 

message는 null이 출력되게 된다. thenThrow() 인자로 new Exception() 을 사용하자.

when(contestService.modify(anyLong(), any())).thenThrow(new AlreadyContestEndException());

 

 

 

 

 

반응형

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

Spring + @Lazy  (0) 2019.08.05
Twelve Best Practices For Spring XML Configurations  (0) 2010.06.23
//

Spring + @Lazy

Posted at 2019. 8. 5. 00:46 | Posted in Framework/Spring
반응형

https://github.com/antop-dev/spring-lazy

요즘 MSA를 공부하면서 아래와 같은 문구를 보았다.

마이크로서비스에서는 완전 자동화를 달성하기 위해 초소한의 시동/종료 시간을 갖도록 애플리케이션의 크기를 가능한 한 작게 유지하는 것이 극단적으로 아주 중요하다.
이를 위해 마이크로서비스에서는 객체와 데이터의 지연 로딩lazy loading에 대해서도 고려해봐야 한다.

이 때 떠오른 단어는 Spring@Lazy이다.

스프링 빈 설정시 @Lazy 애노테이션만 달아주면 이 빈을 가져오는 시점에 생성하기 때문에 모든 빈을 처음 초기화시에 만들지 않는다.

그런데 약간의 의문사항이 있어서 테스트 해봤다... 구글링을 대충 해도 나오는 자료이지만 직접 해봤다.

등장 클래스

아래와 같이 3개의 클래스를 빈으로 등록했다. 패턴은 다 같고 First, Second, Third 만 바뀐다.

@Component
public class First {
    @Autowired
    private Second second;

    public First() {
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}

테스트 코드는 아래와 같다.

public class LazyApplicationTests {
    @Test
    public void lazy() {
        ApplicationContext context = new AnnotationConfigApplicationContext(LazyApplication.class);
        System.out.println("call first.go()");
        First first = context.getBean(First.class);
        first.go();
    }
}

일반적인 빈 설정

먼저 위와 같이 @Lazy를 사용하지 않는 일반 적인 설정이다. 후에 스프링 컨텍스트에서 First 인스턴스를 꺼내서 go() 메서드를 수행할 것이다.

예상한 대로 빈이 전부 생성 된 후(①) FIrst.go() 이후의 작업이 수행된다(②).

first class constructor
second class constructor
third class constructor
Spring context loaded call
first.go()
Hello first!
Hello second
Hello third!

하지만 좀 이상한 점이 있는데 예상은 ThridSecondFirst 순으로 생성될 줄 알았지만 반대로 생성된다. 생성자를 사용하지 않고 @Autowired나 세터setter를 사용하면 스프링이 이 클래스를 까서(?) 적용을 하게 된다.

@Autowired를 사용하지 않고 생성자constructor를 사용하면 예상한 순서대로 빈이 만들어진다.

@Component
public class First {
    private final Second second;

    public First(Second second) {
        this.second = second;
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}
third class constructor second class constructor first class constructor Spring context loaded call first.go() Hello first! Hello second Hello third!

@Lazy 적용

첨부파일
다운로드

Thrid, Second, Fourth 는 스프링 초기화시 생성되고 First는 지연 로딩이 적용된다.

third class constructor
second class constructor
fourth class constructor
Spring context loaded
call first.go()
first class constructor
Hello first!
Hello second
Hello third!

동시성 concurrency

First 빈이 생성 되는데 3초가 걸린다고 가정해보자.

@Component
@Lazy
public class First {
    private final Second second;

    public First(Second second) {
        this.second = second;
        // 인스턴스를 생성하는데 3초가 걸린다.
        System.out.println("It takes 3 seconds to create an instance");
        try {
            for (int i = 0; i < 3; i++) {
                System.out.println((3 - i) + "..");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}

First 인스턴스를 사용하는 다른 인스턴스에서 동시에(거의 동시에?) 요청이 들어온다면?

테스트 코드는 아래와 같다.

@Test
    public void concurrency() throws Exception {
        ApplicationContext context = new AnnotationConfigApplicationContext(LazyApplication.class);
        System.out.println("Spring context loaded");

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("call first.go()");
                First first = context.getBean(First.class);
                first.go();
            });

            thread.start();
            System.out.println(thread.getName() + " started.");
            Thread.sleep(100);
        }

        // 결과를 보기 위해 5초 대기
        Thread.sleep(5000);
    }

걱정과는 다르게 매우 잘 작동한다! 스프링 구욷!

third class constructor
second class constructor
fourth class constructor
Spring context loaded
Thread-2 started.
Thread-2 call first.go()
It takes 3 seconds to create an instance
3..
Thread-3 started.
Thread-3 call first.go()
Thread-4 started.
Thread-4 call first.go()
2..
1..
first class constructor
Thread-2 Hello first!
Thread-4 Hello first!
Thread-2 Hello second
Thread-4 Hello second
Thread-4 Hello third!
Thread-3 Hello first!
Thread-3 Hello second
Thread-3 Hello third!
Thread-2 Hello third!
반응형

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

Mockito.thenThrow() 주의사항!  (0) 2021.09.25
Twelve Best Practices For Spring XML Configurations  (0) 2010.06.23
//
반응형

https://github.com/antop-dev/example/tree/master/mybatis-rowbounds-example

 

Mybatis에 RowBounds 클래스를 이용해서 페이징 처리를 할 수 있다.

package org.antop.mybatis.mapper;
import org.antop.mybatis.model.Employee;
import org.apache.ibatis.session.RowBounds;
import java.util.List;

public interface EmployeeMapper {
	List<Employee> select(RowBounds rowBounds);
}

 

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.antop.mybatis.mapper.EmployeeMapper">
	<resultMap id="BaseResultMap" type="org.antop.mybatis.model.Employee">
    	<id property="no" column="emp_no" />
        <result property="gender" column="gender" typeHandler="org.antop.mybatis.handler.GenderTypeHandler" />
        <association property="name" javaType="org.antop.mybatis.model.Name">
        	<result property="first" column="first_name" />
            <result property="last" column="last_name" />
		</association>
	</resultMap>
    
    <select id="select" resultMap="BaseResultMap">
    	select
        	*
		from
        	employees
		order by
        	emp_no asc
	</select>
</mapper>

 

위와 같이 맵퍼와 XML 이 있다.

RowBounds rowBounds = new RowBounds(5, 10);
// 15건을 가져와서 앞에 5건 건너띔
List select = mapper.select(rowBounds);

결과는 10건이 나왔지만 어떻게 동작할까?

여기저기 찾아보고 소스도 대충(?) 보면 offset + limit 만큼 가져와서 offset 만큼 건너띤다.

페이징이 뒤로 갈 수록 느려지게 된다.

RowBounds rowBounds = new RowBounds(29990, 10);
// 30000건을 가져와서 앞에 29990건 건너띔
List select = mapper.select(rowBounds);

데이터의 양이 적다면 쿼리에서 페이징을 하지 않고 RowBouns를 이용하면 빠르게 개발할 수 있을 것이다.

하긴 요즘 수천만건의 데이터를 페이징에서 마지막 페이지를 볼 일이 많을까? -_-/

 

XML 쿼리문에 페이징을 넣지 않고 RowBounds 클래스를 사용하면 자동으로 페이징 쿼리가 실행되게 할 수 없을까?

방법이 있다! Mybatis Intercepter를 이용하면 된다. (인터셉터의 자세한 사용법은 다루지 않겠다 ㅠㅠ)

  1. Mybatis에서 쿼리를 날리기 전에 가로챈다.
  2. RowBounds가 있으면 쿼리에 페이징 문장를 적용한다.
  3. RowBounds를 제거하여 Mybatis에서 페이징 처리를 하지 않도록 한다.

아래 소스는 쿼리에 MySQL용으로 limit 문을 붙여준다.

package org.antop.mybatis.intercepter;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.util.Properties;

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MysqlRowBoundsInterceptor implements Interceptor {
	private static final Logger logger = LoggerFactory.getLogger(MysqlRowBoundsInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    
    public Object intercept(Invocation invocation) throws Throwable {
    	StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
        String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        RowBounds rb = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
        logger.debug("originalSql = {}", originalSql);
        logger.debug("RowBounds = {}", rb);
        if (rb == null || rb == RowBounds.DEFAULT) { // RowBounds가 없으면 그냥 실행
        	return invocation.proceed();
		}
        
        // RowBounds가 있다!
        // 원래 쿼리에 limit 문을 붙여준다.
        StringBuffer sb = new StringBuffer();
        sb.append(originalSql);
        sb.append(" limit ");
        sb.append(rb.getOffset());
        sb.append(", ");
        sb.append(rb.getLimit());
        
        logger.debug("sql = {}", sb.toString());
        // RowBounds 정보 제거
        metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
        // 변경된 쿼리로 바꿔치기
        metaStatementHandler.setValue("delegate.boundSql.sql", sb.toString());
        
        return invocation.proceed();
	}
    
    public Object plugin(Object target) {
    	return Plugin.wrap(target, this);
	}
    
    public void setProperties(Properties properties) {
    }
    
}

SqlSessionFactory 설정하는 부분에 인터셉터를 적용하면 된다.

 

<?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:mybatis="http://mybatis.org/schema/mybatis-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
	
    <mybatis:scan base-package="org.antop.mybatis"/>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
        <property name="plugins">
        	<list>
            	<!-- 인터셉터 설정 -->
                <bean class="org.antop.mybatis.intercepter.MysqlRowBoundsInterceptor"/>
			</list>
		</property>
	</bean>
    
    <bean id="dataSource" ... />
</beans>

 

실행시켜 보면 쿼리문에 페이징이 적용되서 쿼리가 날라가는 것을 확인할 수 있다.

 

select * from employees order by emp_no asc limit 29990, 10

 

 

출처

http://www.programering.com/a/MTM5gTNwATQ.html

http://hjw1456.tistory.com/10

반응형
//

GWT with Maven

Posted at 2015. 3. 18. 02:31 | Posted in Framework
반응형

Installed the Java SDK


If you don’t have a recent version of the Java SDK installed, download and install Sun Java Standard Edition SDK.


적어도 이 문서를 찾아보는 이라면 자바는 이미 깔려 있을 것이라고 굳게 믿는다... -_-/




Installed Eclipse or your favorite Java IDE


In these tutorials, we use Eclipse because it is open source. However, GWT does not tie you to Eclipse. You can use IntelliJ, NetBeans or any Java IDE you prefer. If you use a Java IDE other than Eclipse, the screenshots and some of the specific instructions in the tutorial will be different, but the basic GWT concepts will be the same.


If your Java IDE does not include Apache Ant support, you can download and unzip Ant to easily compile and run GWT applications.


이클립스를 기준으로 한다. 현재 최신 버전인 Luna 로 하겠다.





Installed the Google Plugin for Eclipse


The Google Plugin for Eclipse adds functionality to Eclipse for creating and developing GWT applications.


구글 플러그인을 깔자.


Help - Install New Software...



Work with: https://dl.google.com/eclipse/plugin/4.4


항목중에 Google Plugin for Eclise 4.4 를 체크 - Next 버튼 클릭.





Using the Archetype


http://mojo.codehaus.org/gwt-maven-plugin/user-guide/archetype.html


메이븐 archetype 를 업데이트 해줘야 한다. (현재 최신 버전은 2.7.0 이다.)


File - New - Other...


Maven Project 선택.



gwt-maven 으로 필터링을 하게 되면 하나가 나오는데 버전이 2.5.0 일 것이다. (아래처럼 2.7.0 이라면 패스)


Add Archetype 버튼 클릭.



Archetype Group Id: org.codehaus.mojo

Archetype Artifact Id: gwt-maven-plugin

Archetype Version: 2.7.0 (현재의 최신 버전)



OK 버튼 클릭하면 archetype 을 다운로드해서 업데이트 한다.

아직 프로젝트는 안만들 것이니 Canel 버튼 클릭.




Creating the StockWatcher application (using Eclipse)


File - New  - Other... - Maven Project


Group Id: com.google.gwt.sample (아무거나 -_-)

Artifact Id: StockWatcher

Package: com.google.gwt.sample.stockwatcher

module: StockWatcher


※ 여기서 모듈명은 GWT 튜토리얼에 있는 StockWatcher 로 하겠다.



Finish 버튼 클릭.




Using the Google Eclipse Plugin


http://mojo.codehaus.org/gwt-maven-plugin/eclipse/google_plugin.html


메이븐을 이용해서 만들었는데 이클립스의 개발자 모드로 해보려면 추가 작업이 필요하다.


resources 쪽에 있는 gwt.xml 파일을 java 쪽으로 옮겨야한다.

구글 플러그인에서 resources 에 있는 gwt.xml 을 인식하지 못하는 것 같다.


src/main/resources/com/google/gwt/sample/stockwatcher 아래에 있는 StockWatcher.gwt.xml 파일을

com.google.gwt.sample.stockwatcher 패키지로 이동시킨다.



이동 결과



다음은 Google 플러그인 연결(?) 작업이다.


StockWatcher 프로젝트에서 오른쪽 버튼 클릭 - Properties



Google - Web Application


This project has a War directory 체크



Google - Web Toolkit


Use Google Web Toolkit 체크

Entry Point Modules 에 StockWatcher 가 등록되어 있다.



OK 버튼 클릭


※ OK 버튼 클릭 시 NullPointException 에러가 나면 취소 후 위 작업을 다시 해보자.




Testing the default project components


StockWatcher 프로젝트에서 오른쪽 버튼 클릭 - Run As - Web Application (GWT Super Dev Mode)



WAR 디렉토리를 선택하라고 최초에 한번 나올 것이다. src/main/webapp(기본) 를 선택하자.


앞으로 여기에 모든 파일들이 적용 된다. 라이브러리, 컴파일 되는 자바스크립트 파일 등등..



Console 창에 로그 출력 되다가 Development Mode 창에 아래와 같이 주소가 나오면 성공!



더블클릭 해서 브라우저를 띄워보자.



이제 GWT 로 "Hello World" 찍었다.


반응형
//

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
//

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
//

MySql Password Encoder

Posted at 2013. 9. 21. 19:24 | Posted in Framework/Spring Security
반응형
개인적으로 자주 사용하는 Spring Security Password Encoder 입니다.

MySql 의 password() 펑션 알고리즘 사용합니다.

import java.security.GeneralSecurityException; import java.security.MessageDigest; import org.springframework.security.crypto.password.PasswordEncoder; public class MySqlPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { if (rawPassword == null) { throw new NullPointerException(); } byte[] bpara = new byte[rawPassword.length()]; byte[] rethash; int i; for (i = 0; i < rawPassword.length(); i++) bpara[i] = (byte) (rawPassword.charAt(i) & 0xff); try { MessageDigest sha1er = MessageDigest.getInstance("SHA1"); rethash = sha1er.digest(bpara); // stage1 rethash = sha1er.digest(rethash); // stage2 } catch (GeneralSecurityException e) { throw new RuntimeException(e); } StringBuffer r = new StringBuffer(41); r.append("*"); for (i = 0; i < rethash.length; i++) { String x = Integer.toHexString(rethash[i] & 0xff).toUpperCase(); if (x.length() < 2) r.append("0"); r.append(x); } return r.toString(); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { if (encodedPassword == null || rawPassword == null) { return false; } if (!encodedPassword.equals(encode(rawPassword))) { return false; } return true; } }


적용 예시..

	<security:authentication-manager alias="authenticationManager">
		<security:authentication-provider user-service-ref="userService">
			<security:password-encoder ref="passwordEncoder" />
		</security:authentication-provider>
	</security:authentication-manager>

	<bean id="passwordEncoder" class="MySqlPasswordEncoder">


반응형

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

AJAX Login with Spring Security  (7) 2013.12.12
Spring Security Session Destroy  (4) 2013.10.27
//

SpringDM Test Project - hello.osgi

Posted at 2013. 4. 7. 21:16 | Posted in Framework/Spring OSGi
반응형

SpringDM 환경 구성 후 이것 저것 잘 되는지 테스트하기 위한 기본 샘플을 만들어 봤습니다.


처음에 잘 못하니까 무지하게 고생 하네요;;


일반적인 스프링 MVC 구조로 보면 아래와 같습니다.



하나의 프로젝트에 컨트롤러/서비스/퍼시스턴스(마이바티스) 등등 모든 구성이 다 되어있죠 ㅎㅎ


이 구조를 각 레이어 기준으로 OSGi 구조로 변경해 보았습니다.



web/service/persistent/core 4개의 번들로 나누어 만들어 봤습니다.


아래 그림은 그냥 사용할 데이터베이스 스키마입니다.



이제 하나하나 번들화 해서 작업 해봐야 겠습니다.


일단 <context:component-scan /> 안되네요... classpath*: 관련된 것은 다 안되는 듯 합니다... 아닌가?




작업하면서 추가한 번들 목록입니다.



jar 파일 및 소스 파일입니다.


hello.osgi.jar.zip


hello.osgi.src.zip


jar 파일은 개발환경에서만 해서 실제 환경에 올리면 될지 모르겠네요 ;;;


반응형

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

OSGi + Tomcat 6 + SpringDM 플랫폼 환경 구성  (0) 2013.03.25
//