비밀번호를 입력받을 때부터 암호화해서 insert 하는 방법에 대해서 살펴보았습니다. 이제 인증할 때 입력받은 패스워드와 db에서 조회한 패스워드가 같은지 비교해야 합니다. 

우선 암호화가 완료된 후이므로 그냥 패스워드를 비교해 보도록 하겠습니다.

기본 인증은 우리가 커스트마이징한 UserDetailsService에서 DB 조회를 한 후 저장된 패스워드와 입력한 패스워드를 비교하여 인증을 하는 방식입니다.

입력한 것은 암호화되지 않은 문자열이고 DB에 저장되어 있는 데이터는 암호화된 데이터 이므로 같을 수가 없겠죠? 그래서 로그인이 되지 않습니다. 

그럼 어떻게 해야 할까요?

실제 인증을 담당하고 있는 AuthenticationProvider를 커스트마이징 해서 입력받은 패스워드를 암호화하여 비교하면 DB에 저장되어 있는 것과 같은지 알 수 있겠죠?

우선 "ktds.erp.emp.authentication"패키지에 AuthenticationProvider를 상속하는 클래스를 추가합니다.

package ktds.erp.emp.authentication;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

public class CustomAuthenticationProvider 
								implements AuthenticationProvider{
	public Authentication authenticate(Authentication data) throws AuthenticationException {
		System.out.println("CustomAuthenticationProvider=>"+data);
		
		return null;
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;//인증을 처리하는 provider가 아이디와 패스워드를 통해서 인증되도록 
		           //하려면 반드시 true이어야 한다.
	}

}

 

우선 AuthenticationProvider를 커스트마이징한 나의 클래스가 요청이 되어야겠죠? 위에서 작성한 클래스를 설정 파일에도 등록해야 합니다.

설정 파일을 security-config_ver6.xml로 rename 하고 web.xml에도 수정하세요.

우리가 작성한 CustomAuthenticationProvider를 bean으로 등록하고 <security:authentication-provider>에 ref속성으로 설정합니다. 

제대로 CustomAuthenticationProvider의 authenticate메소드가 호출되면 우리가 입력한 데이터가 Authentication의 타입의 형태로 매개변수로 전달됩니다. 

주의하실 점은 인증하는 방식은 여러 가지가 있는데 아이디 패스워드 방식으로 인증을 하려면 supports메서드가 true를 리턴해야 합니다. 제일 먼저 supports메서드를 호출하여 판단하고 authenticate메서드가 호출되기 때문에 반드시 true를 리턴하는지 확인해야 합니다.

<security:authentication-manager>
	<security:authentication-provider ref="customProvider">
	</security:authentication-provider>
</security:authentication-manager>
<bean id="passEncoder" 	class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
	<constructor-arg name="strength" value="256"/>
</bean>
<bean id="customProvider" class="ktds.erp.emp.authentication.CustomAuthenticationProvider"/>
<import resource="spring-config.xml"/> 

 

UsernamePasswordAuthenticationToken 타입으로 매개변수가 전달되는 것을 알 수 있습니다. UsernamePasswordAuthenticationToken는 Authentication를 구현하고 있는 클래스입니다.

 

잘 실행되고 요청되는 것을 확인했으니까 이제 비밀번호 비교 로직을 넣어볼까요? 패스워드 비교 로직을 넣기 전에 우선 UsernamePasswordAuthenticationToken에서 데이터를 추출해 보도록 하겠습니다.

public Authentication authenticate(Authentication data) throws AuthenticationException {
		System.out.println("CustomAuthenticationProvider=>"+data);
		//사용자가 입력한 데이터 추출하는 작업
		String username = data.getName();//아이디
		String pwd = (String)data.getCredentials();
		Object obj = data.getPrincipal();
		System.out.println(username+","+pwd+","+obj); 
		
		return null;
 }

 

위의 코드를 추가하고 실행하면 입력했던 아이디와 패스워드가 추출되는 것을 알 수 있습니다.

 

AuthenticationProvider를 사용자가 작성한 것으로 등록하고 사용할 것이므로 authenticate메서드 안에서 UserDetailsService의 loadUserByUsername를 직접 호출해야 합니다. 그런데 loadUserByUsername 메서드 안에서 리턴했던 User클래스는 간단하게 아이디, 패스워드, 권한 정보만 리턴하도록 했었습니다. 이 정보만 필요하다면 이렇게 사용하면 되지만 보통의 웹 사이트에서는 DTO정보가 다 필요한 경우도 있겠죠? 우리 ERP에서도 로그인하고 난 후 메뉴 페이지에 대한 정보가 있어야 하므로 모든 정보를 다 담을 수 있도록 User를 상속하는 새로운 DTO를 정의하고 이 DTO를 생성해서 리턴하도록 수정하겠습니다. 

