-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bp 12 implement user login #5
Changes from all commits
9fe150e
efd5047
c4902f2
b21a25b
f17d983
594fa40
4d6d593
f1ec648
f3fbe5c
3a86f06
7e32390
6d90686
4557c99
2b5cf52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import java.io.IOException; | ||
|
||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
import gdsc.konkuk.platformcore.global.responses.ErrorResponse; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { | ||
|
||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws | ||
IOException, ServletException { | ||
|
||
ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.INVALID_USER_INFO); | ||
response.setContentType("application/json"); | ||
response.setCharacterEncoding("UTF-8"); | ||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST); | ||
objectMapper.writeValue(response.getWriter(), errorResponse); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import java.io.IOException; | ||
|
||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.web.DefaultRedirectStrategy; | ||
import org.springframework.security.web.RedirectStrategy; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache; | ||
import org.springframework.security.web.savedrequest.RequestCache; | ||
import org.springframework.security.web.savedrequest.SavedRequest; | ||
import org.springframework.stereotype.Component; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
|
||
@Component | ||
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
||
private RequestCache requestCache = new HttpSessionRequestCache(); | ||
|
||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); | ||
|
||
@Override | ||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { | ||
|
||
setDefaultTargetUrl("/"); | ||
SavedRequest savedRequest = requestCache.getRequest(request, response); | ||
|
||
if(savedRequest != null){ | ||
String targetUrl = savedRequest.getRedirectUrl(); | ||
redirectStrategy.sendRedirect(request, response, targetUrl); | ||
}else { | ||
redirectStrategy.sendRedirect(request, response, getDefaultTargetUrl()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
|
||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
|
||
public class CustomUserDetails implements UserDetails { | ||
|
||
private final Member member; | ||
|
||
public CustomUserDetails(Member member) { | ||
this.member = member; | ||
} | ||
|
||
@Override | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
Collection<GrantedAuthority> authorities = new ArrayList<>(); | ||
authorities.add(new SimpleGrantedAuthority(member.getRole().toString())); | ||
return authorities; | ||
} | ||
|
||
@Override | ||
public String getPassword() { | ||
return member.getPassword(); | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return member.getMemberId(); | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonExpired() { | ||
return member.isActivated(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
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 gdsc.konkuk.platformcore.application.auth.exceptions.InvalidUserInfoException; | ||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; | ||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class CustomUserDetailsService implements UserDetailsService { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException { | ||
|
||
Member member = memberRepository.findByMemberId(memberId) | ||
.orElseThrow(()-> InvalidUserInfoException.of(ErrorCode.USER_NOT_FOUND)); | ||
|
||
return new CustomUserDetails(member); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package gdsc.konkuk.platformcore.application.auth.exceptions; | ||
|
||
import gdsc.konkuk.platformcore.global.exceptions.BusinessException; | ||
import gdsc.konkuk.platformcore.global.exceptions.ErrorCode; | ||
|
||
public class InvalidUserInfoException extends BusinessException { | ||
|
||
private InvalidUserInfoException(String message, String logMessage) { | ||
super(message, logMessage); | ||
} | ||
|
||
public static InvalidUserInfoException of(ErrorCode errorCode) { | ||
return new InvalidUserInfoException(errorCode.getMessage(), errorCode.getLogMessage()); | ||
} | ||
|
||
} | ||
|
This file was deleted.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 유저 엔티티에 저희 ERD 합친거랑 이야기한거로 반영했는데 어떤 것은 Enum으로 관리했어요. DeleteStatus의 경우 0, 1, 2, 3등의 정수값으로 정의할까 하다가. Enum을 통해 관리하는 것이 코드적으로나, 데이터베이스에서도 (명확하게 표현 가능) 좋을 것 같다고 생각해서 Enum을 활용해요. Ordinal이 아닌 String으로 저장하는 것에 의견있으신가요? |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||
package gdsc.konkuk.platformcore.domain.member.entity; | ||||||||||||||
|
||||||||||||||
import java.time.LocalDateTime; | ||||||||||||||
|
||||||||||||||
import jakarta.persistence.Column; | ||||||||||||||
import jakarta.persistence.Entity; | ||||||||||||||
import jakarta.persistence.EnumType; | ||||||||||||||
import jakarta.persistence.Enumerated; | ||||||||||||||
import jakarta.persistence.GeneratedValue; | ||||||||||||||
import jakarta.persistence.Id; | ||||||||||||||
import lombok.AccessLevel; | ||||||||||||||
import lombok.Builder; | ||||||||||||||
import lombok.Getter; | ||||||||||||||
import lombok.NoArgsConstructor; | ||||||||||||||
|
||||||||||||||
@Entity | ||||||||||||||
@Getter | ||||||||||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||||||||||||||
public class Member { | ||||||||||||||
|
||||||||||||||
@Id @GeneratedValue | ||||||||||||||
private Long id; | ||||||||||||||
|
||||||||||||||
@Column(name = "member_id",unique = true) | ||||||||||||||
private String memberId; | ||||||||||||||
|
||||||||||||||
@Column(name = "password") | ||||||||||||||
private String password; | ||||||||||||||
|
||||||||||||||
@Column(name = "member_name") | ||||||||||||||
private String name; | ||||||||||||||
|
||||||||||||||
@Column(name = "member_email") | ||||||||||||||
private String email; | ||||||||||||||
|
||||||||||||||
@Column(name = "profile_image_url") | ||||||||||||||
private String profileImageUrl; | ||||||||||||||
|
||||||||||||||
@Column(name = "is_activated") | ||||||||||||||
private boolean isActivated; | ||||||||||||||
|
||||||||||||||
@Column(name = "is_deleted") | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리뷰 주시면 반영하겠습니다
Suggested change
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 Enum이나 참조 테이블 둘 중 하나면 괜찮다고 생각해요. Delete Policy라 Enum의 field가 추가될 일이 있을 것 같지도 않고요. 이부분은 선호하시는 방향대로 진행해주시면 감사합니다 :)
그런데 한가지 궁금한건, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 실제로 데이터를 테이블에서 지우는지 좀 찾아보다가 넣어놨는데 빼도 상관없을것같아요 |
||||||||||||||
private boolean isDeleted; | ||||||||||||||
|
||||||||||||||
@Column(name = "soft_deleted_at") | ||||||||||||||
private LocalDateTime softDeletedAt; | ||||||||||||||
|
||||||||||||||
@Enumerated(EnumType.STRING) | ||||||||||||||
@Column(name = "member_role") | ||||||||||||||
private MemberRole role; | ||||||||||||||
|
||||||||||||||
@Column(name = "batch") | ||||||||||||||
private int batch; | ||||||||||||||
|
||||||||||||||
@Builder | ||||||||||||||
public Member(Long id, String memberId, String password, String name, String email, String profileImageUrl, | ||||||||||||||
boolean isActivated, boolean isDeleted, LocalDateTime deletedAt, MemberRole role, int batch) { | ||||||||||||||
this.id = id; | ||||||||||||||
this.memberId = memberId; | ||||||||||||||
this.password = password; | ||||||||||||||
this.name = name; | ||||||||||||||
this.email = email; | ||||||||||||||
this.profileImageUrl = profileImageUrl; | ||||||||||||||
this.isActivated = isActivated; | ||||||||||||||
this.isDeleted = isDeleted; | ||||||||||||||
this.softDeletedAt = deletedAt; | ||||||||||||||
this.role = role; | ||||||||||||||
this.batch = batch; | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package gdsc.konkuk.platformcore.domain.member.entity; | ||
|
||
public enum MemberRole { | ||
LEAD("ROLE_LEAD"), | ||
ADMIN("ROLE_ADMIN"), | ||
MEMBER("ROLE_MEMBER"); | ||
|
||
private final String authority; | ||
|
||
MemberRole(String authority) { | ||
this.authority = authority; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return this.authority; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package gdsc.konkuk.platformcore.domain.member.repository; | ||
|
||
import java.util.Optional; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
|
||
public interface MemberRepository extends JpaRepository<Member, Long> { | ||
Optional<Member> findByMemberId(String memberId); | ||
|
||
Member save(Member member); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package gdsc.konkuk.platformcore.global.configs; | ||
|
||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
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.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; | ||
import org.springframework.security.web.context.SecurityContextPersistenceFilter; | ||
|
||
import gdsc.konkuk.platformcore.application.auth.CustomAuthenticationFailureHandler; | ||
import gdsc.konkuk.platformcore.application.auth.CustomAuthenticationSuccessHandler; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
@RequiredArgsConstructor | ||
public class SecurityConfig { | ||
|
||
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; | ||
private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler; | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { | ||
httpSecurity | ||
.addFilterBefore(new SecurityContextPersistenceFilter(), BasicAuthenticationFilter.class) | ||
|
||
.authorizeHttpRequests(authorize -> authorize | ||
.requestMatchers("/docs/**").permitAll() | ||
.requestMatchers("/admin").hasRole("ADMIN") | ||
.requestMatchers("/member").hasRole("MEMBER") | ||
.anyRequest().authenticated()) | ||
|
||
.formLogin(login -> login | ||
.defaultSuccessUrl("/") | ||
.successHandler(customAuthenticationSuccessHandler) | ||
.failureHandler(customAuthenticationFailureHandler) | ||
.permitAll() | ||
); | ||
return httpSecurity.build(); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain swaggerFilterchain(HttpSecurity httpSecurity) throws Exception { | ||
httpSecurity | ||
.securityMatcher("/docs") | ||
.authorizeHttpRequests(authorize -> authorize | ||
.requestMatchers("/**").authenticated()); | ||
return httpSecurity.build(); | ||
} | ||
|
||
@Bean | ||
public BCryptPasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ROLE_" + "실제Role" 로 권한을 부여하는데 저는 그냥 Enum 클래스의 toString 메소드를 오버라이딩하여 Enum의 String 필드를 리턴 해주고 있습니다. 혹시 어떻게 생각하시나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 느끼기엔 크게 문제가 될 여지는 없어 보입니다. 일반적으로는 어떻게 구현하는지 궁금하네요.