: 앞선 포스팅에서 ShaPasswordEncoder 클래스를 이용해서 패스워드를 인코딩을 해서 DB에 저장을 했었다. 물론 인코딩된 패스워드는 원래의 플레인 텍스트로 디코딩은 되지 않지만 한가지 문제점이 있다. 

 

예를 들어서, 관리자의 인코딩된 패스워드가 "e6e353b108dda8703f1507383815e129358be60b"라고 가정하자. 그런데 다른 사용자가 회원가입을 하고 난 다음 DB에 저장된 인코딩된 패스워드가 관리자가 지정한 패스워드와 동일하게 저장이 되었다면, DB자체가 해킹등으로 인해서 인코딩된 패스워드가 해커에게 노출이 되었을 경우, 해커가 자동화된 툴을 이용해서 인코딩 된 패스워드에 대해서 충분히 많은값을 대입해본다면 패스워드가 노출될 가능성이 있다. 

 

이렇게 다른 사용자들간의 인코딩된 패스워드 값이 같아질 수 있는 이유는 해시 알고리즘이 동일한 입력에 대해서는 항상 동일한 결과가 나오기 때문이다.

 

이러한 단점을 보완하기 위해서 암호화된 패스워드에 솔팅 기법을 적용하는것이다. 아래 그림을 보도록 하자.

 

 

--> 위 그림처럼 사용자가 제시한 텍스트 비밀번호에 암호화가 적용되기전에 첨부되는 또 하나의 텍스트 스트링, 즉 이 텍스트를 솔트라고 한다. 

--> DB에 저장된 솔트 스트링은 랜덤하게 생성된 스트링으로써, 사용자가 제시한 비밀번호와 솔트 스트링이 함께 합쳐져서 암호화가 되기 때문에 서로 다른 사용자들끼리 동일한 암호화가 된 값을 갖는 일은 절대 발생하지 않는다. 

--> 이러한 솔팅 기법을 적용하면 강제로 비밀번호를 크랙하기위한 많은 기법에 대한 취약점이 사라지게 된다.

 

 

그럼 지금부터 솔트를 사용하는 방법을 알아보도록 하자. 여기서 사용할 시나리오는 아래와 같다.

 

1. 관리자가 관리자 모드에서 사용자를 등록할때 인코딩된 비밀번호 및 랜덤하게 생성된 솔트 스트링을 DB에 저장한다.

2. 솔팅을 적용한 기법으로 등록한 사용자 아이디로 로그인이 정상적으로 되는지를 확인한다.

 

이 두가지 로직을 한번 살펴보도록 하자.

 

 

1. 솔팅을 적용한 암호화된 패스워드를 DB에 저장하기.

 

솔팅을 적용하기 위해서는 먼저 SaltSource 인터페이스를 구현한 구현체를 스프링 설정 파일에 빈으로 등록해야한다. 여기서는 ReflectionSaltSoucre 구현체를 사용하도록 하겠다.

 

==== context-common-security.xml ====

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

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:context="http://www.springframework.org/schema/context" 

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-3.0.xsd

       http://www.springframework.org/schema/jee

       http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

     

    

    <!-- JdbcUserDetailsManager 빈 등록 -->

      <bean id="jdbcUserService" class="psm.mobile.security.CustomJdbcUserDetailsManager"

          p:dataSource-ref="dataSource"

          p:authenticationManager-ref="authenticationManager"

          p:enableGroups="true"

          p:enableAuthorities="false"

          p:changePasswordSql="update 

                                                  PSM_USERS

                                               set

                                                   USER_PASS = ?

                                               where

                                                   USER_ID = ?"

          p:usersByUsernameQuery="select 

                                                      USER_ID,

                                                      USER_PASS,

                                                      ENABLED,

                                                      SALT

                                                from 

                                                        PSM_USERS 

                                                where 

                                                        USER_ID = ?"

          p:groupAuthoritiesByUsernameQuery="select

                                                                       A.ID,

                                                                       A.GROUP_NAME,

                                                                       C.AUTHORITY

                                                               from 

                                                                         PSM_GROUPS A, 

                                                                         PSM_GROUP_MEMBERS B,

                                                                         PSM_GROUP_AUTHORITIES C

                                                               where 

                                                                         B.USER_ID = ? and

                                                                         A.ID = B.GROUP_ID and

                                                                         A.ID = C.GROUP_ID"

        />

        

     

   <!-- 패스워드 암호화 클래스 -->

   <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/>

     

   <!-- 패스워드 salt -->

   <bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource"

           p:userPropertyToUse="salt"/>

     

</beans>

--> 이 설정에서 userPropertyTouse 프로퍼티에 대해서 알고 넘어가야한다. 스프링 시큐리티 API 문서에 보면 setUserPropertyToUse() 메서드에 대해서 다음과 같이 나와있다.

"The method to call to obtain the salt"

 

