이제 로그인 기능 구현이다.
생전 처음 들어보는 oauth2 를 사용하느라 여기저기 블로그도 많이 돌아다니고 유튜브도 많이 들락날락거렸다.
( 인증-인가에서부터 시작해서 oauth의 역할, session, cookie, jwt 기본 개념 공부도 자연스럽게 하게 되었다. )
책에서 spring boot로 oauth2 google api를 통해 로그인을 구현하는 예제를 봐서, 이걸 토대로 열심히 코드를 짜다가 . . . 난관에 부딪혔다.
책에서는 view를 mustache로 구현하는데, 지금 나는 프로젝트에서 react라는 다른 프레임워크를 통해 view를 구현하고 있기 때문이었다. 처음에는 뭐 큰 차이가 있겠나 싶었다. 지금 내가 현재 구현한 back oauth 방식 + 뷰는 react로 짜는 법을 열심히 구글링을 해보는데도 도무지 내가 원하는 해결법이 나오지가 않았다. 그러던 중 계속 계속 파보다가 발견한 한 문장 . .
' react를 사용할 경우에는 이 방식이 아니라 credential 방식을 사용해야 합니다. '
.
.
그래서 다 갈아엎고 그냥 react-google-login 라이브러리의 도움을 받았다.
1. react-google-login 라이브러리 설치
$> npm install react-google-login
2. Header.js 에 로그인 버튼 구현
import { Link } from 'react-router-dom';
import GoogleLogin from 'react-google-login';
import GoogleLogout from 'react-google-login';
import style from './Header.module.css';
import { useState } from 'react';
import AuthService from '../service/AuthService';
const Header = () => {
const [user, setUser] = useState({
email: '',
name: 'stranger',
img: '',
});
const onLoginSuccess = (res) => {
console.log(res);
AuthService.login({
email: res.profileObj.email,
userName: res.profileObj.name,
img: res.profileObj.imageUrl,
})
.then((res) => res.data)
.then(function (data) {
setUser({ email: data.email, name: data.name, img: data.img });
});
};
const onFailure = (err) => {
console.log(err);
};
const onLogoutSuccess = (res) => {
console.log(res);
};
return (
<div className={style.header}>
<Link to="/" className={style.logo}>
FromBookToBook
</Link>
<Link to="/post/write" className={style.nav}>
쓰러가기
</Link>
<Link to="/post" className={style.nav}>
독후감 창고
</Link>
{user.name === 'stranger' ? (
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_ID}
buttonText="Login"
onSuccess={onLoginSuccess}
onFailure={onFailure}
/>
) : (
<img src={user.img} />
)}
</div>
);
};
export default Header;
일단 header에 냅다 로그인 버튼을 구현했다. ( header는 Route를 이용해서 모든 페이지에 적용되도록 구현함 )
대충 버튼이랑 필수 함수들만 짜고 onSuccess 함수 내에 console.log(res)를 찍어봤더니 access token이랑 사용자 정보가 그냥 그대로 넘어왔다. 읭?
내가 공부한바로는 일회용 code + clientId + client secret까지 전부 담아서 요청을 보내야 google server에서 사용자 정보를 주는 걸로 알고 있는데 client secret을 첨부하지 않았는데도 그냥 access token이 와서 뭐지 싶다. 이 프로젝트가 완전 기본 정보 (openId, email, profile) 만 요청해서 그냥 넘어오는 것 같기도하고? 모르겠다.
프론트 측에서 access token을 받아서 처리하는게 찝찝하고 보안상에도 안 좋을 것 같아서 이건 첫 단계 개발이 한바탕 다 끝나고 보완할 때 서버단에서 spring security로 access token을 받아오는 로직으로 다시 짤 생각이다.
일단은 프론트단에서 라이브러리를 사용해서 받아온 email, name, profileImage 를 백엔드로 넘기고, 백엔드에서 로그인 및 회원가입 처리를 하도록 했다. ( 이렇게 되니 oauth는 단순 email + name + profile image 연동 역할밖엔 하지 않음 이게 맞는지 모르겠음 input으로 받는거랑 뭐가 다르니 )
무식하게 큰 프로필 이미지 일단 신경 안 쓰기로 한다.
- User.java
package com.frombooktobook.frombooktobookbackend.domain.user;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.frombooktobook.frombooktobookbackend.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String name;
@Column
private String img;
// @Enumerated(EnumType.STRING)
// @Column(nullable=false)
// private Role role;
@Builder
public User(String email, String name, String img) {
this.email = email;
this.name=name;
this.img = img;
}
public User update(String name, String img) {
this.name= name;
this.img = img;
return this;
}
// public String getRoleKey() {
// return this.role.getKey();
// }
}
- UserRepository.java
package com.frombooktobook.frombooktobookbackend.domain.user;
import com.frombooktobook.frombooktobookbackend.domain.user.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User,Long> {
Optional<User> findByEmail(String email);
}
- UserController.java
package com.frombooktobook.frombooktobookbackend.controller.user;
import com.frombooktobook.frombooktobookbackend.domain.user.User;
import com.frombooktobook.frombooktobookbackend.service.UserService;
import lombok.RequiredArgsConstructor;
import org.apache.coyote.Response;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/oauth/login")
public ResponseEntity<UserResponseDto> login(@RequestBody UserCreateRequestDto requestDto) {
User user = userService.findByEmail(requestDto.getEmail());
if(user == null) {
// 회원가입한 적 없는 새로운 user , 회원가입 진행
try {
user = userService.saveUser(requestDto.toEntity());
} catch(Exception e) {
System.out.println(e);
}
} else {
// 사용자의 name이나 profile image가 바뀌었을 수 있으므로 update 시키기
user = userService.updateUser(user, requestDto.getName(), requestDto.getImg());
}
// 로그인 정보 유지하기 위해 JWT 생성하여 보내기
return ResponseEntity.ok()
.body( new UserResponseDto(user));
}
}
- UserService.java
package com.frombooktobook.frombooktobookbackend.service;
import com.frombooktobook.frombooktobookbackend.domain.user.User;
import com.frombooktobook.frombooktobookbackend.domain.user.UserRepository;
import com.frombooktobook.frombooktobookbackend.controller.user.UserCreateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public User saveUser(User user) {
userRepository.save(user);
return user;
}
public User findByEmail(String email) {
User user = userRepository.findByEmail(email).orElse(null);
return user;
}
@Transactional
public User updateUser(User user, String name, String img) {
user.update(name, img);
return userRepository.save(user);
}
}
미치도록 간단한 백엔드 코드.
아직 로그인 유지 기능은 구현하지 못 해서 ROLE 부분은 주석처리 했다.
구글은 사용자가 이름이랑 프로필 사진을 바꾸는게 가능하므로 로그인 할 때마다 프로젝트 서버에도 업데이트가 되어야 한다.
update가 제대로 되는지 확인하기 위해 h2 콘솔을 이용해서 임의로 이름을 바꿔봤다.
재로그인 해본 결과 성공적으로 name이 update 되는 것 확인 완료
🛠 개선할 사항
F.E
1. 로그인시 사용자 이미지 동그랗게 만들기 (크기도반드시조절)
2. 로그인시 로그아웃 버튼 띄우기
B.E
1. server side에서 access token 받아오도록 구현하기
지금은 새로고침 버튼을 누르면 로그인이 풀려버린다.
이제 새로고침 버튼을 눌러도 사용자가 로그아웃 버튼을 누르지 않는 이상 로그인이 풀리지 않도록 로그인 유지 기능을 구현해보자.
'Project > FromBookToBook' 카테고리의 다른 글
[FBTB] 4. 로그인 유지 기능 구현 [1] (0) | 2022.04.21 |
---|---|
(임시) user/password springSecurity 클래스들 (0) | 2022.04.12 |
[FBTB] 2. 독후감 목록 기능 구현 (0) | 2022.04.01 |
[FBTB] 1. 독후감 작성 기능 구현 (0) | 2022.03.30 |
[Error] PostControllerTest 중 에러 (0) | 2022.03.29 |