Springboot

[Springboot] 32. 사용자 비밀번호 암호화 처리

Song hyun 2024. 8. 13. 11:36
728x90
반응형

[Springboot] 32. 사용자 비밀번호 암호화 처리

 

1. SpringSecurityCrypto 의존성 추가

*Spring Security Crypto란?

 

-build.gradle 코드 수정

	// 암호화 
	implementation 'org.springframework.security:spring-security-crypto'
plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.2.8'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	// 의존성 추가 
	implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
	implementation group: 'org.glassfish.web', name: 'jakarta.servlet.jsp.jstl', version: '3.0.0'
	// TODO - check  
	providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
	
	// 암호화 
	implementation 'org.springframework.security:spring-security-crypto'
	
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
	
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

2. 사용자 비밀번호 암호화 처리

 

(1) WebMvcConfig

	@Bean // IOC 대상
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();	
	}
package com.tenco.bank.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.tenco.bank.handler.AuthIntercepter;

import lombok.RequiredArgsConstructor;

@Configuration // <-- 하나의 클래스를 IOC 하고 싶다면 사용
@RequiredArgsConstructor 
public class WebMvcConfig implements WebMvcConfigurer {
	
	@Autowired // DI
	private final AuthIntercepter authIntercepter;
	
	// @RequiredArgsConstructor <-- 생성자 대신 사용 가능!
	
	// 우리가 만든 AuthIntercepter를 등록해야 한다.
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authIntercepter)
		.addPathPatterns("/account/**")
		.addPathPatterns("/auth/**");
	}
	
	@Bean // IOC 대상
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();	
	}
	

}

 

 

(2) UserService 구상

package com.tenco.bank.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.tenco.bank.dto.SignInDTO;
import com.tenco.bank.dto.SignUpDTO;
import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.RedirectException;
import com.tenco.bank.repository.interfaces.UserRepository;
import com.tenco.bank.repository.model.User;

import lombok.RequiredArgsConstructor;

@Service // IoC 대상( 싱글톤으로 관리) 
@RequiredArgsConstructor
public class UserService {
	
	@Autowired
	private final UserRepository userRepository;
	@Autowired
	private final PasswordEncoder passwordEncoder;
	
	
	/**
	 * 회원 등록 서비스 기능
	 * 트랜잭션 처리  
	 * @param dto
	 */
	@Transactional // 트랜잭션 처리는 반드시 습관화 
	public void createUser(SignUpDTO dto) {
		int result = 0; 
		try {
			// 코드 추가 부분
			// 회원 가입 요청 시, 사용자가 던진 비밀번호 값을 암호화처리 해야함
			String hashpwd = passwordEncoder.encode(dto.getPassword());
			System.out.println("암호화 확인: "+hashpwd);
			dto.setPassword(hashpwd);
			result = userRepository.insert(dto.toUser());
		} catch (DataAccessException e) {
			throw new DataDeliveryException("중복 이름을 사용할 수 없습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			throw new RedirectException("알 수 없는 오류", HttpStatus.SERVICE_UNAVAILABLE);
		}
		if(result != 1) {
			throw new DataDeliveryException("회원가입 실패", HttpStatus.INTERNAL_SERVER_ERROR);
		}
	}
	public User readUser(SignInDTO dto) {
		// 유효성 검사는 Controller 에서 먼저 하자. 
		User userEntity = null;  // 지역 변수 선언 
		// 기능 수정
		// username만 가지고 select 처리하기
		// 2가지의 경우의 수 --> 있거나. 없거나
		
		// 객체 안에 사용자의 password가 존재한다. (암호화된 값)
		// passworEncoder 안에 매치하는 메서드 존재 => matches 메서드
		
		try {
			userEntity = userRepository.findByUsernameAndPassword(dto.getUsername(), dto.getPassword());
		} catch (DataAccessException e) {
			throw new DataDeliveryException("잘못된 처리 입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			throw new RedirectException("알수 없는 오류", HttpStatus.SERVICE_UNAVAILABLE);
		}
		
		if(userEntity == null) {
			throw new DataDeliveryException("아이디 혹은 비밀번호가 틀렸습니다.", HttpStatus.BAD_REQUEST);
		}
		
		return userEntity;
	}

 

 

 

3. 비밀번호-암호화된 비밀번호 매칭을 위한 메서드

 

(1) xml / repository 수정

	public User findByUsername(@Param("username") String username);
	<select id="findByUsername" resultType="com.tenco.bank.repository.model.User" >
		select * from user_tb where username = #{username}
	</select>

 

(2) UserService 수정

	public User readUser(SignInDTO dto) {
		// 유효성 검사는 Controller 에서 먼저 하자. 
		User userEntity = null;  // 지역 변수 선언 
		// 기능 수정
		// username만 가지고 select 처리하기
		// 2가지의 경우의 수 --> 있거나. 없거나
		
		// 객체 안에 사용자의 password가 존재한다. (암호화된 값)
		// passworEncoder 안에 매치하는 메서드 존재 => matches 메서드
		
		try {
			userEntity = userRepository.findByUsername(dto.getUsername());
		} catch (DataAccessException e) {
			throw new DataDeliveryException("잘못된 처리 입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			throw new RedirectException("알수 없는 오류", HttpStatus.SERVICE_UNAVAILABLE);
		}
		
		if(userEntity == null) {
			throw new DataDeliveryException("아이디 혹은 비밀번호가 틀렸습니다.", HttpStatus.BAD_REQUEST);
		}
		
		boolean isPwdMatched = passwordEncoder.matches(dto.getPassword(), userEntity.getPassword());
		if(isPwdMatched == false) {
			throw new DataDeliveryException("비밀번호가 틀렸습니다.", HttpStatus.BAD_REQUEST);
		}
		
		return userEntity;
	}
728x90
반응형