--> 솔트 스트링을 얻기위해서 호출 되는 메서드의 이름이라는 뜻이다. 즉, "salt"라는 값은 솔트 스트링을 얻기위한 메서드 이름이다. 그렇다면 어디에 설정이 된 메서드일까?

 

--> SaltSource는 UserDetails 객체에 의존해서 솔트값을 생성한다. 그렇기때문에 salt라는 메서드 이름은 UserDetails 내에 지정된 메서드인것이다. 자동으로 지정된 것이 아니라 UserDetails의 구현체인 User 클래스를 상속한 SaltedUser 클래스라는 놈을 작성해서 이 SaltedUser 클래스안에 개발자가 직접 지정을 해주어야 한다. 메서드 이름이 salt라고 해서 salt() 메서드가 아니라 이놈은 getter 메서드이다. 그러므로 실제 메서드명은 getSalt() 가 된다. SaltedUser 클래스는 조금 있다가 작성을 해보도록 하겠다.

 

 

이제 사용자를 등록해보기로 하자.  먼저 사용자를 등록하기위한 폼 페이지를 작성한다.

 

==== memberRegForm.jsp ====

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

    pageEncoding="UTF-8"%>

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

<c:import! url="/WEB-INF/view/mobile/include/user/header.jsp"/>

<body>

    <div data-role="page" data-theme="b">

        <%// header 시작. %>

        <div data-role="header" data-theme="b" data-position="fixed">

            <h1>SOO MEE MOBILE LOGIN</h1>

        </div>

        <%// header 끝. %>

        

        <%// content 시작. %>

        <div data-role="content" data-theme="d">

            <form name="memberForm" method="POST" action="/smma/member/memberReg">

                <div data-role="fieldcontain" class="bold">

                    <label for="userId">ID</label><br/>

                    <input type="text" name="userId" maxlength="12"/>

                </div>

                <div data-role="fieldcontain" class="bold">

                    <label for="userPassword">PASSWORD</label><br/>

                    <input type="password" name="userPassword" maxlength="12"/>

                </div>

                <div data-role="fieldcontain" class="bold">

                    <label for="userName">Name</label><br/>

                    <input type="text" name="userName" maxlength="20"/>

                </div>

                <div data-role="fieldcontain" class="bold">

                    <label for="userPhone">Phone</label><br/>

                    <input type="text" name="userPhone" maxlength="20"/>

                </div>

                <div data-role="fieldcontain">

                    <label for="enabled">Enabled</label>

                    <fieldset data-role="controlgroup">

                        <input type="radio" name="enabled" id="enabled" value="1" checked="checked"/>

                        <label for="enabled">enabled</label>

                        <input type="radio" name="enabled" id="disabled" value="0"/>

                        <label for="disabled">disabled</label><br/>

                    </fieldset>

                    

                </div>

                <div class="center"><input type="submit" value="Registration" data-inline="true"/> <input type="reset" value="RESET" data-inline="true"/></div>

                

            </form>

        </div>

        <%// content 끝. %>

        

        <%// footer 시작. %>

        <c:import! url="/WEB-INF/view/mobile/include/footer/footer.jsp"/>

        <%// footer 끝. %>

    </div>

</body>

</html>

--> jQuery mobile을 사용에서 작성한 코드이지만 일반 jsp 코드로 작성한것도 이와 비슷할 것이다.

 

 

다음으로, 폼의 요청을 받는 컨트롤러 페이지를 작성해보자.

 

==== AdminMemberController.java ====

package psm.mobile.controller.admin.member;

 

import! javax.annotation.Resource;

 

import! org.springframework.stereotype.Controller;

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

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

 

import! psm.mobile.dto.MemberDto;

import! psm.mobile.service.serviceItf.admin.AdminService;

 

@Controller

@RequestMapping("/smma/member")

public class AdminMemberController

{

    @Resource(name="adminService")

    AdminService adminService;

    

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

    public String addMemberForm()

    {

        return "admin/member/memberRegForm";

    }

    

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

    public String addMember(MemberDto memberDto)

    {

        adminService.addMember(memberDto);

        

        return "admin/adminMain";

    }

}

 

--> 일반적인 컨트롤러와 별반 다를게 없다. 요청으로 받은 데이터를 등록하기위해서 addMember() 서비스 메서드를 호출한다.

 

 

 

다음으로 사용자 등록을 실제로 처리해주는 서비스 클래스를 보도록 하자. 이 서비스 클래스에서 솔팅이 적용되므로 잘 살펴보도록 한다.

 

==== AdminServiceImpl.java ====

package psm.mobile.service.serviceImpl.admin;

 

import! java.util.ArrayList;

import! java.util.List;

 

import! javax.annotation.Resource;

 

import! org.springframework.security.authentication.dao.SaltSource;

import! org.springframework.security.authentication.encoding.ShaPasswordEncoder;

import! org.springframework.security.core.GrantedAuthority;

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

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

