Home Spring, 이메일 인증 회원가입(1).
Post
Cancel

Spring, 이메일 인증 회원가입(1).

  • 회원 가입시 이메일을 인증받아 회원가입시키는 흐름 구성
    • 프로젝트 셋팅
    • user, role, role_authority, role 테이블 구성
    • 기초 데이터 셋팅을 위한 유틸 서비스 구성
    • Email 서비스 연동
    • security 인증 단계에서 이메일 인증이 진행 중인 상태에 따라 회원가입 플로우 진행


프로젝트 셋팅 및 테이블과 Entity 구성

  • 프로젝트 환경
    • java17, spring boot 3.2.3
  • build.gradle : 주요 설정
    • DAO : spring-boot-starter-data-jpa DB 접근 기술
    • Email : spring-boot-starter-mail 이메일 서비스
    • Security : spring-boot-starter-security 보안 설정
    • tamplate-engine : spring-boot-starter-thymeleaf 템플릿 설정
    • gateway : spring-cloud-starter-openfeign 외부 서버와 통신을 위한 설정
    • database : com.mysql:mysql-connector-j mysql 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'org.spring.oauth2'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2023.0.0")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-mail'

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}


  • 유저, 역할, 역활_권한, 권한 테이블 설계

entity


MappedSuperclass, BaseEntity

  • 공통 테이블 컬럼 정의
    • @MappedSuperclass : 부모 클래스 정의
    • @EntityListeners(AuditingEntityListener.class) :
      • @CreatedDate, @LastModifiedDate : 다음과 같은 어노테이션이 동작할수 있도록 하는 어노테이션
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

  @Column(name = "created_by")
  private String createdBy;

  @CreatedDate
  @Column(name = "created_date")
  private LocalDateTime createdDate;

  @Column(name = "last_modified_by")
  private String lastModifiedBy;

  @LastModifiedDate
  @Column(name = "last_modified_date")
  private LocalDateTime lastModifiedDate;

  @Column(name = "used")
  private boolean used;

}


User

  • UserDetails를 구현하여 실제 검증할때 해당 Entity를 사용할 수 있게 설정.
    • Security를 통한 인증, 인가 부분을 구현하면서 바뀌는 부분이 많아질 예정, 딱히 설명할부분은 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@Getter
@Entity
@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseEntity implements UserDetails {

  @Id
  @Column(name = "user_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "password", nullable = false)
  private String password;

  @Column(name = "user_email", nullable = false, unique = true)
  private String userEmail;

  @ColumnDefault("1")
  @Column(name = "account_non_expired", nullable = false)
  private boolean accountNonExpired;

  @ColumnDefault("1")
  @Column(name = "account_non_locked", nullable = false)
  private boolean accountNonLocked;

  @ColumnDefault("1")
  @Column(name = "credentials_non_expired", nullable = false)
  private boolean credentialsNonExpired;

  @ColumnDefault("1")
  @Column(name = "enabled", nullable = false)
  private boolean enabled;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "role_id")
  private Roles roles;

  public User(String userEmail, String password) {
    this.userEmail = userEmail;
    this.password = password;
    this.accountNonExpired = true;
    this.accountNonLocked = true;
    this.credentialsNonExpired = true;
    this.enabled = true;
  }

  @Override
  public String toString() {
    return "User{" +
      "password='" + password + '\'' +
      ", userEmail='" + userEmail + '\'' +
      '}';
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return roles.getRoleAuthorities().stream()
      .map(a -> new SimpleGrantedAuthority(
        a.getAuthorityId().getAuthorityName()
      )).collect(Collectors.toList());
  }

  @Override
  public String getPassword() {
    return this.password;
  }

  @Override
  public String getUsername() {
    return this.userEmail;
  }

  @Override
  public boolean isAccountNonExpired() {
    return this.accountNonExpired;
  }

  @Override
  public boolean isAccountNonLocked() {
    return this.accountNonLocked;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return this.credentialsNonExpired;
  }

  @Override
  public boolean isEnabled() {
    return this.enabled;
  }

}


Roles

  • 역할 테이블, 유저의 역할을 매핑하기 위함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Getter
@Table(name = "roles")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Roles extends BaseEntity {

  @Id
  @Column(name = "role_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long roleId;

  @Column(name = "role_name", unique = true, nullable = false)
  private String roleName;

  @OneToMany(mappedBy = "roles", fetch = LAZY)
  private List<User> users = new ArrayList<>();

  @OneToMany(mappedBy = "roleId", fetch = LAZY)
  private List<RoleAuthority> roleAuthorities = new ArrayList<>();

}


RoleAuthority

  • 역할과 권한을 중간에서 연결해주는 중간 테이블, 외래키를 복합키로 갖게되는 구조가 된다.
    • @EmbeddedId 복합키를 사용할 때 쓰이는 어노테이션
      • 자매품으로 @IdClass가 있지만 여기서는 @EmbeddedId 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Entity
@Getter
@Table(name = "role_auth")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RoleAuthority extends BaseEntity {

  @EmbeddedId
  private RoleAuthorityId id;

  @MapsId("roleId")
  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "role_id")
  private Roles roleId;

  @MapsId("authorityId")
  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "authority_id")
  private Authority authorityId;

}
  • EmbeddedId
1
2
3
4
5
6
7
8
9
10
11
@Getter
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
public class RoleAuthorityId implements Serializable {

  private Long roleId;
  private Long authorityId;

}


Authority

  • 권한들이 들어가게 되는 테이블 딱히 설명할게 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
@Getter
@Table(name = "authorities")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Authority extends BaseEntity {

  @Id
  @Column(name = "authority_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long authorityId;

  @Column(name = "authority_name", nullable = false, unique = true)
  private String authorityName;

  @Column(name = "end_point", nullable = false)
  private String endPoint;

  @OneToMany(mappedBy = "authorityId", fetch = LAZY)
  private List<RoleAuthority> roleAuthorities = new ArrayList<>();

}


  • 다음에 할것
    • 기초 데이터 셋팅을 위한 유틸 서비스 구성
This post is licensed under CC BY 4.0 by the author.