Skip to content

Commit

Permalink
Merge pull request #18 from 9oormDari/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
T-ferret authored Sep 27, 2024
2 parents 3399b45 + 37bb75f commit 3cadb8b
Show file tree
Hide file tree
Showing 15 changed files with 480 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
*.yaml

### STS ###
.apt_generated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.goormdari.domain.user.domain;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

public class CustomUserDetails implements UserDetails {

private final User user;

public CustomUserDetails(User user) {
this.user = user;
}

// ๊ถŒํ•œ ๋ฐ˜ํ™˜
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
}

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

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

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

@Override
public boolean isAccountNonLocked() {
return true;
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/goormdari/domain/user/domain/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.goormdari.domain.user.domain;

public enum Role {
ROLE_USER, ROLE_ADMIN
}
31 changes: 24 additions & 7 deletions src/main/java/com/goormdari/domain/user/domain/User.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.goormdari.domain.user.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.goormdari.domain.common.BaseEntity;
import com.goormdari.domain.team.domain.Team;
import jakarta.persistence.*;
Expand All @@ -14,27 +15,35 @@
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor

public class User extends BaseEntity {

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

private String email;
private int currentStep;

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

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

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

private int currentStep;
private String profileUrl;

private String routineImg1;
private String routineImg2;
private String routineImg3;
private String routineImg4;
private String routinImg1;
private String routinImg2;
private String routinImg3;
private String routinImg4;

private String role;

private String goal;
private LocalDate deadLine;

Expand All @@ -53,4 +62,12 @@ public void updateGoal(String goal) {
public void updateDeadLine(LocalDate deadLine) {
this.deadLine = deadLine;
}

@Builder
public User(String nickname, String username, String password, String role) {
this.nickname = nickname;
this.username = username;
this.password = password;
this.role = role;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.goormdari.domain.user.domain.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AddUserRequest {
@NotBlank(message = "Nickname cannot be blank")
private String nickname;
@NotBlank(message = "Username cannot be blank")
private String username;
@NotBlank(message = "Password cannot be blank")
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.goormdari.domain.user.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class JwtResponse {
private String accessToken;
private String tokenType = "Bearer";

public JwtResponse(String accessToken) {
this.accessToken = accessToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.goormdari.domain.user.domain.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
@NotBlank(message = "Username cannot be blank")
private String username;

@NotBlank(message = "Password cannot be blank")
private String password;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByUsername(String username);

@Query("SELECT t.joinCode FROM User u JOIN u.team t WHERE u.id = :userId")
String findJoinCodeByUserId(Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.goormdari.domain.user.domain.service;

import com.goormdari.domain.user.domain.CustomUserDetails;
import com.goormdari.domain.user.domain.User;
import com.goormdari.domain.user.domain.repository.UserRepository;
import lombok.RequiredArgsConstructor;
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;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

// ์‚ฌ์šฉ์ž ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์†Œ๋“œ
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("๋‹ค์Œ ์œ ์ €์˜ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + username));
return new CustomUserDetails(user);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.goormdari.domain.user.domain.service;

import com.goormdari.domain.user.domain.User;
import com.goormdari.domain.user.domain.dto.AddUserRequest;
import com.goormdari.domain.user.domain.dto.JwtResponse;
import com.goormdari.domain.user.domain.dto.LoginRequest;
import com.goormdari.domain.user.domain.repository.UserRepository;
import com.goormdari.global.config.security.jwt.JWTUtil;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;

@Transactional
public Long save(AddUserRequest dto) {
// ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ค‘๋ณต ์ฒดํฌ
if (userRepository.findByUsername(dto.getUsername()).isPresent()) {
throw new IllegalArgumentException("Username is already exists.");
}

// ์‚ฌ์šฉ์ž ์ €์žฅ
return userRepository.save(User.builder()
.nickname(dto.getNickname())
.username(dto.getUsername())
.password(passwordEncoder.encode(dto.getPassword()))
.role("ROLE_USER")
.build()).getId();
}

public JwtResponse signupAndLogin(AddUserRequest dto) {
save(dto);

return loginAndGetToken(new LoginRequest(dto.getUsername(), dto.getPassword()));
}

public JwtResponse loginAndGetToken(LoginRequest loginRequest) {
// ์‚ฌ์šฉ์ž ์ธ์ฆ
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);

SecurityContextHolder.getContext().setAuthentication(authentication);

// JWT ์ƒ์„ฑ
User user = userRepository.findByUsername(loginRequest.getUsername()).orElseThrow(() -> new UsernameNotFoundException("Username not found with: " + loginRequest.getUsername()));
String jwt = jwtUtil.generateToken(user.getUsername(), user.getRole());

return new JwtResponse(jwt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.goormdari.domain.user.presentation;

import com.goormdari.domain.user.domain.dto.AddUserRequest;
import com.goormdari.domain.user.domain.dto.JwtResponse;
import com.goormdari.domain.user.domain.dto.LoginRequest;
import com.goormdari.domain.user.domain.service.UserService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {

private final UserService userService;

/**
* ํšŒ์›๊ฐ€์ž… ํ›„ JWT ํ† ํฐ ๋ฐœ๊ธ‰
*
* @param addUserRequest ํšŒ์›๊ฐ€์ž… ์š”์ฒญ ๋ฐ์ดํ„ฐ
* @return JWT ์‘๋‹ต
*/
@PostMapping("/signup")
public ResponseEntity<JwtResponse> registerUser(@Valid @RequestBody AddUserRequest addUserRequest) {
JwtResponse jwtResponse = userService.signupAndLogin(addUserRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(jwtResponse);
}

/**
* ๋กœ๊ทธ์ธ ํ›„ JWT ํ† ํฐ ๋ฐœ๊ธ‰
*
* @param loginRequest ๋กœ๊ทธ์ธ ์š”์ฒญ ๋ฐ์ดํ„ฐ
* @return JWT ์‘๋‹ต
*/
@PostMapping("/login")
public ResponseEntity<JwtResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
JwtResponse jwtResponse = userService.loginAndGetToken(loginRequest);
return ResponseEntity.ok(jwtResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package com.goormdari.global.config.security;

import com.goormdari.global.config.security.jwt.JWTFilter;
import com.goormdari.global.config.security.jwt.JWTUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static org.springframework.security.config.Customizer.withDefaults;

Expand All @@ -15,8 +23,13 @@
@EnableWebSecurity
public class SecurityConfig {

private final JWTUtil jwtUtil;
private final UserDetailsService userDetailsService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
JWTFilter jwtFilter = new JWTFilter(jwtUtil, userDetailsService);

http
.cors(withDefaults());
http
Expand All @@ -32,11 +45,27 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/**").permitAll()
.requestMatchers("/reissue").permitAll()
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated());
// ์„ธ์…˜ ์„ค์ • : STATELESS
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
// JWT Filter ์ถ”๊ฐ€
http
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}

// ๋น„๋ฐ€๋ฒˆํ˜ธ ์ธ์ฝ”๋” ๋นˆ ๋“ฑ๋ก
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// ์ธ์ฆ ๊ด€๋ฆฌ์ž ๊ด€๋ จ ์„ค์ •
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Loading

0 comments on commit 3cadb8b

Please sign in to comment.