1. 서론
스프링 시큐리티를 이용하여 권한을 부여하고, 회원가입 처리를 하는 간단 예제를 만들어 볼 것입니다.
이 글을 이해하면 간단한 회원가입을 스프링 시큐리티로 구현할 수 있습니다.
간단한 MVC 구조를 알고 계신다면 더 편하게 따라오실 수 있습니다.
2. 본론
엔티티 모델링
먼저, 원활한 회원가입을 위한 엔티티 모델링부터 진행합니다.
여기서 주의할 점은 username 부분인데, 시큐리티에서 템플릿과 연동 시 기본으로 제공하는 name 값이 username 이므로 이에 맞추어 줍니다. 커스터마이징 또한 따로 할 수 있으나 추후 포스팅하겠습니다.
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
// 다른 패키지에서 생성자 함부로 생성하지 마세요!
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Account {
@Id @Column(name = "user_id")
// SQL 에서 자동생성되도록 돕는 어노테이션
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String email;
private String age;
private String role;
@Builder
public Account(Long id, String username, String password, String email, String age, String role) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.age = age;
this.role = role;
}
}
레파지토리 모델링
엔티티를 바탕으로 repository 클래스를 만들어줍니다.
package com.example.springsecurity.repository;
import com.example.springsecurity.domain.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountRepository extends JpaRepository<Account,Long> {
}
- 이를 사용하면 JpaRepository에서 제공하는 여러 함수들을 이용할 수 있게 됩니다.
- 자세한 것은 추후 포스팅하겠습니다.
서비스 모델링
- 서비스 클래스를 만들어 Transaction 처리할 수 있는 환경을 만들어줍니다. @Transactional 어노테이션은 함수가 불의의 사고로 구동 실패 시 Rollback 할 수 있도록 안전장치하는 어노테이션이므로 로직 처리는 여기에서 진행해줍니다.
package com.example.springsecurity.service;
import com.example.springsecurity.domain.Account;
import com.example.springsecurity.dto.AccountForm;
import com.example.springsecurity.repository.AccountRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
@Transactional
public Long createUser(AccountForm form) {
Account account = form.toEntity();
accountRepository.save(account);
return account.getId();
}
}
- 여기서 주의할 점은 AccountForm 이라는 DTO 객체가 나와있다는 점입니다. Entity 가 있는데 왜 굳이 DTO를 만들어 toEntity() 처리를 해주어야 하실지 궁금하실 것입니다. 이에 관해서 추후 포스팅하겠습니다.
- save(객체) 구문은 JpaRepository에서 제공하는 함수입니다. entityManager.persist()로 DB에 실제적으로 저장시켜줍니다.
DTO 모델링
package com.example.springsecurity.dto;
import com.example.springsecurity.domain.Account;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@NoArgsConstructor
public class AccountForm {
private Long id;
private String username;
private String password;
private String email;
private String age;
private String role;
@Builder
public AccountForm(Long id, String username, String password, String email, String age, String role) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.age = age;
this.role = role;
}
public Account toEntity(){
return Account.builder()
.id(id)
.username(username)
.password(new BCryptPasswordEncoder().encode(password))
.email(email)
.age(age)
.role(role)
.build();
}
}
- 여기서 주의해서 볼 점은 toEntity() 부분입니다. 스프링 시큐리티에서 제공하는 BCryptPasswordEncoder를 이용하여 사용자 비밀번호를 암호화합니다.
컨트롤러 모델링
package com.example.springsecurity.controller;
import com.example.springsecurity.dto.AccountForm;
import com.example.springsecurity.service.AccountService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@Controller
@RequiredArgsConstructor
public class UserController {
private final AccountService accountService;
@GetMapping("/loginUser")
public String createUserForm(Model model){
model.addAttribute("userForm",new AccountForm());
return "user/login/register";
}
@PostMapping("/loginUser")
public String createUser(@Valid AccountForm form, BindingResult result){
if(result.hasErrors()){
return "user/login/register";
}
accountService.createUser(form);
return "redirect:/";
}
}
- 주의해서 볼 점은 둘 다 "/loginUser" 임에도 불구하고 @GetMapping 인지, @PostMapping 인지에 따라 다른 함수가 실행된다는 것입니다. 자세한 내용은 get, post 방식을 선행 학습하시면 됩니다.
- get 방식으로 접속 시, userForm이라는 객체를 HTML에 전달해주게 됩니다.
- post 방식으로 접속 시, 회원가입 처리를 하게 됩니다.
- BindingResult는 @Valid 어노테이션에서 문제가 생겼을 경우, 처리를 돕는 기본 제공 로직입니다.
- 회원가입이 완료되지 않아 문제가 생겼을 경우 다시 회원가입 페이지로 가도록 하였고, 성공 시 redirect를 사용하여 루트 페이지로 오게 만들었습니다.
프런트엔드 로직
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header"></head>
<body>
<div class="container">
<div th:replace="fragments/bodyHeader :: bodyHeader"></div>
<h1>회원가입</h1>
<form role="form" th:action="@{/loginUser}" th:object="${userForm}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="form-group">
<label th:for="username">아이디</label>
<input type="text" th:field="*{username}" class="form-control" placeholder="아이디 입력해주세요">
</div>
<div class="form-group">
<label th:for="password">비밀번호</label>
<input type="password" class="form-control" th:field="*{password}" placeholder="비밀번호">
</div>
<div class="form-group">
<label th:for="email">이메일</label>
<input type="email" th:field="*{email}" class="form-control" placeholder="이메일 입력해주세요">
</div>
<div class="form-group">
<label th:for="age">나이</label>
<input class="form-control" placeholder="나이를 입력하세요." th:field="*{age}" type="number">
</div>
<div class="form-group">
<label th:for="role">권한</label>
<input class="form-control" placeholder="권한 입력하세요." th:field="*{role}" type="text">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<br/>
<div th:replace="fragments/footer :: footer"></div>
</div>
</body>
</html>
- 저 같은 경우 fragments 내부에 각각의 템플릿 파일들을 삽입하여 꾸며보았습니다.
- 지금 포스팅에서는 설명하지 않겠지만, 관심 있으시다면 thymeleaf th:replace를 검색하여 공부하시면 좋을 것 같습니다.
- 타임리프 문법 중에 th:object = ${userForm}이라는 객체로 controller에서 매핑한 이름을 가지고 받아왔습니다. 옵젝으로 받은 하위 변수에는 ${} 이 아닌 *{}으로 변수를 할당한다는 것에 유의하세요.
- th:field는 name + id입니다.
3. 결론
- 회원가입 기능을 추가하였습니다.
클릭 시 해당 회원가입을 진행하게 됩니다.
회원가입 완료 후 데이터가 잘 저장된 것을 볼 수 있습니다.
password는 아까 설명드린 곳에서 보았듯, 암호화되어 저장되어있습니다.
4. 마무리
- 이상으로 간단한 회원가입 예제를 구현해보았습니다. 이어서 custom 로그인을 구현해보는 예제를 포스팅해볼까 합니다.
+) version 2.0을 만들었습니다.
'Dev > SpringBoot' 카테고리의 다른 글
8. [springboot] Spring Security 간단 권한관리 예제 - AuthenticationProvider 방식 (1) | 2020.06.09 |
---|---|
7. [springboot] Spring Security 간단 권한관리 예제 - UserDetailsService 방식 (1) | 2020.06.09 |
5. [springboot] Spring Security 간단 권한관리 예제 (4) | 2020.06.08 |
4. [springboot] hibernate.hbm2ddl.auto 속성 정리 (0) | 2020.03.26 |
3. [Springboot] Hello JPA! - JPA 시작하기 (1) | 2020.03.26 |