import! org.springframework.security.core.userdetails.User;

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

import! org.springframework.security.provisioning.UserDetailsManager;

import! org.springframework.stereotype.Service;

 

import! psm.mobile.dao.daoItf.admin.PsmDao;

import! psm.mobile.dto.FileInfoDto;

import! psm.mobile.dto.MemberDto;

import! psm.mobile.security.SaltedUser;

import! psm.mobile.service.serviceItf.admin.AdminService;

import! psm.mobile.service.util.StringUtilService;

 

 

/**

 * 관리자 서비스에 대한 처리를 해주는 클래스.

 * 

 * @author 황정식

 *

 */

@Service("adminService")

public class AdminServiceImpl implements AdminService

{

 

       @Resource(name="jdbcUserService")

    private UserDetailsManager userDetailsManager;

    

    @Resource(name="psmDao")

    private PsmDao<MemberDto, FileInfoDto> psmDao;

      

        // (1) 

    @Resource(name="passwordEncoder")

    private ShaPasswordEncoder passwordEncoder;

      

        // (2) 

    @Resource(name="saltSource")

    private SaltSource saltSource;

    

    public void addMember(MemberDto memberDto)

    {

        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");

        

        List<GrantedAuthority> authorityList = new ArrayList<GrantedAuthority>();

        authorityList.add(authority);

          

                // (3) 

        String salt = StringUtilService.getRandomString(20);

          

                // (4)   

        UserDetails user = new SaltedUser(memberDto.getUserId(), memberDto.getUserPassword(), 

                true, true, true, true, authorityList, salt);

          

        

        // (5) 

        String encodePassword = passwordEncoder.encodePassword(memberDto.getUserPassword(), saltSource.getSalt(user));

        

        memberDto.setUserPassword(encodePassword);

        memberDto.setSalt(salt);

        

        psmDao.insertData("member.insertMember", memberDto);

        psmDao.insertData("member.insertAuthorities", memberDto);

        psmDao.insertData("member.insertGroupMember", memberDto);

    }

      

    

}

--> (1) 스프링 설정 파일에 빈으로 등록된 ShaPasswordEncoder 빈을 DI 해둔다.

 

--> (2) 사용자를 등록할 때 패스워드에 솔팅을 적용하기위해서 스프링 설정 파일에 등록된 SaltSource 빈을 DI 해둔다.

 

--> (3) 솔트 스트링으로 사용될 문자열을 얻어온다. 랜덤한 문자열을 생성하는 부분은 각자에 맞게 적절한 방법을 사용하면 되겠다.

 

--> (4) 위에서 언급한대로 SaltSource는 UserDetails 객체에 의존해서 솔트값을 생성한다고 했다. UserDetails 인터페이스를 구현한 User 클래스를 상속받은 SaltedUser 클래스를 이용해서 User 객체를 생성하고,  salt 스트링을 SaltedUser 클래스의 프로퍼티 값으로 지정해주기 위해서 생성자를 이용해서 값을 넘긴다. 이 SaltedUser 클래스는 바로 다음에서 코드를 작성해보도록 하겠다.

 

--> (5) 솔팅이 적용된 암호화된 패스워드를 생성한다.

 

--> 그 다음부터는 DB에 저장하기 위한 로직이므로, 개별적으로 작성하면 되겠다.

 

 

 

다음으로 솔팅을 하기 위해서 사용되는 User 객체를 생성하는 SaltedUser 클래스를 작성해보도록 하자.

 

==== SaltedUser.java ====

package psm.mobile.security;

 

import! java.util.Collection;

 

import! org.springframework.security.core.GrantedAuthority;

import! org.springframework.security.core.userdetails.User;

 

@SuppressWarnings("serial")

public class SaltedUser extends User

{

    private String salt;

    

    public SaltedUser(String username, String password, boolean enabled,

            boolean accountNonExpired, boolean credentialsNonExpired,

            boolean accountNonLocked,

            Collection<? extends GrantedAuthority> authorities, String salt)

    {

        super(username, password, enabled, accountNonExpired, credentialsNonExpired,

                accountNonLocked, authorities);

        

        this.salt = salt;

        

        System.out.println("넘어온 솔트는? " + salt);

    }

 

    public String getSalt()

    {

        return salt;

    }

 

    public void setSalt(String salt)

    {

        this.salt = salt;

    }

 

    

}

 

--> User 객체를 생성하기위해서 수퍼 클래스인 User 클래스의 객체를 생성하면서 사용자 정보를 넘겨준다.

--> AdminServiceImpl 클래스에서 넘겨받은 salt 스트링을 salt 프로퍼티의 값으로 저장하고, getter 메서드를 지정하는것을 볼 수 있다. 이 getter 메서드인 getSalt() 메서드가 스프링 설정파일에서 등록한 saltSource 빈의 userPropertyToUse 프로퍼티에 설정된 "salt"라는 메서드 이름인 값인것이다.

 

 

