66일: Spring+MyBatis (메일 보내기, 아이디 사용여부 확인, 아이디 찾기, 회원가입서비스, 프로퍼티 직접 생성)

2022.02.17.Thur.

✍ 복습

공통

조금은 다른 spring boot start

보안 수준이 낮은 앱 사용 설정 방법

구글 계정 관리 → 보안 → 보안 수준이 낮은 앱의 엑세스 허용

sql.txt 생성

table 만들어서 cmd창에 복사&붙여넣기

설정 잡기와 파일 추가

설정 잡기
pom.xml
mapper.xml 수정
application.properties
파일 추가
log4jdbc.log4j2.properties
logback-spring.xml

DI하는 방법

생성자 주입(@RequiredArgsConstructor), 새터 주입, 필드 주입(@Autowired)
어떤 DI가 바람직할까?
롬복을 이용해 생성자 주입을 하자

순서대로

com.example.demo

SecurityConfig.java

package com.example.demo;

import org.springframework.beans.factory.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.method.configuration.*;
import org.springframework.security.config.annotation.web.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.security.crypto.password.*;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	private PasswordEncoder passwordEncoder;
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeHttpRequests().antMatchers("/**").permitAll();
	}
}
  • Security 메소드와 상속 : @Configuration, @EnableWebSecurity, @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true), public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
  • protected void configure(HttpSecurity http) throws Exception {
    http : WebSecurityconfigurerAdapter가 제공하는 configure(HttpSecurity http)안에 있는 기본 설정을 제공
  • .authorizeHttpRequests() : 만약 사용자가 인증되지 않았다면, 스프링 시큐리티 필터는 요청을 잡아내고 사용자를 로그인 페이지로 리다이렉션한다.
  • .antMatchers() : antMatcher에 주어지는 패스들은 Ant 스타일 와일드카드를 사용할 수 있다. 패스 명시 가능
  • .permitAll() : 어떠한 보안 요구 없이 요청을 허용

Zboard6Appication.java

package com.example.demo;

import org.mybatis.spring.annotation.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@MapperScan("com.example.demo.dao")
@SpringBootApplication
public class Zboard6Application {

	public static void main(String[] args) {
		SpringApplication.run(Zboard6Application.class, args);
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}
  • @MapperScan("com.example.demo.dao") : 매퍼를 하나씩 등록하는게 아닌 페키지 경로를 지정하여 이하 위치에있는 인터페이스들은 전부 맵퍼로 사용할 수 있다.

com.example.demo.controller

MemberController.java

package com.example.demo.controller;

import java.lang.reflect.Member;
import java.time.LocalDate;

import org.springframework.http.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import com.example.demo.controller.editor.DatePropertyEditor;
import com.example.demo.service.MemberService;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class MemberController {
	private  final MemberService service;
	
	// 사용자 입력을 가지고 command 객체를 생성하는 작업을 스프링에서 binding한다고 한다.
	@InitBinder
	public void init(WebDataBinder wdb) {
		// LocalDate로 변환할 에디터를 등록한다면 
		// wdb.registerCustomEditor(LocalDate,class, new DatePropertyEditor()); → 모든 LocalDate에 대해 동작
		// wdb.registerCustomEditor(LocalDate,class, "birthday", new DatePropertyEditor()); → birthday 필드에 대해 동작
		wdb.registerCustomEditor(LocalDate.class, "birthday", new DatePropertyEditor());
	}
	
	@GetMapping("/member/join")
	public void join() {
	}
	
	@PostMapping("/member/join")
	public String join(Member member) {
		// 아이디, 비번, 이메일, 이름, 생일, 레벨을 사용자가 입력한다(레벨은 연습용)
		// 문자열 "15"를 입력하면 스프링은 String, Integer, Double로 변환할 수 있다
		// 스프링에서 입력할 때 문자열 변환을 담당하는 표준은 PropertyEditor
		// 		스프링은 문자열, 정수, 실수등의 PropertyEditor를 제공한다
		// 그런데 문자열을 날짜로 또는 문자열을 enum으로 변환하는 PropertyEditor는 제공하지 않는다 errorcode400→ 우리가 만들어서 등록하면 된다
		// 우선 "2020-01-10"을 LocalDate로 바꾸는 DatePropertyEditor를 만들자
		service.join(member);
		return "redirect:/member/login";
	}
}
  • @RequiredArgsConstructor : 생성자 주입
  • @InitBinder : 커스텀 데이터 타입 변환을 처리
  • @InitBinder....registerCustomEditor() : 요청 파라미터를 바인딩 하기전에 자동으로 호출되며, 이곳에 registerCustomEditor메서드를 이용해서 WebDataBinder에 직접 정의한 PropertyEditor구현 클래스를 등록할 수 있다.

com.example.demo.controller.editor

DatePropertyEditor.java - 프로퍼티 직접 생성

package com.example.demo.controller.editor;

import java.beans.PropertyEditorSupport;
import java.time.LocalDate;


public class DatePropertyEditor extends PropertyEditorSupport {
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		// setAs ctrl+enter
		// text 파라미터 : 사용자가 입력한 문자열. 지금같은 경우 "2020-11-20"
		String str[] = text.split("-");      // ["2020", "11", "20"]
		Integer year = Integer.parseInt(str[0]);
		Integer month = Integer.parseInt(str[1]);
		Integer day = Integer.parseInt(str[2]);
		
		LocalDate date = LocalDate.of(year, month, day);
		
		// 마지막으로 setValue 사용
		setValue(date);
	}
}

com.example.demo.dao

MemberDao.java

package com.example.demo.dao;

import java.util.Optional;

import org.apache.ibatis.annotations.*;

import com.example.demo.entity.*;

@Mapper
public interface MemberDao {
	public Integer save(Member member);
	
	// mybatis 3.5부터 Optional 리턴을 지원
	public Optional<Member> findById(String username);
	
	// select * from
	public Optional<Member> findByEmail(String email);
	
	// select username from
	// public String findUsernameByEmail(String email);
	
	public Integer update(Member member);
	
	public Integer deleteById(String username);
	
	public Boolean existsById(String username);

}

com.example.demo.entity

Level.java

package com.example.demo.entity;

public enum Level {
	BRONZE, SILVER, GOLD;
}

Member.java

package com.example.demo.entity;

import java.time.*;

import lombok.*;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Member {
	private String username;
	private String password;
	private String irum;
	private String email;
	private LocalDate birthday;
	private LocalDate joinday;
	private Boolean enabled;
	private String authority;
	private String checkcode;
	private Integer count;
	private Level levels;
}

com.example.demo.service

MemberService.java

package com.example.demo.service;

import java.lang.reflect.Member;
import java.util.Optional;

import javax.mail.*;
import javax.mail.internet.*;

import org.springframework.beans.factory.annotation.*;
import org.springframework.mail.javamail.*;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.demo.dao.MemberDao;
import com.example.demo.entity.Level;

import lombok.RequiredArgsConstructor;


// DI하는 방법 : 생성자 주입(@RequiredArgsConstructor), 새터 주입, 필드 주입(@Autowired) → 어떤 DI가 바람직할까? → 롬복을 이용해 생성자 주입을 하자
@RequiredArgsConstructor
@Service
public class MemberService {
	private final JavaMailSender javaMailSender;
	private final MemberDao memberDao;
	private final PasswordEncoder passwordEncoder;
	
	// 메일 보내는 메소드
	public void sendMail(String from, String to, String title, String content) throws MessagingException {
		// MIME : 이메일의 형식 -> 파일의 형식. 파일의 종류를 MIME type
		// html에서 엑셀 문서를 클릭하면 application/excel이라는 MIME 타입이 내 브라우저로 전달된다
		// 		브라우저는 윈도우에서 엑셀을 찾아서 있으면 엑셀을 실행 -> 엑셀 문서가 열린단
		//		엑셀이 없으면 즉 application/excel이란 마임 타입이 알 수 없는 타입이라면 다운로드
		MimeMessage message = javaMailSender.createMimeMessage();
		MimeMessageHelper helper = new MimeMessageHelper(message, false, "utf-8");
		helper.setFrom(from);
		helper.setTo(to);
		helper.setSubject(title);
		helper.setText(content, true);
		javaMailSender.send(message);;
	}
	
	// 아이디 사용여부 확인
	public Boolean idAvailableCheck(String username) {
		// existsById는 null이 발생하지 않는다
		return !memberDao.existsById(username);
	}

	// 아이디 찾기
	public String findId(String email) {
		// return memberDao.findByEmail(email).getUsername(); → 사용자가 없는 경우 NPE(null pointer exception) 발생할 수 있다
		// NPE이 발생하는 지 여부를 판단을 누가 판단할까 → DAO 작성자
		// 메소드를 만드는 사람이 Optional 클래스를 이용해 메소드 사용자에게 NPE여부를 가르쳐주자
		
		// Optional이라는 포장상자안에 Member가 들어있다
		// Optional을 꺼내는 메소드는 get() → null인 경우 NoSuchElememtException 발생
		// memberDao.findByEmail(email).get();	
		Optional<Member> result = memberDao.findByEmail(email);
		if(result.isPresent()==true)
			return result.get().getUsername();
		return "아이디를 찾지 못했습니다";
			
	}
	
	// 회원가입
	public void join(Member member) {
		member.setPassword(passwordEncoder.encode(member.getPassword()));
		System.out.println(member);
		// member.setLevels(Level.BRONZE);
		// memberDao.save(member);
	}
}
  • throws : 메소드나 생성자를 수행할 때 발생하는 exception을 선언할 때 사용하는 키워드로 예외를 자신이 처리하지 않고 자신을 호출하는 메소드에게 책임을 전가한다.
  • throws IllegalArgumentException : 적합하지 않거나(illegal) 적절하지 못한(inappropriate) 인자를 메소드에 넘겨줄 때 예외 처리. 해당 오류는 호출한 쪽에서 처리해야 된다.

Test

MemberDaoTest.java, MemberServiceTest.java

svn으로 파일 가져오기 (헷갈려..)

좋은 웹페이지 즐겨찾기