Skip to content

Commit

Permalink
Feat: 회원가입 및 로그인 구현
Browse files Browse the repository at this point in the history
회원가입 JWT토큰 구현 완료 및 로그인 구현 완료

Related to: #5
  • Loading branch information
crayon committed Mar 18, 2022
1 parent 83e534a commit 33c0572
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 65 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ repositories {
}

dependencies {
implementation 'junit:junit:4.13.2'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'
compileOnly 'org.projectlombok:lombok'
Expand Down
Binary file modified build/tmp/compileJava/previous-compilation-data.bin
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@SpringBootApplication
public class MoodstationApplication {
public static void main(String[] args) {
SpringApplication.run(MoodstationApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.moodstation.springboot.config;

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

private final JwtTokenProvider jwtTokenProvider;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 헤더에서 JWT 를 받아옵니다.
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
// 유효한 토큰인지 확인합니다.
if (token != null && jwtTokenProvider.validateToken(token)) {
// 토큰이 유효하면 토큰으로부터 유저 정보를 받아옵니다.
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext 에 Authentication 객체를 저장합니다.
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.moodstation.springboot.config;

import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.Base64;
import java.util.Date;
import java.util.List;

@RequiredArgsConstructor
@Component
public class JwtTokenProvider {

private String secretKey = "moodstationsecret";

// 토큰 유효시간 30일
private long tokenValidTime = 30 * 24 * 60 * 60 * 1000L;

private final UserDetailsService userDetailsService;

// 객체 초기화, secretKey를 Base64로 인코딩한다.
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}

// JWT 토큰 생성
public String createToken(String userPk, List<String> roles) {
Claims claims = Jwts.claims().setSubject(userPk); // JWT payload 에 저장되는 정보단위
claims.put("roles", roles); // 정보는 key / value 쌍으로 저장된다.
Date now = new Date();
return Jwts.builder()
.setClaims(claims) // 정보 저장
.setIssuedAt(now) // 토큰 발행 시간 정보
.setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time
.signWith(SignatureAlgorithm.HS256, secretKey) // 사용할 암호화 알고리즘과
// signature 에 들어갈 secret값 세팅
.compact();
}

// JWT 토큰에서 인증 정보 조회
public Authentication getAuthentication(String token) {
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserPk(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

// 토큰에서 회원 정보 추출
public String getUserPk(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

// Request의 Header에서 token 값을 가져옵니다. "X-AUTH-TOKEN" : "TOKEN값'
public String resolveToken(HttpServletRequest request) {
return request.getHeader("ACCESS-TOKEN");
}

// 토큰의 유효성 + 만료일자 확인
public boolean validateToken(String jwtToken) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.moodstation.springboot.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

private final JwtTokenProvider jwtTokenProvider;

// 암호화에 필요한 PasswordEncoder를 Bean 등록합니다
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

// authenticationManager를 Bean 등록합니다.
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable() // rest api 만을 고려하여 기본 설정은 해제하겠습니다.
.csrf().disable() // csrf 보안 토큰 disable처리.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 토큰 기반 인증이므로 세션 역시 사용하지 않습니다.
.and()
.authorizeRequests() // 요청에 대한 사용권한 체크
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().permitAll() // 그외 나머지 요청은 누구나 접근 가능
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class);
// JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 전에 넣는다
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moodstation.springboot.dto;

import com.moodstation.springboot.entity.User;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLoginRequestDto {

private String email;
private String password;

@Builder
public UserLoginRequestDto(String email, String password) {
this.email = email;
this.password = password;
}

public User toEntity() {
return User.builder()
.email(email)
.password(password)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import org.springframework.http.HttpStatus;

@Getter
public class UserCreateResponseDto {
public class UserResponseDto {

private HttpStatus statusCode;
private int status;
private String message;
private String email;
private String nickname;

public UserCreateResponseDto(HttpStatus statusCode, UserCreateRequestDto entity) {
this.statusCode = statusCode;
public UserResponseDto(HttpStatus statusCode, User entity) {
this.status = statusCode.value();
this.message = "회원가입 성공";
this.email = entity.getEmail();
this.nickname = entity.getNickname();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@

@Getter
@NoArgsConstructor
public class UserCreateRequestDto {
public class UserSignupRequestDto {

private String email;
private String nickname;
private String password;

@Builder
public UserCreateRequestDto(String email, String nickname, String password) {
public UserSignupRequestDto(String email, String nickname, String password) {
this.email = email;
this.nickname = nickname;
this.password = password;
Expand Down
75 changes: 48 additions & 27 deletions src/main/java/com/moodstation/springboot/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,48 +1,69 @@
package com.moodstation.springboot.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "user")
public class User {
public class User implements UserDetails {

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

@Column(nullable = false)
@Column(length = 100, nullable = false, unique = true)
private String email;

@Column(nullable = false)
@Column(length = 20, nullable = false, unique = true)
private String nickname;

@Column(nullable = false)
@Column(length = 30, nullable = false)
private String password;

@Column(name = "created_at")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDateTime createdAt;

@Column(name = "updated_at")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDateTime updatedAt;

@Builder
public User(String email, String nickname, String password) {
this.email = email;
this.nickname = nickname;
this.password = password;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}

@Override
public String getUsername() {
return email;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,5 @@

public interface UserRepository extends JpaRepository<User, Long> {

// 이메일 중복 방지
Optional<User> findByEmail(String email);

// 닉네임 중복 방지
Optional<User> findByNickname(String nickname);
}
24 changes: 10 additions & 14 deletions src/main/java/com/moodstation/springboot/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
package com.moodstation.springboot.service;

import com.moodstation.springboot.dto.UserCreateRequestDto;
import com.moodstation.springboot.dto.UserCreateResponseDto;
import com.moodstation.springboot.dto.UserResponseDto;
import com.moodstation.springboot.entity.User;
import com.moodstation.springboot.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@RequiredArgsConstructor
@Service
public class UserService {
public class UserService implements UserDetailsService {

private final UserRepository userRepository;

// 회원가입
public String save(UserCreateRequestDto requestDto) {
userRepository.save(User.builder()
.email(requestDto.getEmail())
.nickname(requestDto.getNickname())
.password(requestDto.getPassword())
.build());

return "Success";
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
}
}
Loading

0 comments on commit 33c0572

Please sign in to comment.