이전 게시글에서 간단히 loginDto에 username을 입력하면 token을 발급하는 부분을 구현해봤습니다!
https://changha-dev.tistory.com/160
이번에는 이어서 회원가입과 로그인 부분을 추가로 구현해보겠습니다.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.13'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
maven {
url 'https://repo.clojars.org'
name 'Clojars'
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-security'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'com.h2database:h2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
}
tasks.named('test') {
useJUnitPlatform()
}
application.yml
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:jwtv1
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
show_sql: true
defer-datasource-initialization: true
jwt:
secret:
controller.MemberController
@RestController
@RequestMapping("/api/v1/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody MemberSignInResquestDto request) throws Exception {
return ResponseEntity.ok().body(memberService.signIn(request));
}
@PostMapping("/join")
@ResponseStatus(HttpStatus.OK)
public Long join(@Valid @RequestBody MemberSignUpRequestDto request) throws Exception {
return memberService.signUp(request);
}
}
dto.MemberSignInRequestDto
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MemberSignInResquestDto {
private String email;
private String password;
}
dto.MemberSignUpRequestDto
@Data
@Builder
@AllArgsConstructor
public class MemberSignUpRequestDto {
@NotBlank(message = "아이디를 입력해주세요.")
private String email;
@NotBlank(message = "닉네임을 입력해주세요.")
@Size(min=2, message = "닉네임이 너무 짧습니다.")
private String nickname;
@NotNull(message = "나이를 입력해주세요")
@Range(min = 0, max = 150)
private int age;
@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
private String checkedPassword;
private com.example.jwtv1.entity.Role role;
@Builder
public Member toEntity() {
return Member.builder()
.email(email)
.nickname(nickname)
.age(age)
.password(password)
.role(Role.USER)
.build();
}
}
entity.BaseTimeEntity
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
private LocalDateTime createDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
회원가입시 유저 생성시간과 수정시간을 추가해주는 Entity입니다.
BaseTimeEntity를 추가하면 메인Application에
@EnableJpaAuditing
위 어노테이션을 추가해야 합니다.
@SpringBootApplication
@EnableJpaAuditing
public class JwtV1Application {
public static void main(String[] args) {
SpringApplication.run(JwtV1Application.class, args);
}
}
entity.Member
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Entity
public class Member extends BaseTimeEntity {
@Id @Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 45, unique = true)
private String email;
@Column(length = 45)
private String nickname;
private int age;
@Column(length = 100)
private String password;
@Enumerated(EnumType.STRING)
private Role role;
public void addUserAuthority() {
this.role = Role.USER;
}
public void encodePassword(PasswordEncoder passwordEncoder){
this.password = passwordEncoder.encode(password);
}
}
entity.Role
public enum Role {
USER, MANAGER, ADMIN;
}
repository.MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
config.AuthenticationConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class AuthenticationConfig {
@Value("${jwt.secret}")
private String secretKey;
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic().disable()
.csrf().disable()
.cors().and()
.headers().frameOptions().sameOrigin().and()
.authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/api/v1/members/login").permitAll()
.antMatchers("/api/v1/members/join").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/**").authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtFilter(secretKey), UsernamePasswordAuthenticationFilter.class)
.build();
}
}
service.MemberService
public interface MemberService {
public String signIn(MemberSignInResquestDto requestDto) throws Exception;
public Long signUp(MemberSignUpRequestDto requestDto) throws Exception;
}
service.impl.MemberServiceImpl
@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
@Value("${jwt.secret}")
private String secretKey;
private Long expiredMs = 1000 * 60 * 60l;
@Override
@Transactional
public Long signUp(MemberSignUpRequestDto requestDto) throws Exception {
if(memberRepository.findByEmail(requestDto.getEmail()).isPresent()){
throw new Exception("이미 존재하는 이메일입니다.");
}
if(!requestDto.getPassword().equals(requestDto.getCheckedPassword())){
throw new Exception("비밀번호가 일치하지 않습니다.");
}
Member member = memberRepository.save(requestDto.toEntity());
member.encodePassword(passwordEncoder);
member.addUserAuthority();
return member.getId();
}
public String signIn(MemberSignInResquestDto requestDto){
Member member = memberRepository.findByEmail(requestDto.getEmail())
.orElseThrow(() -> new IllegalArgumentException("가입되지 않은 이메일 입니다."));
if(!passwordEncoder.matches(requestDto.getPassword(), member.getPassword())){
throw new IllegalArgumentException("잘못된 비밀번호 입니다.");
}
return JwtUtil.createJwt(member.getNickname(), secretKey, expiredMs);
}
}
여기서 언급되지 않은 클래스들은 변경사항이 없어서 작성하지 않았습니다
이전 게시글이나 깃허브 소스코드로 확인하시면 됩니다!
https://github.com/Changha-dev/jwt-security-v1
결과
기존 jwt 코드에 회원가입, 로그인 로직만 추가한 거라 딱히 힘든 부분은 없었다.
accessToken과 refreshToken으로 좀 더 완성도 있게 jwt구현 해봐야겠다
'개발' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 영속성 관리 (0) | 2023.09.10 |
---|---|
[Spring Boot] AccessToken, RefreshToken을 이용한 로그인 구현 (0) | 2023.07.22 |
[Spring Boot] Security + jwt로 인증, 인가 구현하기 (1) | 2023.07.06 |
session, cookie, jwt 관계 및 정리 (1) | 2023.07.01 |
[Spring Boot] 회원 정보 조회 및 삭제 (0) | 2023.02.12 |