마지막으로 스프링 설정 파일에서 AuthenticationManager가 saltSource 빈을 참조하도록 아래와 같이 설정한다.

 

==== context-security.xml ====

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

<b:beans xmlns="http://www.springframework.org/schema/security"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:b="http://www.springframework.org/schema/beans"

    xmlns:jdbc="http://www.springframework.org/schema/jdbc"

    xmlns:context="http://www.springframework.org/schema/context"

    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-3.0.xsd

        http://www.springframework.org/schema/jdbc  

        http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd

        http://www.springframework.org/schema/security 

        http://www.springframework.org/schema/security/spring-security-3.1.xsd

    ">

 

    <!-- @Required, @Autowired, @Resource, @PostConstruct, @PreDestroy, @Configuration 어노테이션 처리 -->   

    <context:annotation-config/>

    

    <!--

        - xml 설정 파일과 빈 자동 스캔을 동시에 사용하는것을 테스트 하기 위해 서비스 클래스만

           어노테이션을 적용할 예정.

        - 디폴트로 어노테이션이 설정된 클래스만 자동 스캔한다.

        - 아래 설정에서는 Controller 어노테이션 클래스는 자동 스캔에서 제외하였다.

     -->

    <context:component-scan base-package="egovframework">

       <context:exclude-filter type="annotation" expression!="org.springframework.stereotype.Controller" />

    </context:component-scan>

        

     

    <http auto-config="true" use-expressions="true">

        <intercept-url pattern="/psm/main/login" access="permitAll"/>

        <intercept-url pattern="/css/**" access="permitAll"/>

        <intercept-url pattern="/js/**" access="permitAll"/>

           <intercept-url pattern="/favicon.ico" access="permitAll"/>

           <intercept-url pattern="/smma/**" access="hasRole('ROLE_ADMIN')"/>

        <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

          

        <form-login login-page="/psm/main/login"/>

        <logout invalidate-session="true" logout-success-url="/" logout-url="/logout"/>

    </http>

   

   

    <authentication-manager alias="authenticationManager">

        <authentication-provider user-service-ref="jdbcUserService">

            <password-encoder ref="passwordEncoder">

                <salt-source ref="saltSource"/>

            </password-encoder>

        </authentication-provider>

    </authentication-manager> 

      

    

</b:beans>

 

--> 위와 같이 설정했을때, 사용자 정보를 폼에 입력하고 전송을 하게되면 전송받은 패스워드에 솔트 스트링을 포함해서 솔팅을 적용한후에 패스워드를 암호화 하게 된다.

 

 

솔팅을 적용해서 패스워드를 암호화한후, DB에 저장하는 과정이 끝이 났다. 

 

 

 

 

다음으로, 솔팅이 적용된 패스워드를 이용해서 로그인을 하는 과정을 살펴보자.

 

2. 솔팅을 적용한 기법으로 등록한 사용자 아이디로 로그인 하기.

 

지금부터는 사용자가 입력한 로그인 패스워드를 DB에 저장된 솔팅이 적용된 패스워드와 비교해서 일치하면 로그인에 성공하는 과정을 작성해보겠다. 사용자가 입력한 패스워드와 DB에 저장된 솔팅이 적용된 패스워드를 비교하기 위해서는 사용자가 로그인 시 입력한 패스워드에 솔팅을 먼저 적용해야한다. 그러기 위해서는 DB에 저장된 솔트 스트링이 필요하다. 

 

위에서 누차 언급했던대로 SaltSource는 UserDatials 객체에 의존해서 솔트값을 생성한다고 했다. 그러므로 DB에 저장된 솔트 스트링을 불러오고, 위 솔트 스트링을 사용자가 로그인시 입력한 패스워드에 적용을 하기위해서는 UserDetails 인터페이스의 구현체인 User 클래스를 상속한 SaltedUser 클래스의 객체를 생성하면서 salt 스트링 값을 전달하는 작업이 여기서도 필요하다.

 

눈에 보이는 대로 순차적으로 진행되는것이 확인이 안되고, 스프링 프레임워크 내에서 눈에 보이지 않게 진행이 되는 관계로 이해하기가 조금은 어려울 수도 있지만 그러려니 하고 넘어갈 수 있었으면 좋겠다.

 

스프링 시큐리티의 설정은 위에서 작성했던것과 같이 동일하다.

==== context-security.xml ====

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

