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
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ dependencies {
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

}

//sourceSets {
Expand Down
Binary file not shown.
Binary file modified build/classes/java/main/umc/spring/domain/Member.class
Binary file not shown.
2 changes: 1 addition & 1 deletion build/reports/problems/problems-report.html

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion build/resources/main/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@ spring:
use_sql_comments: true
hbm2ddl:
auto: update
default_batch_fetch_size: 1000
default_batch_fetch_size: 1000

jwt:
token:
secretKey: umceightfightingjwttokenauthentication
expiration:
access: 14400000
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 @@ -26,7 +26,9 @@ public enum ErrorStatus implements BaseErrorCode {

REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION40001", "지역이 존재하지 않습니다."),

RESTAURANT_NOT_FOUND(HttpStatus.NOT_FOUND, "RESTAURANT4001", "식당이 존재하지 않습니다.");
RESTAURANT_NOT_FOUND(HttpStatus.NOT_FOUND, "RESTAURANT4001", "식당이 존재하지 않습니다."),

INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "TOKEN401", "인증이 필요합니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package umc.spring.apiPayload.exception.handler;

import umc.spring.apiPayload.code.BaseErrorCode;
import umc.spring.apiPayload.exception.GeneralException;

public class MemberHandler extends GeneralException {

public MemberHandler(BaseErrorCode baseErrorCode) {
super(baseErrorCode);
}
}
7 changes: 7 additions & 0 deletions src/main/java/umc/spring/config/properties/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package umc.spring.config.properties;

public final class Constants {

public static final String AUTH_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
}
24 changes: 24 additions & 0 deletions src/main/java/umc/spring/config/properties/JwtProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package umc.spring.config.properties;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Getter
@Setter
@ConfigurationProperties("jwt.token")
public class JwtProperties {

private String secretKey = "";
private Expiration expiration;

@Getter
@Setter
public static class Expiration {

private Long access;
// TODO : refreshToken
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package umc.spring.config.security;

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;
import umc.spring.domain.Member;
import umc.spring.repository.MemberRepository.MemberRepository;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member member = memberRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("해당 이메일을 가진 유저가 존재하지 않습니다.: " + username));

return org.springframework.security.core.userdetails.User
.withUsername(member.getEmail())
.password(member.getPassword())
.roles(member.getRole().name())
.build();
}
}
45 changes: 45 additions & 0 deletions src/main/java/umc/spring/config/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package umc.spring.config.security;

import lombok.RequiredArgsConstructor;
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.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 umc.spring.config.security.jwt.JwtAuthenticationFilter;
import umc.spring.config.security.jwt.JwtTokenProvider;

@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(
(requests) -> requests
.requestMatchers("/", "/members/join", "/members/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable())
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package umc.spring.config.security.jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import umc.spring.config.properties.Constants;

import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

String token = resolveToken(request);

if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}

private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(Constants.AUTH_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(Constants.TOKEN_PREFIX)) {
return bearerToken.substring(Constants.TOKEN_PREFIX.length());
}
return null;
}
}
91 changes: 91 additions & 0 deletions src/main/java/umc/spring/config/security/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package umc.spring.config.security.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import umc.spring.apiPayload.code.status.ErrorStatus;
import umc.spring.apiPayload.exception.handler.MemberHandler;
import umc.spring.config.properties.Constants;
import umc.spring.config.properties.JwtProperties;

import java.security.Key;
import java.util.Collections;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

private final JwtProperties jwtProperties;

private Key getSigningKey() {
return Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes());
}

public String generateToken(Authentication authentication) {

String email = authentication.getName();

return Jwts.builder()
.setSubject(email)
.claim("role", authentication.getAuthorities().iterator().next().getAuthority())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration().getAccess()))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}

public boolean validateToken(String token) {

try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}

public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();

String email = claims.getSubject();
String role = claims.get("role", String.class);

User principal = new User(email, "", Collections.singleton(() -> role));
return new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities());
}

public static String resolveToken(HttpServletRequest request) {

String bearerToken = request.getHeader(Constants.AUTH_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(Constants.TOKEN_PREFIX)) {
return bearerToken.substring(Constants.TOKEN_PREFIX.length());
}
return null;
}

public Authentication extractAuthentication(HttpServletRequest request) {

String accessToken = resolveToken(request);
if (accessToken == null || !validateToken(accessToken)) {
throw new MemberHandler(ErrorStatus.INVALID_TOKEN);
}
return getAuthentication(accessToken);
}
}
18 changes: 18 additions & 0 deletions src/main/java/umc/spring/converter/MemberConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ public static MemberResponseDTO.MemberJoinResultDTO toJoinResultDTO(Member membe
.build();
}

public static MemberResponseDTO.LoginResultDTO toLoginResultDTO(Long memberID, String accessToken) {
return MemberResponseDTO.LoginResultDTO.builder()
.memberId(memberID)
.accessToken(accessToken)
.build();
}

public static MemberResponseDTO.MemberInfoDTO toMemberInfoDTO(Member member){
return MemberResponseDTO.MemberInfoDTO.builder()
.name(member.getName())
.email(member.getEmail())
.gender(member.getGender().name())
.build();
}

public static Member toMember(MemberRequestDTO.MemberJoinDto request){
Gender gender = null;

Expand All @@ -34,11 +49,14 @@ public static Member toMember(MemberRequestDTO.MemberJoinDto request){

return Member.builder()
.address(request.getAddress())
.email(request.getEmail())
.password(request.getPassword())
.gender(gender)
.name(request.getName())
.birthYear(request.getBirthYear())
.birthMonth(request.getBirthMonth())
.birthDay(request.getBirthDay())
.role(request.getRole())
.favoriteFoodList(new ArrayList<>())
.build();
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/umc/spring/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import umc.spring.domain.common.BaseEntity;
import umc.spring.domain.enums.Gender;
import umc.spring.domain.enums.MemberStatus;
import umc.spring.domain.enums.Role;
import umc.spring.domain.mapping.FavoriteFood;
import umc.spring.domain.mapping.MemberMission;
import umc.spring.domain.mapping.MemberTerm;
Expand Down Expand Up @@ -73,6 +74,16 @@ public class Member extends BaseEntity {

private String profileImage;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

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

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberTerm> memberTermList = new ArrayList<>();

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/umc/spring/domain/enums/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package umc.spring.domain.enums;

public enum Role {
ADMIN, USER
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
import org.springframework.data.jpa.repository.JpaRepository;
import umc.spring.domain.Member;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member,Long> {

Optional<Member> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import umc.spring.domain.Member;
import umc.spring.web.dto.MemberRequestDTO;
import umc.spring.web.dto.MemberResponseDTO;

public interface MemberCommandService {
Member joinMember(MemberRequestDTO.MemberJoinDto request);

MemberResponseDTO.LoginResultDTO loginMember(MemberRequestDTO.LoginRequestDTO request);
}
Loading