Skip to content
Closed
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
10 changes: 6 additions & 4 deletions src/main/java/com/juu/juulabel/admin/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Tag(
name = "관리자 API",
Expand All @@ -35,4 +33,8 @@ public ResponseEntity<CommonResponse<Void>> assignBadge(
return CommonResponse.success(SuccessCode.SUCCESS);
}

@GetMapping("/permission/test")
public ResponseEntity<Member> test(@AuthenticationPrincipal Member member) {
return ResponseEntity.ok(member);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@


import com.juu.juulabel.common.provider.JwtTokenProvider;
import com.juu.juulabel.member.domain.Member;
import com.juu.juulabel.member.service.MemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,14 +20,16 @@
public class TestAccessTokenController {

private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;

@Operation(
summary = "JWT 테스트용 토큰 발급 API",
description = "기본 rldh11111@naver.com 이메일로 JWT 발급"
)
@GetMapping("/token")
public String testAccessToken(@RequestParam(defaultValue = "rldh11111@naver.com") String email) {
return jwtTokenProvider.createAccessToken(email);
Member member = memberService.getMemberByEmail(email);
return jwtTokenProvider.createAccessToken(member);
}

}
75 changes: 37 additions & 38 deletions src/main/java/com/juu/juulabel/common/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

import com.juu.juulabel.common.filter.JwtAuthorizationFilter;
import com.juu.juulabel.common.filter.JwtExceptionFilter;
import com.juu.juulabel.member.domain.MemberRole;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
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 org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.List;

import static org.springframework.http.HttpMethod.OPTIONS;
Expand All @@ -29,58 +29,57 @@ public class SecurityConfig {
private final JwtExceptionFilter jwtExceptionFilter;

private static final String[] PERMIT_PATHS = {
"/swagger-ui/**", "/v3/api-docs/**", "/error", "/favicon.ico", "/", "/actuator/**",
"/v1/api/alcohols/**", "/v1/api/terms/**", "/v1/api/images",
"/v1/api/members/**", "/v1/api/shared-space/tasting-notes/**", "/v1/api/notifications/**",
"/v1/api/daily-lives/**", "/v1/api/alcoholicDrinks/**", "v1/api/follow" , "/**"
"/swagger-ui/**", "/v3/api-docs/**", "/error", "/favicon.ico", "/", "/actuator/**",
"/v1/api/alcohols/**", "/v1/api/terms/**", "/v1/api/images",
"/v1/api/members/**", "/v1/api/shared-space/tasting-notes/**", "/v1/api/notifications/**",
"/v1/api/daily-lives/**", "/v1/api/alcoholicDrinks/**", "v1/api/follow",
"/**"
};

private static final String[] ALLOW_ORIGINS = {
"http://localhost:8084",
"http://localhost:8080",
"http://localhost:5173",
"http://localhost:3000",
"https://api.juulabel.com",
"https://qa.juulabel.com",
"https://juulabel.com",
"https://juulabel.shop",
"https://juulabel-front.vercel.app/",
"https://juulabel-front-seven.vercel.app/",
"https://d3jwyw9rpnxu8p.cloudfront.net"
"http://localhost:8084",
"http://localhost:8080",
"http://localhost:5173",
"http://localhost:3000",
"https://api.juulabel.com",
"https://qa.juulabel.com",
"https://juulabel.com",
"https://juulabel.shop",
"https://juulabel-front.vercel.app/",
"https://juulabel-front-seven.vercel.app/",
"https://d3jwyw9rpnxu8p.cloudfront.net"
};

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/v1/api/members/logout").authenticated()
.requestMatchers(OPTIONS, "**").permitAll()
.requestMatchers(PERMIT_PATHS).permitAll()
.anyRequest().authenticated()
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/v1/api/members/logout").authenticated()
.requestMatchers(OPTIONS, "**").permitAll()
.requestMatchers(PERMIT_PATHS).permitAll()
.requestMatchers("/v1/api/admins/permission/test").hasAnyAuthority(MemberRole.ROLE_ADMIN.name())
.anyRequest().authenticated()
)

.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtExceptionFilter, JwtAuthorizationFilter.class)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtExceptionFilter, JwtAuthorizationFilter.class)

