2024. 10. 17. 09:13ㆍSpring/JPA 개인과제
※ 일정관리 게시판 만들기(JPA기반)
- 도전 1,2,3,4
레벨 | 기능 내용 |
도전 1,2 | 회원가입,로그인 |
도전 3,4 | 권한 확인(인가), 외부API연동 |
[ 회원가입 ]
[ 회원가입 파일 - PasswordConfig, MemberController, MemberSerivce ]
// Controller
@PostMapping("/member")
public MemberResponseDto createMember(@RequestBody @Valid MemberRequestDto requestDto) {
return memberService.createMember(requestDto);
}
// Service
public MemberResponseDto createMember(MemberRequestDto requestDto) {
String name = requestDto.getName();
String email = requestDto.getEmail();
String pw = passwordEncoder.encode(requestDto.getPw());
String pws = requestDto.getPw();
if(pws == null || pws.isEmpty()){
throw new IllegalArgumentException("비밀번호는 필수로 입력해주시길 바랍니다.");
}
if(pws.length() < 8){
throw new IllegalArgumentException("비밀번호는 최소 8자리 이상 입력해주시길 바랍니다.");
}
Optional<Member> checkName = memberRepository.findByName(name);
if (checkName.isPresent()) {
throw new IllegalArgumentException("중복된 이름(아이디)입니다. 다른 이름(아이디)를 입력해주시기 바랍니다.");
}
Optional<Member> checkEmail = memberRepository.findByEmail(email);
if (checkEmail.isPresent()) {
throw new IllegalArgumentException("중복된 이메일입니다. 다른 이메일을 입력해주시기 바랍니다.");
}
Member member = new Member(name,email,pw);
Member saveMember = memberRepository.save(member);
MemberResponseDto memberResponseDto = new MemberResponseDto(saveMember);
return memberResponseDto;
}
// PasswordConfig
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
[코드설명]
1) 비밀번호 암호화를 위해 BCryptPasswordEncoder를 사용한다
- PasswordConfig에서 PasswordEncoder를 Bean에 등록한다.
- PasswordEncoder는 스프링 시큐리티의 인터페이스 객체로 비밀번호를 암호화하는 역할을 한다.(암호화알고리즘)
2) Service에서 이름,이메일, 비밀번호에 대한 예외처리를 위해 변수 초기화를 진행한다.
- 이름,이메일은 requestDto에서 기본 예외처리를 하고 service에선 중복값을 체크한다.
- 비밀번호는 requestDto에서 예외처리를 진행하고 싶었으나, 어노테이션이 적용되지 않아서 어쩔수없이 service에서 진행을 했다. (비밀번호 최소 8자리, 비밀번호 필수값)
※ 비밀번호 관련 이슈는 추후에 찾아봐야할것 같다.
※ 토큰생성은 로그인시에만 하고, 회원가입시에도 요구사항에 발급해야하지만 작업하지 않았다.
[ 로그인 ]
[ 로그인 파일 - Dto,Controller,Service ]
// LoginRequestDto
@Setter
@Getter
public class LoginRequestDto {
private String email;
private String pw;
}
// Controller
@PostMapping("/member/login")
public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse res) {
return memberService.login(loginRequestDto,res);
}
// Service
public String login(LoginRequestDto loginRequestDto, HttpServletResponse res) {
String email = loginRequestDto.getEmail();
String pw = loginRequestDto.getPw();
Member member = memberRepository.findByEmail(email).orElseThrow(
() -> new IllegalArgumentException("등록된 이메일이 없습니다.")
);
if (!passwordEncoder.matches(pw, member.getPw())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
// 토큰 생성
String token = jwtUtil.createToken(member.getEmail(), member.getAuthority_name());
// 토큰 저장
jwtUtil.addJwtToCookie(token,res);
// 토큰 가공
String tokenValue = jwtUtil.substringToken(token);
// 토큰 예외처리
jwtUtil.validateToken(tokenValue);
return "회원정보 : "+jwtUtil.getUserInfoFromToken(tokenValue)+"\n로그인 성공";
}
[코드설명]
1) Dto에서 먼저 로그인에 필요한 이메일과 비밀번호 항목을 세팅한다.
2) Controller에서 Dto를 담아주고, HttpServletResponse를 활용한다.
- HttpServletResponse는 클라이언트에게 응답을 보내기 서블릿에 전달되는 역할을 한다.
- 서블릿은 객체로 응답코드, 응답메시지 등을 전송한다.
3) Service에서 로그인 처리할때 JWT토큰 관련된 작업을 진행한다.
- 이메일,비밀번호 예외처리 후에 토큰 생성,저장,가공,예외처리,사용자정보를 가져오도록 한다.
- jwtUtil로 토큰 관련된 정보를 가져와서 사용중이다.
4) 마지막에 사용자정보를 리턴해준다.
※ 추후에 복습할 시간이 필요하다.
[ 권한확인(인가) ]
ㄴ 403에러 반환 작업은 따로 하지 않았다.
[ 권한확인 파일 - Controller,Service, Enum ]
// Controller
// 일정 수정
@PutMapping("/schedule/{id}")
public Long updateSchedule(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue, @PathVariable Long id, @RequestBody @Valid ScheduleRequestDto requestDto) {
return scheduleService.updateSchedule(tokenValue,id,requestDto);
}
// 일정 삭제
@DeleteMapping("/schedule/{id}")
public Long deleteSchedule(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue, @PathVariable Long id) {
return scheduleService.deleteSchedule(tokenValue,id);
}
// Service
// 일정 수정
@Transactional
public Long updateSchedule(String tokenValue, Long id, ScheduleRequestDto requestDto) {
String token = jwtUtil.substringToken(tokenValue);
if(!jwtUtil.validateToken(token)){
throw new IllegalArgumentException("Token Error");
}
Claims info = jwtUtil.getUserInfoFromToken(token); // 토큰에서 사용자 정보 가져오기
// ADMIN 일때만 수정 되도록
String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
if(Objects.equals(authority, "ADMIN")){
Schedule schedule = findSchedule(id);
schedule.update(requestDto);
}else{
throw new IllegalArgumentException("일정 수정을 할 수 없는 권한입니다.");
}
return id;
}
public Long deleteSchedule(String tokenValue, Long id) {
String token = jwtUtil.substringToken(tokenValue);
if(!jwtUtil.validateToken(token)){
throw new IllegalArgumentException("Token Error");
}
Claims info = jwtUtil.getUserInfoFromToken(token); // 토큰에서 사용자 정보 가져오기
// ADMIN 일때만 삭제 되도록
String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
if(Objects.equals(authority, "ADMIN")){
Schedule schedule = findSchedule(id);
scheduleRepository.delete(schedule);
}else{
throw new IllegalArgumentException("일정 삭제를 할 수 없는 권한입니다.");
}
return id;
}
// Enum
public enum AuthorityEnum {
MEMBER(Authority.MEMBER),
ADMIN(Authority.ADMIN);
private final String authority;
AuthorityEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String MEMBER = "AUTH_MEMBER";
public static final String ADMIN = "AUTH_ADMIN";
}
}
// memberService - 회원가입시 권한이 저장되는 과정
private final String ADMIN_TOKEN = "AAABnwioslwoekHHHTBcddeeWWSliL"; // 회원가입 관리자 권한 토큰값
AuthorityEnum authority_name = AuthorityEnum.MEMBER;
String admin_token = requestDto.getAdminToken();
if(admin_token != null && !admin_token.isEmpty()){
if(!ADMIN_TOKEN.equals(admin_token)){
throw new IllegalArgumentException("관리자 암호가 일치하지 않아 등록을 할 수 없습니다. 확인바랍니다.");
}
authority_name = AuthorityEnum.ADMIN;
}
[코드설명]
1) AuthorityEnum에서 유저 권한에 대한 세팅을 한다.
- MEMBER, ADMIN으로 유저테이블에 데이터로 들어갈예정이다.
2) memberService에서 ADMIN_TOKEN을 입력해서 일치하면 [ADMIN] 입력하지 않으면 [MEMBER]로 회원의 권한을 부여한다.
3) Controller에서 로그인했을때 토큰으로 추출한 사용자 정보가 필요하기 때문에 @CookieValue 어노테이션을 사용.
- 쿠키 객체로 받아서 HEADER를 가져온다.
- 토큰값을 가져온다.
4) 수정, 삭제시 token으로 예외처리를 진행한 후 사용자정보를 가져온다.
- 사용자정보를 가공해서 [ADMIN] 권한일 경우에만 일정수정,삭제를 할수있게 해준다.
※ 추후에 복습할 시간이 필요하다.
[ 권한확인(인가) 영상 ]
[ 외부API조회 ]
ㄴ API 형태
[ API 조회 - ScheduleService ]
// 날씨 API 조회
public String getWeatherFromApi() {
String url = "https://f-api.github.io/f-api/weather.json";
// API 날짜와 형식 맞춘 후 오늘 날짜 추출
SimpleDateFormat sdf2 = new SimpleDateFormat("MM-dd");
Date now = new Date();
String nowTime = sdf2.format(now);
// 전체 데이터 가공 및 필요한 데이터 추출
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
String str = responseEntity.getBody();
Gson gson = new GsonBuilder().setPrettyPrinting().create();
JsonArray jsonElements = gson.fromJson(str, JsonArray.class);
for(JsonElement obj: jsonElements) {
if(obj.getAsJsonObject().get("date").getAsString().equals(nowTime)){
return obj.getAsJsonObject().get("weather").getAsString();
}
}
return "nothing";
}
// 일정생성
public ScheduleResponseDto createSchedule(Long id, ScheduleRequestDto requestDto) {
findMember(id);
String weather = getWeatherFromApi(); // 날짜 API 호출
requestDto.setWeather(weather); // 날씨 값 넣어주기
Schedule schedule = new Schedule(requestDto);
Schedule saveSchedule = scheduleRepository.save(schedule);
ScheduleResponseDto scheduleResponseDto = new ScheduleResponseDto(saveSchedule);
return scheduleResponseDto;
}
[코드설명]
1) API를 호출한다. 이때 RestTemplate 사용
- RestTemplate : API를 요청하고 응답받을 수 있는 스프링에서 제공하는 라이브러리
2) API 형태와 같은 날짜 형식으로 월-일로 맞춘다.
3) 오늘날짜를 추출해서 해당 월-일 날짜형식으로 변환한다.
4) RestTemplate에서 제공하는 ResponseEntity로 API 정보를 가져온다.
- ResponseEntity안에 getBody로 전체 정보를 가져온다
5) gson라이브러리를 활용하여 받아온 정보를 json형태로 가공한다.
6) 향상된 for문을 사용하여 'date' : 오늘날짜를 if문에 비교하여 조건문안에서 오늘날짜의 날씨를 추출한다.
7) 일정생성 메소드에서 API를 호출을 변수 초기화하고 날씨를 set으로 넣어준다.
8) requestDto에 날씨도 담겼고, 아래에서부터 기존 일정생성을 정상적으로 실행하고, 날씨까지 일정테이블에 저장이 되고 마무리된다.
※ 추후에 복습할 시간이 필요하다.
[ 외부API조회 영상 ]
'Spring > JPA 개인과제' 카테고리의 다른 글
[13] Spring - lv 5 내가 정의한 문제와 해결과정 (8) | 2024.10.31 |
---|---|
[12] Spring - 심화과제 aop 등 트러블슈팅 (8) | 2024.10.31 |
[7] Spring - 개인과제_2차 JPA 다루기 트러블 슈팅 (11) | 2024.10.17 |
[6] Spring - 개인과제_2차 JPA다루기 (필수레벨) (4) | 2024.10.16 |