<b:beans xmlns="http://www.springframework.org/schema/security"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:b="http://www.springframework.org/schema/beans"

    xmlns:jdbc="http://www.springframework.org/schema/jdbc"

    xmlns:context="http://www.springframework.org/schema/context"

    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-3.0.xsd

        http://www.springframework.org/schema/jdbc  

        http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd

        http://www.springframework.org/schema/security 

        http://www.springframework.org/schema/security/spring-security-3.1.xsd

    ">

 

    <!-- @Required, @Autowired, @Resource, @PostConstruct, @PreDestroy, @Configuration 어노테이션 처리 -->   

    <context:annotation-config/>

    

    <!--

        - xml 설정 파일과 빈 자동 스캔을 동시에 사용하는것을 테스트 하기 위해 서비스 클래스만

           어노테이션을 적용할 예정.

        - 디폴트로 어노테이션이 설정된 클래스만 자동 스캔한다.

        - 아래 설정에서는 Controller 어노테이션 클래스는 자동 스캔에서 제외하였다.

     -->

    <context:component-scan base-package="egovframework">

       <context:exclude-filter type="annotation" expression!="org.springframework.stereotype.Controller" />

    </context:component-scan>

        

     

    <http auto-config="true" use-expressions="true">

        <intercept-url pattern="/psm/main/login" access="permitAll"/>

        <intercept-url pattern="/css/**" access="permitAll"/>

        <intercept-url pattern="/js/**" access="permitAll"/>

           <intercept-url pattern="/favicon.ico" access="permitAll"/>

           <intercept-url pattern="/smma/**" access="hasRole('ROLE_ADMIN')"/>

        <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

          

        <form-login login-page="/psm/main/login"/>

        <logout invalidate-session="true" logout-success-url="/" logout-url="/logout"/>

    </http>

   

   

    <authentication-manager alias="authenticationManager">

        <authentication-provider user-service-ref="jdbcUserService">

            <password-encoder ref="passwordEncoder">

                <salt-source ref="saltSource"/>

            </password-encoder>

        </authentication-provider>

    </authentication-manager> 

      

    

</b:beans>

 

--> 사용자가 로그인시 입력한 패스워드가 이 부분을 통해 솔팅이 적용되고, 암호화 된다. 로그인시 입력한 패스워드에 솔팅이 적용되기전에 DB에서 솔트 스트링을 가져와서 솔팅을 하기위한 설정이 진행되는 부분은 jdbcUserService 빈이다.

 

 

스프링 설정파일에 설정된 이 부분을 살펴보도록 하자.

==== context-common-security.xml ====

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

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:context="http://www.springframework.org/schema/context" 

    xmlns:jee="http://www.springframework.org/schema/jee"

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-3.0.xsd

       http://www.springframework.org/schema/jee

       http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

     

    

    <!-- JdbcUserDetailsManager 빈 등록 -->

      <bean id="jdbcUserService" class="psm.mobile.security.CustomJdbcUserDetailsManager"

          p:dataSource-ref="dataSource"

          p:authenticationManager-ref="authenticationManager"

          p:enableGroups="true"

          p:enableAuthorities="false"

          p:changePasswordSql="update 

                                                  PSM_USERS

                                               set

                                                   USER_PASS = ?

                                               where

                                                   USER_ID = ?"

          p:usersByUsernameQuery="select 

                                                      USER_ID,

                                                      USER_PASS,

                                                      ENABLED,

                                                      SALT

                                                from 

                                                        PSM_USERS 

                                                where 

                                                        USER_ID = ?"

          p:groupAuthoritiesByUsernameQuery="select

                                                                       A.ID,

                                                                       A.GROUP_NAME,

                                                                       C.AUTHORITY

                                                               from 

                                                                         PSM_GROUPS A, 

                                                                         PSM_GROUP_MEMBERS B,

                                                                         PSM_GROUP_AUTHORITIES C

                                                               where 

                                                                         B.USER_ID = ? and

                                                                         A.ID = B.GROUP_ID and

                                                                         A.ID = C.GROUP_ID"

        />

        

     

   <!-- 패스워드 암호화 클래스 -->

   <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/>

     

   <!-- 패스워드 salt -->

   <bean id="saltSource" class="org.springframework.security.authentication.dao.ReflectionSaltSource"

           p:userPropertyToUse="salt"/>

     

</beans>

--> 다른 빈은 첫번째 작업 과정에서 설명을 했었고, 여기서 설명할 부분은 CustomJdbcUserDetailsManager 클래스이다. CustomJdbcUserDetailsManager 클래스는 JdbcUserDetailsManager 클래스를 상속한 클래스로서 이 클래스에서 솔팅을 하기위한 설정들이 코드로 작성된다.  아래에서 살펴보도록 하자.

 

 

 

==== CustomJdbcUserDetailsManager.java ====

package psm.mobile.security;

 

import! java.sql.ResultSet;

import! java.sql.SQLException;

import! java.util.ArrayList;

import! java.util.List;

 

import! org.springframework.jdbc.core.RowMapper;

import! org.springframework.security.core.GrantedAuthority;

import! org.springframework.security.core.authority.AuthorityUtils;

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

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

import! org.springframework.security.provisioning.JdbcUserDetailsManager;

 

public class CustomJdbcUserDetailsManager extends JdbcUserDetailsManager

{

        // (1) 

    public UserDetails loadUserByUsername(String userId)