.build();
.build();
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowedOrigins(List.of(ALLOW_ORIGINS));
config.addExposedHeader(HttpHeaders.AUTHORIZATION);
config.setAllowCredentials(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@OpenAPIDefinition(
servers = {
@Server(url = "https://dev.juulabel.com", description = "Server"),
@Server(url = "http://localhost:8080", description = "Local")
@Server(url = "http://localhost:8084", description = "Local")
}
)
@Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,71 @@
package com.juu.juulabel.common.provider;

import com.juu.juulabel.common.principal.CustomUserDetailsService;
import com.juu.juulabel.common.principal.JuulabelMember;
import com.juu.juulabel.common.constants.AuthConstants;
import com.juu.juulabel.common.exception.CustomJwtException;
import com.juu.juulabel.common.exception.InvalidParamException;
import com.juu.juulabel.common.exception.code.ErrorCode;
import com.juu.juulabel.member.domain.Member;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.time.Duration;
import java.util.Base64;
import java.util.Date;
import java.util.Optional;
import java.util.*;

@Component
public class JwtTokenProvider {

public static final Long ACCESS_TOKEN_EXPIRE_TIME = Duration.ofHours(6).toMillis();
private static final long ACCESS_TOKEN_EXPIRE_TIME = Duration.ofDays(1).toMillis();
private static final String ISSUER = "juulabel";
private static final String ROLE_CLAIM = "role";

private final SecretKey key;
private final CustomUserDetailsService customUserDetailsService;

public JwtTokenProvider(
@Value("${spring.jwt.secret}") String key,
CustomUserDetailsService customUserDetailsService
) {
public JwtTokenProvider(@Value("${spring.jwt.secret}") String key) {
byte[] keyBytes = Base64.getDecoder().decode(key);
this.key = Keys.hmacShaKeyFor(keyBytes);
this.customUserDetailsService = customUserDetailsService;
}

public String createAccessToken(String email) {
Date now = new Date();
long accessTokenExpireTime = now.getTime() + ACCESS_TOKEN_EXPIRE_TIME;

public String createAccessToken(Member member) {
return Jwts.builder()
.subject(email)
.issuedAt(now)
.expiration(new Date(accessTokenExpireTime))
.subject(String.valueOf(member.getId()))
.claim(ROLE_CLAIM, member.getRole().name())
.issuedAt(new Date())
.issuer(ISSUER)
.expiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE_TIME))
.signWith(key)
.compact();
}

public Authentication getAuthentication(String token) {
JuulabelMember userDetails = (JuulabelMember) customUserDetailsService.loadUserByUsername(getEmailByToken(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());

public Authentication getAuthentication(String accessToken) {
Claims claims = parseClaims(accessToken);

Collection<? extends GrantedAuthority> roles =
Collections.singletonList(new SimpleGrantedAuthority(claims.get(ROLE_CLAIM, String.class)));

Member member = Member.builder()
.id(Long.parseLong(claims.getSubject()))
.build();

return new UsernamePasswordAuthenticationToken(
member,
null,
roles
);
}

public String resolveToken(String header) {
return Optional.ofNullable(header)
.orElseThrow(() -> new InvalidParamException(ErrorCode.INVALID_AUTHENTICATION))
.replace(AuthConstants.TOKEN_PREFIX, "");
.orElseThrow(() -> new InvalidParamException(ErrorCode.INVALID_AUTHENTICATION))
.replace(AuthConstants.TOKEN_PREFIX, "");
}

public boolean isValidateToken(String token) {
Expand All @@ -68,10 +76,6 @@ public boolean isValidateToken(String token) {
return !getExpirationByToken(token).before(new Date());
}

public String getEmailByToken(String token) {
return parseClaims(token).getSubject();
}

public Date getExpirationByToken(String token) {
return parseClaims(token).getExpiration();
}
Expand Down
23 changes: 12 additions & 11 deletions src/main/java/com/juu/juulabel/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public class MemberService {
private final FollowReader followReader;


public Member getMemberByEmail(String email) {
return memberReader.getByEmail(email);
}

@Transactional
public LoginResponse login(OAuthLoginRequest oAuthLoginRequest) {
OAuthLoginInfo authLoginInfo = oAuthLoginRequest.toDto();
Expand All @@ -86,8 +90,13 @@ public LoginResponse login(OAuthLoginRequest oAuthLoginRequest) {

validateNotWithdrawnMember(email);

Token token = createTokenForMember(isNewMember, email); // TODO : 카카오와 구글 이메일이 같다면 토큰 중복 사용 가능 여부 확인

String generatedToken = jwtTokenProvider.createAccessToken(getMemberByEmail(email));
Token token;
if (isNewMember) {
token = new Token(null, null);
}else{
token = new Token(generatedToken, jwtTokenProvider.getExpirationByToken(generatedToken));
}
return new LoginResponse(
token,
isNewMember,
Expand Down Expand Up @@ -117,22 +126,14 @@ public SignUpMemberResponse signUp(SignUpMemberRequest signUpRequest) { // TODO
memberTermsWriter.storeAll(memberTerms);
}

String token = jwtTokenProvider.createAccessToken(member.getEmail());
String token = jwtTokenProvider.createAccessToken(member);

return new SignUpMemberResponse(
member.getId(),
new Token(token, jwtTokenProvider.getExpirationByToken(token))
);
}

private Token createTokenForMember(boolean isNewMember, String email) {
if (isNewMember) {
return new Token(null, null);
} else {
String generatedToken = jwtTokenProvider.createAccessToken(email);
return new Token(generatedToken, jwtTokenProvider.getExpirationByToken(generatedToken));
}
}

private List<MemberAlcoholType> getMemberAlcoholTypeList(Member member, List<Long> alcoholTypeIdList) {
return alcoholTypeIdList.stream()
Expand Down
Loading