본문 바로가기

Java & Spring

spring + JWT + Angular 프로젝트 (3) - 회원가입을 위한 백엔드 개발

이번 포스팅에서는 회원가입을 위한 백엔드 DB 설계 및 유저등록 로직을 추가하겠다.


Spring 서버와 DB 연동을 하기 위하여 ORM인 Hibernate 기반의 Spring JPA를 이용할 것이다. 회원가입의 가장 기본이 되는 유저 엔티티와 권한 엔티티를 설계하고 프론트에서 REST로 유저의 정보를 받아 패스워드를 암호화하고 권한을 설정하여 DB에 적재하는 로직을 정리할 것이다.


이 프로젝트에는 H2 DB를 사용하고 hibernate.ddl-auto 설정을 create-drop으로 설정하였기 때문에 코드에 엔테티를 정의해놓으면 애플리케이션 구동시 h2에 자동으로 스키마가 생성된다. 기본적으로 user, authority, user_authority 테이블이 필요하다.


1. Authority 엔티티 생성


먼저 권한 테이블은 권한 목록을 관리하는 테이블이다. 권한은 유저에 따라 ROLE_ADMIN / ROLE_UESR / ROLE_XXX등이 될 수 있다.


@Entity
@Table(name = "authority")
public class Authority extends BaseModel {

private static final long serialVersionUID = 1L;

@NotNull
@Size(min = 0, max = 20)
@Id
@Column(length = 20)
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

Authority authority = (Authority) o;

return !(name != null ? !name.equals(authority.name) : authority.name != null);
}

@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}


2. User 엔티티 생성


User 엔티티에는 기본적인 login, password, email, name 필드 외에 user의 권한을 관리하기 위한 authority 정보를 저장하기 위해 JPA의 joinTable 어노테이션에 정보를 추가한다. user 생성시 user_authority 테이블에 유저id와 권한 매핑정보를 같이 저장한다.


@Entity
@Table(name = "user")
public class User extends BaseModel {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Size(max = 10)
@Column(name = "login", length = 10, unique = true, nullable = false)
private String login;

@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60)
private String password;

@Email
@Size(min = 5, max = 20)
@Column(length = 20, unique = true)
private String email;

@Size(max = 20)
@Column(name = "name", length = 20)
private String name;

@NotNull
@Column(nullable = false)
private boolean activated = false;

@JsonIgnore
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})

@BatchSize(size = 20)
private Set<Authority> authorities = new HashSet<>();
........


3. 회원등록 URL 보안해제 설정


이전 포스팅에서도 살펴보았지만 회원가입을 위한 /register URL에는 보안검증을 하지 않기 위하여 해당 URL에 permitAll()메소드로 설정한 것을 다시 확인할 수 있다.


@Override
protected void configure(HttpSecurity http) throws Exception {
http
....................
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/**").authenticated();
}



4. UserController 등록 URL 설정


UserController 클래스에 /register 경로에 RequestBody 형태로 유저의 정보를 전달하면 기본적인 검증을 하고 유저등록이 완료되면 HTTP 201 Created 정보를 반환한다.


@PostMapping(path = "/register",
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE})
public ResponseEntity registerAccount(@Valid @RequestBody UserDto.Create userDto) {

HttpHeaders textPlainHeaders = new HttpHeaders();
textPlainHeaders.setContentType(MediaType.TEXT_PLAIN);
if (!!StringUtils.isEmpty(userDto.getPassword()) &&
userDto.getPassword().length() >= 4 && userDto.getPassword().length() <= 10) {
return new ResponseEntity<>(CHECK_ERROR_MESSAGE, HttpStatus.BAD_REQUEST);
}
userService.registerAccount(userDto);
return new ResponseEntity<>(HttpStatus.CREATED);
}



5. UserService 유저등록 로직추가


실제로 User를 등록하는 UserService 클래스에서 상세하게 알아보겠다. 먼저 기본적으로 유저와 권한 정보를 저장해야 하기때문에 UserRepository와 AuthorityRepository가 의존성 주입을 받은 것을 확인할 수 있다. 또한 패스워드를 평문으로 저장하지 않고 BCrypt 암호화를 하기 위해 PasswordEncoder를 주입받은 것을 확인할 수 있다.


@Service
@Transactional
public class UserService {

private Logger log = LoggerFactory.getLogger(getClass());

@Autowired
private UserRepository userRepository;

@Autowired
private AuthorityRepository authorityRepository;

@Autowired
private PasswordEncoder passwordEncoder;

public User registerAccount(UserDto.Create userDto) {
userRepository.findOneByLoginOrEmail(userDto.getLogin(), userDto.getEmail())
.ifPresent(user -> {
throw new ApiException("이미 등록된 아이디나 이메일입니다.", HttpStatus.BAD_REQUEST);
});

User user = this.createUser(userDto.getLogin(), userDto.getPassword(),
userDto.getName(), userDto.getEmail().toLowerCase());
return user;
}

public User createUser(String login, String password, String name, String email) {
User newUser = new User();
Authority authority = authorityRepository.findOne(AuthoritiesConstants.USER);
Set<Authority> authorities = new HashSet<>();
String encryptedPassword = passwordEncoder.encode(password);
newUser.setLogin(login);
newUser.setPassword(encryptedPassword);
newUser.setName(name);
newUser.setEmail(email);
newUser.setActivated(false);
authorities.add(authority);
newUser.setAuthorities(authorities);
userRepository.save(newUser);
log.debug("Created Information for User: {}", newUser);
return newUser;
}


다음 포스팅에서는 로그인을 통한 인증 및 Jwt 토큰 생성과 검증로직 구현에 대해 살펴볼 것이다.


위와 관련된 모든 소스는 아래의 github에서 확인 할 수 있다.

-> https://github.com/keumtae-kim/spring-angular-app