    {

        // (2) 

                final List<GrantedAuthority> groupAuthorities = super.loadGroupAuthorities(userId);

        for(GrantedAuthority authority : groupAuthorities)

        {

            System.out.println("사용자의 권한: " + authority.getAuthority());

        }

   

                // (3) 

        UserDetails user =  getJdbcTemplate().queryForObject(

                super.getUsersByUsernameQuery(),

                new String[]{userId},

                new RowMapper<UserDetails>(){

                    public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException

                    {

                        String userId = rs.getString(1);

                        String password = rs.getString(2);

                        boolean enabled = rs.getBoolean(3);

                        String salt = rs.getString(4);

                        

                        System.out.println("DB에서 조회한 사용자 아이디: " + userId);

                        System.out.println("DB에서 조회한 사용자 패스워드: " + password);

                        System.out.println("DB에서 조회한 사용자 사용유무: " + enabled);

                        System.out.println("DB에서 조회한 사용자 솔트: " + salt);

                        

                        return new SaltedUser(userId, password, enabled, true, true,

                                true, groupAuthorities, salt);

                    }

                }

            );

        

        return user;

    }

}

 

--> (1) 의 loadUserByUsername() 메서드는 JdbcUserDetailsManager에서 오버라이드 된 메서드이다. CustomJdbcUserDetailsManger 클래스를 작성하지 않았다면 스프링 시큐리티 설정파일에서 JdbcUserDetailsManager 클래스를 빈으로 등록하고, 빈에 설정된 프로퍼티의 설정값들을 이용해서 UserDetails 정보가 SecurityContext에 저장이 될것이고, JdbcUserDetailsManager 클래스이 loadUserByUsername() 메서드를 호출하면 UserDeatils 객체를 얻을 수 있을것이다. 

--> 하지만 여기서는 JdbcUserDetailsManager 클래스를 상속해서 loadUserByUsername() 메서드를 오버라이드하기때문에 DB 에서 조회한 사용자 정보 즉, UserDetails 객체를 얻은 후, 솔팅 설정을 추가적으로 하게된다.

 

--> (2) 스프링 시큐리티 설정파일에서 그룹 권한을 사용하도록 지정해두었기 때문에 위와 같이 loadGroupAuthorities() 메서드를 이용해서 해당 사용자의 그룹의 권한을 얻어올 수 있다.

 

 

--> (3) 은 스프링의 JdbcTemplate 클래스를 이용해서 DB에서 사용자 정보를 가져오는 부분이다. JdbcTemplate 클래스이 콜백 메서드를 사용하는 방법은 각자가 공부하도록 하자. 여기서 getUsersByUsernameQuery() 메서드를 이용해서 스프링 시큐리티 설정파일에 빈으로 등록한 CustomJdbcUserDetailsManager 클래스의 프로퍼티에 있는 쿼리를 가져올 수 있다.

 

 

이상으로 솔팅이 적용해서 로그인 하는 부분까지 알아보았다. 내 경우는 ORM으로 현재 iBatis를 사용하고 있는 관계로 iBatis를 사용했을때 위에서 작성한 JdbcTempalte 클래스를 이용하여 사용자 정보를 조회하는 부분을 대체해보도록 하겠다

 

출처 - http://cafe.daum.net/ITVillage/Pgo6/6?docid=1DpW4%7CPgo6%7C6%7C20120411171000&q=%C0%CE%C4%DA%B5%F9

'Security > Common' 카테고리의 다른 글

naver login process  (0) 2012.12.23
Pass phrase vs. Password  (0) 2012.07.29
SASL(Simple Authentication and Security Layer)  (0) 2012.03.02
XSS(Cross-Site Scripting)  (0) 2012.02.29
Implicit FTPS and Explicit FTPS  (0) 2011.11.14
Posted by linuxism
,


OOP와 생산성 – 다형성의 문제점? 을 읽다보니 다음과 같이 얘기가 나온다.

OOP 덕에 전역 변수가 많이 줄었다는 것은 아쉽게도 착각입니다. 일례로 Singleton Pattern을 보세요. 이건 사용이 장려되고 있는 (디자인 패턴이라는 탈을 쓰고 나타난) 전역 변수 아닌가요?

싱글톤 패턴이 전역변수인가?

구글에서 애자일 코치로 활동하면서 테스트편의성을 가진 깔끔한 코드를 작성하도록 개발자들을 돕고 있는Misko Hevery에 따르면 그렇다. 그는 "싱글톤패턴은 양의 탈을 쓴 전역상태(global state)"라고 실날하게 비판한다. 싱글톤패턴은 전역상태를 조장하고 그 때문에 설계상의 문제점을 찾기 힘들게 하며 테스트하기 어렵게 만드는 안티패턴이라는 것이다.

