Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

tasks.named('test') {
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/leets/blogapplication/config/TokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ public String generateAccessToken(String email, Long userId, Duration ttl) {
.compact();
}

public String generateAccessToken(Long userId, Duration ttl) {
Date now = new Date();
Date exp = new Date(now.getTime() + ttl.toMillis());

Map<String, Object> claims = new HashMap<>();
claims.put("id", userId);
claims.put("tokenType", "accessToken");

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(props.getIssuer())
.setIssuedAt(now)
.setExpiration(exp)
.addClaims(claims)
.signWith(hmacKey, SignatureAlgorithm.HS256)
.compact();
}

public String generateRefreshToken(String email, Long userId, Duration ttl) {
Date now = new Date();
Date exp = new Date(now.getTime() + ttl.toMillis());
Expand All @@ -87,6 +105,25 @@ public String generateRefreshToken(String email, Long userId, Duration ttl) {
.compact();
}

public String generateRefreshToken(Long userId, Duration ttl) {
Date now = new Date();
Date exp = new Date(now.getTime() + ttl.toMillis());

Map<String, Object> claims = new HashMap<>();
claims.put("id", userId);
claims.put("tokenType", "refreshToken");

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(props.getIssuer())
.setIssuedAt(now)
.setExpiration(exp)
.addClaims(claims)
.signWith(hmacKey, SignatureAlgorithm.HS256)
.compact();
}


public String createNewAccessTokenFromRefresh(String refreshToken, Duration accessTtl) {
Claims claims = parseClaims(refreshToken);
String tokenType = claims.get("tokenType", String.class);
Expand Down
34 changes: 28 additions & 6 deletions src/main/java/leets/blogapplication/config/WebSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
package leets.blogapplication.config;

import leets.blogapplication.handler.KakaoLoginSuccessHandler;
import leets.blogapplication.service.auth.kakao.KakaoOAuth2UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.core.annotation.Order;
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.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
//spring security의 정책, 필터 설정

private final TokenProvider tokenProvider;
private final KakaoOAuth2UserService kakaoOAuth2UserService;
private final KakaoLoginSuccessHandler kakaoLoginSuccessHandler;

public WebSecurityConfig(TokenProvider tokenProvider) {
public WebSecurityConfig(TokenProvider tokenProvider, KakaoOAuth2UserService kakaoOAuth2UserService, KakaoLoginSuccessHandler kakaoLoginSuccessHandler) {
this.tokenProvider = tokenProvider;
this.kakaoOAuth2UserService = kakaoOAuth2UserService;
this.kakaoLoginSuccessHandler = kakaoLoginSuccessHandler;
}

// 체인 1: OAuth2 로그인 전용(세션 필요)
@Bean @Order(1)
SecurityFilterChain oauth2Chain(HttpSecurity http) throws Exception {
http.securityMatcher("/oauth2/**", "/login/**")
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(a -> a.anyRequest().permitAll())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
// ❌ 여기엔 토큰필터 넣지 말 것
.oauth2Login(o -> o
.userInfoEndpoint(u -> u.userService(kakaoOAuth2UserService))
.successHandler(kakaoLoginSuccessHandler) // or .defaultSuccessUrl("/oauth/signed-in", true)
.failureUrl("/login?error") // 에러 확인용
);
return http.build();
}

@Bean
Expand All @@ -28,6 +50,7 @@ public TokenAuthenticationFilter tokenAuthenticationFilter() {
}

@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
Expand All @@ -36,7 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.httpBasic(AbstractHttpConfigurer::disable)

.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login", "/auth/signup").permitAll()
.requestMatchers("/auth/login", "/auth/signup", "/oauth2/**","/login/**","/error").permitAll()
.requestMatchers("/auth/logout", "/comments/**", "/post/**", "/posts/**").authenticated()
.anyRequest().authenticated() // 나머지 보호하려면 이렇게. 전부 공개면 permitAll
)
Expand All @@ -45,7 +68,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

// SecurityContextHolder에서 유저 정보 빼오기 위해서 필수 add 코드
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;

import javax.naming.AuthenticationNotSupportedException;
import java.time.Duration;

@RestController
Expand Down
23 changes: 20 additions & 3 deletions src/main/java/leets/blogapplication/domain/RefreshToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import jakarta.persistence.*;

import java.time.Duration;
import java.time.LocalDateTime;

@Entity
Expand Down Expand Up @@ -31,6 +30,10 @@ public class RefreshToken {
@JoinColumn(name = "user_id", nullable = false)
private User user;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_social_id", nullable = false)
private UserSocial userSocial;

public void setUser(User user) { this.user = user; }
public void setRevoked(boolean revoked) { this.revoked = revoked; }

Expand All @@ -42,13 +45,27 @@ public RefreshToken(User user, String token, LocalDateTime expiresAt, LocalDateT
this.user = user;
}

public RefreshToken(UserSocial userSocial, String token, LocalDateTime expiresAt, LocalDateTime issuedAt, boolean revoked) {
this.refreshToken = token;
this.expiresAt = expiresAt;
this.issuedAt = issuedAt;
this.revoked = revoked;
this.userSocial = userSocial;
}

public static RefreshToken createRefreshToken(String token, User user){
RefreshToken ref = new RefreshToken(user, token, LocalDateTime.now().plusDays(7), LocalDateTime.now(), false);
return ref;
}

public static RefreshToken createRefreshToken(String token, UserSocial userSocial){
RefreshToken ref = new RefreshToken(userSocial, token, LocalDateTime.now(), LocalDateTime.now(), false);
return ref;
}

protected RefreshToken() {}

public Long getUserId() { return user.getId();
}
public Long getUserId() { return user.getId(); }
public Long getSocialUserId() { return userSocial.getId(); }
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
}
52 changes: 26 additions & 26 deletions src/main/java/leets/blogapplication/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -35,18 +36,14 @@ public class User implements UserDetails {
@Column(length=500)
private String profileImage;

//회원가입 방식 및 비밀번호
@Enumerated(EnumType.STRING)
@Column(nullable=false, length=10)
private Provider provider; // EMAIL or KAKAO
@Column(nullable=false)
private LocalDateTime createdAt;
@Column(nullable=true)
private LocalDateTime updatedAt;
@Column
private String providerUserId; // KAKAO
@Column
private String password; // EMAIL
@Column(name = "display_name", nullable = false)
private String displayName;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RefreshToken> refreshTokens = new ArrayList<>();
Expand All @@ -67,21 +64,13 @@ public void removeToken(RefreshToken ref) {
ref.setRevoked(false);
}

public static User createUserWithKakao(String email, String name, LocalDate birthdate,
String nickname, String intro, String profileImage, Provider provider,
String providerUserId) {
User user = new User();
user.status = UserStatus.ACTIVE;
user.email = email;
user.name = name;
user.birthdate = birthdate;
user.nickname = nickname;
user.intro = intro;
user.profileImage = profileImage;
user.provider = Provider.KAKAO;
user.createdAt = LocalDateTime.now();
user.providerUserId = providerUserId;
return user;
public static User newSocial(String email, String displayName) {
User u = new User();
u.setEmail(email);
u.setPassword(null);
u.setDisplayName(displayName != null ? displayName : "KakaoUser");
u.setCreatedAt(LocalDateTime.now());
return u;
}

public static User createUserWithEmail(String email, String name, LocalDate birthdate,
Expand All @@ -94,13 +83,12 @@ public static User createUserWithEmail(String email, String name, LocalDate birt
user.nickname = nickname;
user.intro = intro;
user.profileImage = profileImage;
user.provider = Provider.EMAIL;
user.createdAt = LocalDateTime.now();
user.password = password;
return user;
}

public void updateEmailUser(String nickname, String intro, String email, String password, String name,
public void updateUser(String nickname, String intro, String email, String password, String name,
LocalDate birthdate) {
this.nickname = nickname;
this.intro = intro;
Expand All @@ -111,9 +99,6 @@ public void updateEmailUser(String nickname, String intro, String email, String
this.updatedAt = LocalDateTime.now();
}

// public void updateKakaoUser() {}
//카카오는 대체 뭘 변경한다는 건지 모르겟어서 일단 주석처리 했습니다

protected User () {}

// ====== 접근자 ======
Expand All @@ -133,5 +118,20 @@ public Collection<? extends GrantedAuthority> getAuthorities() {
public String getUsername() { return id.toString(); }

public String getEmail() { return email; }

// getters/setters
public void setId(Long id) { this.id = id; }

public void setEmail(String email) { this.email = email; }

public void setPassword(String password) { this.password = password; }

public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }

public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }

public void setNickname(String nickname) { this.nickname = nickname; }
}

65 changes: 65 additions & 0 deletions src/main/java/leets/blogapplication/domain/UserSocial.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package leets.blogapplication.domain;

import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;

@Entity
@Table(name = "user_social",
uniqueConstraints = @UniqueConstraint(columnNames = {"provider", "provider_user_id"}))
public class UserSocial {

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

@Column(nullable = false)
private String profile_nickname;

@Column(name = "provider_user_id", nullable = false, length = 64)
private Long providerUserId;

@Column(nullable = false, length = 20)
private String provider = "KAKAO";

@OneToOne
@JoinColumn(name = "user_id", unique = true)
private User user;

@CreationTimestamp
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;

@UpdateTimestamp
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

public UserSocial() {}

public static UserSocial link(Long userId, String profile_nickname, Long providerUserId) {
UserSocial us = new UserSocial();
User u = new User();
u.setId(userId); // 레퍼런스만 set
u.setNickname(profile_nickname);
Comment on lines +44 to +45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User객체를 생성하고 내부 필드값을 세팅을 하고 있지만 이후 해당 생성된 객체를 사용하지 않는 것으로 보이는데 이렇게 작성하신 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 정적 메서드는 향후에 기존 User에 저장되어 있는 사용자와 UserSocial을 연동할 때 사용될 것으로 생각되어 제작한 메서드입니다. 아직 해당 기능은 구현하지 못해 참조없음으로 남겨두었습니다.

us.setProviderUserId(providerUserId);
return us;
}

// getters/setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getProfile_nickname() { return profile_nickname; }
public void setProfile_nickname(String profile_nickname) {}

public Long getProviderUserId() { return providerUserId; }
public void setProviderUserId(Long providerUserId) { this.providerUserId = providerUserId; }

public User getUser() { return user;
}

public void setNickname(String nickname) { this.profile_nickname = nickname; }
}

Loading