[쇼핑몰만들기] 3. 로그인 기능을 다듬고 상품 등록 기능을 추가하자
User와 Normal_User 엔티티 통합하기
이전 시간에는 자체 로그인을 구현하기 위해서 Normal_User라는 엔티티를 따로 만들어서 관리하였다.
하지만 ! 굳이 두 개를 따로 두어서 관리한다는 것은 그만큼 DB도 많이 사용하고 까다로워져서 비효율적이라고 판단하였다.
그래서 이번 시간에는 자체 로그인과 소셜 로그인을 합쳐볼 것이다.
우선 기존에 소셜 로그인과 자체 로그인의 데이터 차이에 대해서 알아보자.
소셜 로그인 : name, email, picture
자체 로그인 : name, username, password
이처럼 두 로그인의 데이터가 다르기 때문에 각 로그인 시, 데이터를 채우는 작업이 필요했다.
소셜 로그인의 경우, 아이디와 비밀번호를 "null"로 채워주고 자체 로그인은 email과 picture를 "null"로 채워주기로 하였다.
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
private String username; // 자체 로그인과의 호환을 위해서 추가한 데이터
private String password; // 자체 로그인과의 호환을 위해서 추가한 데이터
@Builder
public OAuthAttributes(Map<String, Object> attributes,
String nameAttributeKey, String name,
String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
// .. 코드 중략 ..
// User 엔티티 생성
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.USER)
.username("null") // 소셜 로그인의 경우, null
.password("null") // 소셜 로그인의 경우, null
.build();
}
}
@PostMapping("/register")
public String create(UserForm userForm) {
User user = new User(userForm.getName(), "null", "null", Role.USER, userForm.getUsername(), userForm.getPassword());
userService.create(user); // 자체 로그인의 경우, email과 picture를 null로 채워줌
return "redirect:/";
}
상품 업로드 화면 만들기
그리고 상품을 담을 수 있는 기능을 만들기 위해서 Item이라는 엔티티를 생성하였다.
검색 엔진도 구현할 것이기 때문에 이를 제작하기 위해 상품에 대한 정보를 ElasticSearch에 같이 저장하였다.
@Entity
@Getter
@Setter
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
private String description;
@ManyToOne
@JoinColumn(name = "user")
private User user;
}
상품의 경우, 한 명의 사용자가 여러 개의 상품을 등록할 수 있기 때문에 다대일의 관계로 매핑해주었다.
@Getter
@Setter
@ToString
@Document(indexName = "item")
public class ElasticItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
검색 엔진을 담당하는 ElasticItem 역시 동일하게 작성하였다.
<div th:if="${userName}">
<label th:text="${userName} + '님'"></label>
<a th:href="@{/logout}" class="btn btn-info active" role="button">로그아웃</a>
<a th:href="@{/upload}" class="btn btn-info active" role="button">상품 등록하기</a>
</div>
<div th:if="!${userName}">
<a th:href="@{/login}" class="btn btn-primary me-2 active" role="button">로그인</a>
</div>
일반 사용자 역시 언제든지 상품을 등록할 수 있기 때문에 로그인이 되어있으면 '상품 등록하기' 버튼이 보이게 설정하였고
.authorizeHttpRequests((authorizeRequest) -> authorizeRequest
.requestMatchers("/comments/save", "/upload").hasRole(String.valueOf(Role.USER))
.requestMatchers("/login", "/register", "/login").permitAll()
.requestMatchers("/oauth2/authorization/google").permitAll()
.requestMatchers("/", "/css/**", "images/**", "/js/**", "/login", "/logout/*", "/posts/**", "/comments/**").permitAll()
.anyRequest().authenticated()
)
.logout( // 로그아웃 성공 시 / 주소로 이동
(logoutConfig) -> logoutConfig.logoutSuccessUrl("/")
)
해당 사용자가 권한이 있으면 상품을 등록할 수 있는 "/upload" 경로로 이동할 수 있게 Config를 수정하였다.
하지만 여기서 문제가 발생하였다.
소셜 로그인으로 로그인을 했을 때는 문제가 없었지만, 자체 로그인으로 로그인을 했을 때는 권한이 없다고 판단하고 자동으로 OAuth2 로그인 화면으로 이동하는 것이었다.
분명 자체 로그인에서도 회원가입 시에, Role.USER로 권한이 부여되었고 log를 사용하여 권한을 출력했을 때도 소셜 로그인과 동일한 값이 나오는 것을 확인할 수 있었다.
....
아쉽게도 해당 문제는 아직 해결하지 못해서 우선은 "/upload"의 경로는 모두 접근이 가능하게 하였다.
그리고 "/upload"로 get 요청을 했을 때, 세션이 없다면(로그인이 되어있지 않다면) 로그인 화면으로 이동하게끔 인증/인가 기능을 얼추 구현하여 문제를 해결하였다.
.authorizeHttpRequests((authorizeRequest) -> authorizeRequest
.requestMatchers("/comments/save").hasRole(String.valueOf(Role.USER))
.requestMatchers("/login", "/register", "/login", "/upload").permitAll()
.requestMatchers("/oauth2/authorization/google").permitAll()
.requestMatchers("/", "/css/**", "images/**", "/js/**", "/login", "/logout/*", "/posts/**", "/comments/**").permitAll()
.anyRequest().authenticated()
)
.logout( // 로그아웃 성공 시 / 주소로 이동
(logoutConfig) -> logoutConfig.logoutSuccessUrl("/")
)
@GetMapping("/upload")
public String uploadPage() {
if (httpSession.getAttribute("user") == null)
{
return "redirect:/login";
}
return "upload";
}
오늘은 이렇게 소셜 로그인과 자체 로그인을 통합하고 로그인된 유저만 상품을 등록할 수 있는 기능을 만들어보았다.
다음 시간에는 등록된 상품들을 홈 화면에 출력시키고 장바구니 기능을 만들어보도록 하겠다.