그는 심지어 싱글톤을 적용한 부분을 찾아서 제거하는데 도움이 되도록, 클래스 파일의 바이트코드를 분석해서 싱글톤패턴을 찾아주는 Google Singleton Detector를 개발하기도 했다. 이 싱글톤 디텍터는 전형적인 싱글콘코드 뿐 아니라 변조 싱글톤인 Fingleton, Mingleton, Hingleton도 찾아준다.

싱글톤과 전역상태의 문제점에 관해서는 그의 Clean Code Talk 시리즈의 하나인 Global State and Singleton을 보면 이해하는데 도움이 될 것이다.

 

Misko Hevery 말고도 싱글톤패턴에 대해서 매우 비판적인 사람을 꼽자면 바로 스프링을 만든 로드 존슨이다. 로드 존슨은 자바의 싱글톤패턴의 단점을 극복하는 방법의 하나로 스프링 컨테이너와 같은 싱글톤 레지스트리가 필요하다고 주장한다. 테스트를 어렵게 만들고, 글로벌하게 접근하기 쉽게 만드는 싱글톤패턴 대신 public 생성자를 가진 평범한 POJO를 만들어 사용하되, 상태없는 서비스 싱글톤으로 사용이 필요하면 컨테이너를 통해서 싱글톤으로 관리되도록 만들면 된다는 것이다. IoC싱글톤이라고 해야 하나. 아무튼 스프링의 핵심 컨테이너의 빈 관리를 담당하는 BeanFactory의 핵심 구현 클래스는 DefaultListableBeanFactory이다. 대부분의 애플리케이션 컨텍스트는 바로 이 클래스를 BeanFactory로 사용하는데, 이 DefaultListableBeanFactory가 구현하고 있는 인터페이스의 한가지가 바로 SingletonRegistry이다. 결국 스프링 컨테이너 = 애플리케이션 컨텍스트 = 빈 팩토리 = 싱글톤 레지스트리가 되는 것이다.

나는 스프링을 사용한 이후로 싱글톤 패턴을 한번도 적용해본 적이 없다. 스프링을 사용하면 싱글톤의 장점은 얻으면서도 편리한 테스트를 만들 수 있고 전역접근에 대한 위험은 피하는 클래스의 작성이 쉽게 가능하기 때문이다.

 

DL(lookup)이 DI(injection)보다 못한점을 꼽자면 보통 lookup API의 노출과 컨테이너/컨텍스트를 거치지 않고 테스트 등에서 직접 POJO를 다루고 수동 DI하기가 힘들다는 점을 들 수 있다. 거기다 하나 더 추가하자면 DL은 전역상태를 사용하는 코드를 만들기 쉽다는 점을 들 수 있을 것 같다. 스프링의 모든 빈은 이너빈이 아니라면 전역적으로 접근이 가능하기 때문이다. 하지만 DI는 컨테이너를 코드에서 완벽하게 감추기 때문에 미리 설정된 구조를 따르지 않고는 다른 빈에 접근하지 않도록 해준다. 그에 반해서 DL은 언제든지 빈 이름을 통해서 임의의 빈을 액세스 할 수 있는 위험한 기능에 노출되어있다. 물론 조심해서 사용하면 상관없지만 잘못 사용하면 생성방식만 다를 뿐 싱글톤 패턴의 문제점을 가질 수 밖에 없다

 

어제 KSUG에 간단한 퀴즈를 냈는데 쉬운 질문이지만 트릭이 있는 것이었다. 싱글톤패턴으로 만든 클래스처럼 private 생성자를 가져서 외부에서 new로 생성이 불가능한 클래스를 스프링의 빈으로 바로 등록해서 사용할 수 있는가하는 질문이었다. 상식적으로 생각하자면 private생성자를 가졌으니 안될 것이고, 대신 getInstance() 같은 스태틱 팩토리를 가지고 있으니 스프링에서 스태틱 팩토리 메소드를 지정해서 팩토리 빈으로 만들서 사용하면 된다고 볼 수 있다. 그런데 정답은 "private 생성자를 가진 클래스도 스프링의 빈으로 바로 사용이 가능하다"이다. 스프링은 리플렉션을 통해서 인스턴스를 만들고, 리플렉션을 통해서라면 private생성자를 호출해서 인스턴스를 만드는 것이 가능하다. 해보면 알겠지만 스프링은 별 경고 없이 그냥 빈을 만들어버린다.

그래서 기존에 싱글톤패턴으로 만든 클래스나 팩토리 메소드를 통해서만 오브젝트가 만들어지는 클래스를 그냥 스프링의 빈으로 등록해서 DI방식으로 사용하게 하면 싱글톤패턴 코드로서의 단점을 극복하고, 테스트 편의성을 가져올 수도 있다. 굳이 오브젝트 팩토리 방식을 쓰거나 팩토리 빈으로 지정하지 않아도 된다는 것이다.

