: 앞선 포스팅에서 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
,