User를 상속하는 SecurityLoginDTO를 정의합니다. 생성자 안에서는 User객체의 생성자를 호출해서 필요한 정보를 셋팅하기만 해 주면 됩니다.

package ktds.erp.emp.authentication;

import java.sql.Date;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.multipart.MultipartFile;

public class SecurityLoginDTO extends User{
	private String id;
	private String pass;
	private String name;
	private String ssn;
	private String birthday;
	private String marry;
	private String gender;
	private String position;
	private String duty;
	private String classes;
	private Date startday;
	private Date endday;
	private String deptno;
	private String curstate;
	private String zipcode;
	private String addr;
	private String detailaddr;
	private String phonehome;
	private String phoneco;
	private String phonecell;
	private String email;
	private String profile_photo;
	private String deptname;
	private String job_category;
	private String menupath;
	
	public SecurityLoginDTO(String mem_id, String pwd,
			boolean enabled, boolean accountNonExpired,
			boolean credentialsNonExpired, boolean accountNonLocked,
			Collection<? extends GrantedAuthority> authorities) {
		super(mem_id,pwd, enabled, accountNonExpired, 
				credentialsNonExpired, accountNonLocked, authorities);
	}
	public SecurityLoginDTO(Collection<? extends GrantedAuthority> authorities,
			String id, String pass, String name, String ssn, String birthday, String marry, 
			String gender,String position, String duty, String classes, Date startday, 
			Date endday, String deptno, String curstate,
			String zipcode, String addr, String detailaddr, String phonehome, 
			String phoneco, String phonecell,
			String email, String profile_photo, String deptname, String job_category,
			String menupath) {
		super(id, pass, authorities);
		this.id = id;
		this.pass = pass;
		this.name = name;
		this.ssn = ssn;
		this.birthday = birthday;
		this.marry = marry;
		this.gender = gender;
		this.position = position;
		this.duty = duty;
		this.classes = classes;
		this.startday = startday;
		this.endday = endday;
		this.deptno = deptno;
		this.curstate = curstate;
		this.zipcode = zipcode;
		this.addr = addr;
		this.detailaddr = detailaddr;
		this.phonehome = phonehome;
		this.phoneco = phoneco;
		this.phonecell = phonecell;
		this.email = email;
		this.profile_photo = profile_photo;
		this.deptname = deptname;
		this.job_category = job_category;
		this.menupath = menupath;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPass() {
		return pass;
	}
	public void setPass(String pass) {
		this.pass = pass;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSsn() {
		return ssn;
	}
	public void setSsn(String ssn) {
		this.ssn = ssn;
	}
	public String getBirthday() {
		return birthday;
	}
	public void setBirthday(String birthday) {
		this.birthday = birthday;
	}
	public String getMarry() {
		return marry;
	}
	public void setMarry(String marry) {
		this.marry = marry;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getPosition() {
		return position;
	}
	public void setPosition(String position) {
		this.position = position;
	}
	public String getDuty() {
		return duty;
	}
	public void setDuty(String duty) {
		this.duty = duty;
	}
	public String getClasses() {
		return classes;
	}
	public void setClasses(String classes) {
		this.classes = classes;
	}
	public Date getStartday() {
		return startday;
	}
	public void setStartday(Date startday) {
		this.startday = startday;
	}
	public Date getEndday() {
		return endday;
	}
	public void setEndday(Date endday) {
		this.endday = endday;
	}
	public String getDeptno() {
		return deptno;
	}
	public void setDeptno(String deptno) {
		this.deptno = deptno;
	}
	public String getCurstate() {
		return curstate;
	}
	public void setCurstate(String curstate) {
		this.curstate = curstate;
	}
	public String getZipcode() {
		return zipcode;
	}
	public void setZipcode(String zipcode) {
		this.zipcode = zipcode;
	}
	public String getAddr() {
		return addr;
	}
	public void setAddr(String addr) {
		this.addr = addr;
	}
	public String getDetailaddr() {
		return detailaddr;
	}
	public void setDetailaddr(String detailaddr) {
		this.detailaddr = detailaddr;
	}
	public String getPhonehome() {
		return phonehome;
	}
	public void setPhonehome(String phonehome) {
		this.phonehome = phonehome;
	}
	public String getPhoneco() {
		return phoneco;
	}
	public void setPhoneco(String phoneco) {
		this.phoneco = phoneco;
	}
	public String getPhonecell() {
		return phonecell;
	}
	public void setPhonecell(String phonecell) {
		this.phonecell = phonecell;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getProfile_photo() {
		return profile_photo;
	}
	public void setProfile_photo(String profile_photo) {
		this.profile_photo = profile_photo;
	}
	public String getDeptname() {
		return deptname;
	}
	public void setDeptname(String deptname) {
		this.deptname = deptname;
	}
	public String getJob_category() {
		return job_category;
	}
	public void setJob_category(String job_category) {
		this.job_category = job_category;
	}
	public String getMenupath() {
		return menupath;
	}
	public void setMenupath(String menupath) {
		this.menupath = menupath;
	}
	@Override
	public String toString() {
		return "SecurityLoginDTO [id=" + id + ", pass=" + pass + ", name=" + name + ", ssn=" + ssn + ", birthday="
				+ birthday + ", marry=" + marry + ", gender=" + gender + ", position=" + position + ", duty=" + duty
				+ ", classes=" + classes + ", startday=" + startday + ", endday=" + endday + ", deptno=" + deptno
				+ ", curstate=" + curstate + ", zipcode=" + zipcode + ", addr=" + addr + ", detailaddr=" + detailaddr
				+ ", phonehome=" + phonehome + ", phoneco=" + phoneco + ", phonecell=" + phonecell + ", email=" + email
				+ ", profile_photo=" + profile_photo + ", deptname=" + deptname + ", job_category=" + job_category
				+ ", menupath=" + menupath + "]";
	}
}

 

SecurityLoginService의 loadUserByUsername메소드 안에서도 위에서 작성한 SecurityLoginDTO를 리턴하도록 수정합니다.

package ktds.erp.emp.authentication;


import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import ktds.erp.emp.EmpDAO;
import ktds.erp.emp.LoginDTO;

@Service("loginService")
public class SecurityLoginService implements UserDetailsService {
	@Autowired  
	EmpDAO dao;
	@Autowired 
	AuthorityDAO authDao;
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		System.out.println(username+"........loadUserByUsername");
		//로그인 사용자 db에서 조회하기
		LoginDTO user = dao.findById(username);
		System.out.println("user===>"+user);
		UserDetails loginUser = null;
		
		//권한정보 조회하기
		List<MemberAuthoritysDTO> authorityList = authDao.getAuthorityList(username);
		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
		System.out.println(authorityList);
		for(int i=0;i<authorityList.size();i++) {
			authorities.add(new SimpleGrantedAuthority(authorityList.get(i).getAuthorityname()));
		}
		System.out.println(authorities);
		//인증정보와 권한을 User객체로 변환하여 리턴하기
		System.out.println(authorities);
		loginUser = new SecurityLoginDTO(authorities,user.getId(),user.getPass(),
				user.getName(),user.getSsn(),user.getBirthday(),user.getMarry(),
				user.getGender(),user.getPosition(),user.getDuty(),user.getClasses(),
				user.getStartday(),user.getEndday(),user.getDeptno(),user.getCurstate(),
				user.getZipcode(),user.getAddr(),user.getDetailaddr(),user.getPhonehome(),
				user.getPhoneco(),user.getPhonecell(),user.getEmail(),user.getProfile_photo(),
				user.getDeptname(),user.getJob_category(),user.getMenupath());
		System.out.println(loginUser);
		return loginUser;
	}

}

 

CustomAuthenticationProvider의 메소드에서 SecurityLoginService의 loadUserByUsername메소드를 호출하여 DB에서 인증하고 인증받은 객체를 전달받습니다.

@Autowired
SecurityLoginService securityService;
public Authentication authenticate(Authentication data) throws AuthenticationException {
		System.out.println("CustomAuthenticationProvider=>"+data);
		//사용자가 입력한 데이터 추출하는 작업
		String username = data.getName();//아이디
		String pwd = (String)data.getCredentials();
		Object obj = data.getPrincipal();
		System.out.println(username+","+pwd+","+obj); 
		
        //db인증작업을 하기 위해서 mybatis를 이용해서 db에서 조회한 결과를 저장
		SecurityLoginDTO user = (SecurityLoginDTO)securityService.loadUserByUsername(username);
		System.out.println("CustomAuthenticationProvider==>"+user);
		return null;
 }

 

데이터가 잘 조회된 것을 알 수 있습니다.

 

이제 마지막으로 DB에 저장된 패스워드와 사용자가 입력한 패스워드를 암호화해서 비교해 보도록 하겠습니다. ShaPasswordEncoder의 isPasswordValid메서드를 이용하여 작업합니다. 내부에서 알아서 처리가 됩니다. 첫 번째 매개변수는 DB에 저장된 사용자의 패스워드이고 두 번째 매개변수는 사용자가 로그인하기 위하여 입력한 패스워드입니다. 세 번째 매개변수는 salt를 위해 추가한 아이디입니다.

	@Autowired
	private ShaPasswordEncoder passencoder;
	public Authentication authenticate(Authentication data) throws AuthenticationException {
		System.out.println("CustomAuthenticationProvider=>"+data);
		//사용자가 입력한 데이터 추출하는 작업
		String username = data.getName();//아이디
		String pwd = (String)data.getCredentials();
		Object obj = data.getPrincipal();
		System.out.println(username+","+pwd+","+obj); 
		
		//db인증작업을 하기 위해서 mybatis를 이용해서 db에서 조회한 결과를 저장
		SecurityLoginDTO user = 
		(SecurityLoginDTO)securityService.loadUserByUsername(username);
		System.out.println("CustomAuthenticationProvider==>"+user);
		//db에서 조회한 데이터와 사용자가 입력한 데이터를 비교
		//사용자로 부터 입력받은 데이터를 암호화해서 비교
		boolean state =
			passencoder.isPasswordValid(user.getPassword(), pwd, user.getId());
		System.out.println(state);
				
		
		return null;
	}

 

실행하면 패스워드가 같은 경우 state가 true로 출력되고 패스워드가 다른 경우 false로 출력됩니다.

 

내부에서 지원되는 API를 이용하여 인증처리를 완료하였습니다. 이제 authenticate메소드의 사양대로 작업이 완료되고 인증이 완료되면 이 정보를 Authentication객체의 형태로 만들어 리턴하면 되는데 우리는 Authentication를 구현하고 있는 UsernamePasswordAuthenticationToken을 만들어서 리턴할 것입니다.

	@Autowired
	private ShaPasswordEncoder passencoder;
	public Authentication authenticate(Authentication data) throws AuthenticationException {
		System.out.println("CustomAuthenticationProvider=>"+data);
		//사용자가 입력한 데이터 추출하는 작업
		String username = data.getName();//아이디
		String pwd = (String)data.getCredentials();
		Object obj = data.getPrincipal();
		System.out.println(username+","+pwd+","+obj); 
		
		//db인증작업을 하기 위해서 mybatis를 이용해서 db에서 조회한 결과를 저장
		SecurityLoginDTO user = 
		(SecurityLoginDTO)securityService.loadUserByUsername(username);
		System.out.println("CustomAuthenticationProvider==>"+user);
		//db에서 조회한 데이터와 사용자가 입력한 데이터를 비교
		//사용자로 부터 입력받은 데이터를 암호화해서 비교
		boolean state =
			passencoder.isPasswordValid(user.getPassword(), pwd, user.getId());
		System.out.println(state);
				
		//원하는데이터를 UsernamePasswordAuthenticationToken로 만들어서 리턴  
		UsernamePasswordAuthenticationToken authUser = null;
		if(state) {//로그인 성공
			System.out.println("로그인 성공");
			//security의 authentication객체로 만들어서 리턴하는 것
			authUser =
				new UsernamePasswordAuthenticationToken(user,pwd,user.getAuthorities());
			System.out.println("provider===>"+authUser.getPrincipal());
		}else {
			System.out.println("로그인실패");
		}
				
		
		return authUser;
	}

 

이제 모두 완성하였습니다. 인증이 완료되면 "/index.do"가 실행되도록 하기 위해 <form-login>태그의 "default-target-url"에 정의하였습니다. 

 

실행을 하여 인증을 하면 /index.do가 실행되며 /index.do를 요청하는 컨트롤러의 메서드에서 매개변수로 받은 인증 데이터도 null이 아니라 UsernamePasswordAuthenticationToken이 잘 출력되는 것을 알 수 있습니다.

+ Recent posts