하지만 그다지 권장하고 싶지는 않다. 가능하면 클래스 설계자의 의도를 존중하고, 접근방법을 지키도록 하는 것이 바람직하다. 팩토리 메소드를 적용한 특별한 의도가 있거나 인스턴스 생성시 부가 작업이 필요한 경우가 있기 때문에 private생성자를 두었을 수도 있는데, 이를 무시하고 리플렉션을 통해서 가능하다고 직접 생성자를 호출해서 만드는 것은 다른 문제를 일으킬 수 있기 때문이다.

 

결론은 스프링을 쓰자.


출처 - http://toby.epril.com/?p=849


===================================================================================


Application Registry를 이용하여 Singleton의 확산을 피하라

Singleton을 사용할 때의 문제점(187페이지) => static을 사용할 때의 문제점이 될 수 있다.

  • Singleton 클래스에 대한 의존관계는 다른 클래스에 하드코딩되어야 한다.
  • Singleton은 그 자신의 설정을 처리해야 한다. => 이해가 잘 되지 않는다.
  • 복잡한 애플리케이션은 많은 Singleton을 가진다. 각 클래스는 서로 다른 설정 로딩을 갖는다. 이는 설정을 위한 중앙 Storage가 없음을 의미한다.
  • Singleton은 인터페이스와 친밀하지 않다. Singleton의 일반적인 구현은 Interface가 아닌 클래스에 타입을 정의하는 것이다.
  • Singleton은 상속을 잘 따르지 않는다. 자바는 static method의 오버라이딩을 허용하지 않기 때문이다.
  • Singleton의 상태를 Runtime시 일관성 있게 갱신하는 것이 불가능하다.

Rod Johnson은 일반적으로 static 변수를 사용하는 것을 좋아하지 않는다. 이는 특정 클래스에 대한 의존성을 만듦으로써 객체 지향을 깨트린다.

ApplicationContext의 등장

  • Rod Johnson은 Singleton 패턴의 이 같은 단점을 보완하기 위하여 ApplicationContext라는 Registry를 만들었다.

Global State와 Singleton 패턴

Singleton 패턴

  • http://www.ibm.com/developerworks/kr/library/j-dcl.html : Singleton 패턴을 사용할 때 발생하는 Double-checked locking 문제에 대해서 설명하고 있으며, 해결 방법을 제시해주고 있다.
    • ApplicationContext 개념을 사용할 경우 Double-checked locking에 대해서 고민하지 않아도 된다


출처 - http://www.javajigi.net/pages/viewpage.action?pageId=207388870






Posted by linuxism
,



자바 java.lang.NullPointerException 에러  study / Java Programing 

2012/01/31 11:04

복사http://blog.naver.com/ksj_7701/130130133632

질문자도 나와 같은 이유로 에러가 났으며

java.lang.NullPointerException 에러 가 나는 이유는

생성자를 이용해 객체를 제대로 생성(인스턴스 생성)하지 않은채로

객체를 사용하려고 했기때문에 나는 에러

 

 

지식in 원문( 약간수정 )

-------------------------------------------------------------------------------------------

Exception in thread "main" java.lang.NullPointerException

               at Main.<init><Main.java:28>

               at Main.main<Main.java:309>

 

이러한 오류가 납니다..

초기 변수 설정시

 

JMenu m1,m2,m3,m4,m5;

 

28번째 줄은   m1.setFont(f_1);

 

그뒤 m1.setFont(f_2);

 

309번째 줄은 메인문으로 frame.setContentPane(new Main()); 인데요

 

대충 어떠한 오류인지 감은오는데 자세하게 설명해주실분 계신가요?

 

컴파일은 되는데 실행시 이러한 오류가 뜹니다

 

-------------------------------------------------------------------------------------------

 

Exception in thread "main" java.lang.NullPointerException 에러는

생성하지 않은 객체에서 해당 메소드를 사용하는 경우에 발생하는 것으로 보입니다.

 

ex)잘 못 된 표현

 

JMenu m1;//객체를 생성하는 것이 아니라 레퍼런스 변수 선언만 한 상태

m1.setFont();  // 객체를 생성하지 않은 상태에서 setFont(); 메소드를 사용할 수 없습니다.

                    // nullpointexception 발생

 

ex)잘 된 표현

JMenu m1 ; // 레퍼런스 변수 선언

m1 = new JMenu(); // 객체생성코드 추가

m1.setFont(); // 메소드 사용 가능

 

 

 ------------------------------------------------------------------------------------------

출처 : 지식in

아이디 : tonk000


출처 - http://blog.naver.com/ksj_7701?Redirect=Log&logNo=130130133632


'Development > Java' 카테고리의 다른 글

java - java.lang.NumberFormatException: For input string  (1) 2012.07.19
java - 현재 시간 확인  (0) 2012.07.18
java - crontab에서 실행  (0) 2012.05.16
java - ORM  (0) 2012.05.15
JNDI(Java Naming and Directory Interface)  (0) 2012.05.10
Posted by linuxism
,