Last Modified 2015.9.10

스프링 시큐리티 - 비밀번호 암호화

비밀번호는 단순 텍스트로 저장해서는 안 된다.

패스워드 인코더 설정

<authentication-provider> 엘리먼트에 <password-encoder> 엘리먼트를 추가한다.

security.xml
<authentication-manager>
  <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select 
          email as username,passwd as password,1 as enabled
        from member 
        where email = ?"
      authorities-by-username-query="select 
          email as username,authority
        from authorities 
        where email = ?" />
    <password-encoder hash="bcrypt" />
  </authentication-provider>
</authentication-manager>

hash="bcrypt"로 설정하면 인터페이스 PasswordEncoder의 구현체 중 bcrypt 알고리즘을 사용하는 BCryptPasswordEncoder가 설정된다.
톰캣을 재실행하고 hong@gmail.org/1111으로 로그인을 시도하면 이전과 달리 로그인이 실패하게 된다.
회원 테이블에 암호화가 안된 1111이 저장되어 있기 때문이다.
권한 테이블과 회원 테이블을 모두 삭제한다.

sqlplus java/school

delete from authorities;

delete from member;

commit;

이메일은 hong@gmail.org, 비밀번호는 1111로 회원가입한다.
가입 후 로그인을 시도하면 역시 로그인이 실패한다.
회원 테이블에 비밀번호가 여전히 단순 텍스트 1111로 저장되기 때문이다.
비밀번호가 암호화하여 저장하기 위해 UserServiceImpl.java를 수정해야 한다.

UserServiceImpl.java

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	private UserMapper userMapper;
	
	@Autowired
	private BCryptPasswordEncoder bcryptPasswordEncoder;

	@Override
	public void addUser(User user) {
		user.setPasswd(this.bcryptPasswordEncoder.encode(user.getPasswd()));
		userMapper.insert(user);
	}
	
	//..생략..	
}	

자바 소스를 수정했으니 컴파일(mvn clean compile war:inplace)하고 톰캣을 재실행한다.
이번에는 애플리케이션이 로딩되지 않는다.
톰캣 로그를 확인해 보면,
로딩에 실패한 원인은 UserServiceImpl에 BCryptPasswordEncoder를 주입할 수 없기 때문이다.
BCryptPasswordEncoder를 Authentication Provider 밖에서도 참조하기 원한다면 패스워드 인코더 설정을 수정해야 한다.

security.xml
<beans:bean id="bcryptPasswordEncoAccess Deniedder" 
  class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

<authentication-manager>
  <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select 
          email as username,passwd as password,1 as enabled
        from member 
        where email = ?"
      authorities-by-username-query="select 
          email as username,authority
        from authorities 
        where email = ?" />
    <password-encoder ref="bcryptPasswordEncoder" />
  </authentication-provider>
</authentication-manager>

톰캣을 재실행한 후 회원가입을 시도한다.
이번에는 회원 테이블의 패스워드 컬럼의 길이때문에 SQLException이 발생하게 된다.
member 테이블의 passwd 컬럼을 변경한다.

Oracle
alter table member modify passwd varchar2(200);
MySQL
alter table member modify passwd varchar(200) not null;

이메일은 im@gmail.org, 비밀번호는 1111로 회원가입한다.
이제는 로그인이 되어야 한다.

회원 수정, 비밀번호 변경, 탈퇴

회원 수정, 비밀번호 변경, 탈퇴를 수정해야 한다.

UsersController.java
@RequestMapping(value="/bye", method=RequestMethod.POST)
public String bye(String email, String passwd, HttpServletRequest req) 
        throws ServletException {
	User user = new User();
	user.setEmail(email);
	user.setPasswd(passwd);
	userService.bye(user);
	
	req.logout();

	return "redirect:/users/bye_confirm";
}
UserServiceImpl.java
@Override
public int editAccount(User user) {
	String encodedPassword = this.getUser(user.getEmail()).getPasswd();   
	boolean check = this.bcryptPasswordEncoder.matches(user.getPasswd(), encodedPassword);

	if (check == false) {
		throw new AccessDeniedException("현재 비밀번호가 틀립니다.");
	}
	
	user.setPasswd(encodedPassword);

	return userMapper.update(user);
}

@Override
public int changePasswd(String currentPasswd, String newPasswd, String email) {
	String encodedPassword = this.getUser(email).getPasswd();
	boolean check = this.bcryptPasswordEncoder.matches(currentPasswd, encodedPassword);

	if (check == false) {
		throw new AccessDeniedException("현재 비밀번호가 틀립니다.");
	}
	
	newPasswd = this.bcryptPasswordEncoder.encode(newPasswd);
	
	return userMapper.updatePasswd(encodedPassword, newPasswd, email);
}

@Override
public void bye(User user) {
	String encodedPassword = this.getUser(user.getEmail()).getPasswd();
	boolean check = this.bcryptPasswordEncoder.matches(user.getPasswd(), encodedPassword);

	if (check == false) {
		throw new AccessDeniedException("현재 비밀번호가 틀립니다.");
	}
	
	userMapper.deleteAuthority(user.getEmail());
	userMapper.delete(user);
}

UserService.java의 login(String email, String passwd) 메소드는 필요 없으니 삭제한다.
UserServiceImpl.java와 UserMapper.java와 UserMapper.xml에서도 이 메소드와 관련된 부분을 함께 삭제한다.