Skip to content

Commit

Permalink
Merge pull request #21 from kookmin-sw/BE
Browse files Browse the repository at this point in the history
BE #6, #7, #13
  • Loading branch information
wjdwlghks authored Apr 19, 2024
2 parents e25ff84 + 657d44d commit e5bfc48
Show file tree
Hide file tree
Showing 38 changed files with 1,019 additions and 8 deletions.
6 changes: 6 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ dependencies {

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

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

implementation 'org.springframework.boot:spring-boot-starter-security'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.project.capstone.auth.controller;

import com.project.capstone.auth.controller.dto.LoginRequest;
import com.project.capstone.auth.controller.dto.SignupRequest;
import com.project.capstone.auth.controller.dto.TokenResponse;
import com.project.capstone.auth.service.AuthService;
import lombok.RequiredArgsConstructor;
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;

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

private final AuthService authService;

@PostMapping("/signup")
public ResponseEntity<TokenResponse> signup(@RequestBody SignupRequest request) {
authService.signup(request);
TokenResponse tokenResponse = authService.login(request.email());
return ResponseEntity.ok().body(tokenResponse);
}

@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
TokenResponse tokenResponse = authService.login(request.email());
return ResponseEntity.ok().body(tokenResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.capstone.auth.controller.dto;

public record LoginRequest(
String email
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.project.capstone.auth.controller.dto;

public record SignupRequest(
String email,
String name,
int age,
String gender
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.project.capstone.auth.controller.dto;

public record TokenResponse(
String token
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.project.capstone.auth.domain;

import com.project.capstone.member.domain.Member;
import lombok.AllArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.UUID;

@AllArgsConstructor
public class PrincipalDetails implements UserDetails {

private Member member;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getPassword() {
return null;
}

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

public String getUserId() {return member.getId().toString(); }

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

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

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

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.project.capstone.auth.exception;

import com.project.capstone.common.exception.BaseException;
import com.project.capstone.common.exception.ExceptionType;

public class AuthException extends BaseException {
public AuthException(ExceptionType exceptionType) {
super(exceptionType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.project.capstone.auth.exception;

import com.project.capstone.common.exception.ExceptionType;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;

import static org.springframework.http.HttpStatus.*;

@AllArgsConstructor
public enum AuthExceptionType implements ExceptionType {
ALREADY_EMAIL_EXIST(BAD_REQUEST, 1000, "이메일이 이미 존재합니다."),
EMAIL_NOT_FOUND(NOT_FOUND, 1001, "이메일을 찾을 수 없습니다."),
SIGNATURE_NOT_FOUND(UNAUTHORIZED, 1002, "서명을 확인하지 못했습니다"),
SIGNATURE_INVALID(UNAUTHORIZED, 1003, "서명이 올바르지 않습니다."),
MALFORMED_TOKEN(UNAUTHORIZED, 1004, "토큰의 길이 및 형식이 올바르지 않습니다"),
EXPIRED_TOKEN(UNAUTHORIZED, 1005, "이미 만료된 토큰입니다"),
UNSUPPORTED_TOKEN(UNAUTHORIZED, 1006, "지원되지 않는 토큰입니다"),
INVALID_TOKEN(UNAUTHORIZED, 1007, "토큰이 유효하지 않습니다"),
;

private final HttpStatus status;
private final int exceptionCode;
private final String message;

@Override
public HttpStatus httpStatus() {
return status;
}

@Override
public int exceptionCode() {
return exceptionCode;
}

@Override
public String message() {
return message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.project.capstone.auth.jwt;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final HandlerExceptionResolver resolver;

public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
resolver.resolveException(request, response, null, (Exception) request.getAttribute("exception"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.project.capstone.auth.jwt;


import com.project.capstone.auth.exception.AuthException;
import com.project.capstone.member.exception.MemberException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.SignatureException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

import static com.project.capstone.auth.exception.AuthExceptionType.*;
import static com.project.capstone.member.exception.MemberExceptionType.*;

@RequiredArgsConstructor
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtProvider jwtProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtProvider.resolveToken(request);
try {
String id = jwtProvider.validateTokenAndGetId(token);
Authentication authentication = jwtProvider.createAuthentication(id);
log.info(authentication.getName());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (SecurityException e) {
request.setAttribute("exception", new AuthException(SIGNATURE_NOT_FOUND));
} catch (SignatureException e) {
request.setAttribute("exception", new AuthException(SIGNATURE_INVALID));
} catch (MalformedJwtException e) {
request.setAttribute("exception", new AuthException(MALFORMED_TOKEN));
} catch (ExpiredJwtException e) {
request.setAttribute("exception", new AuthException(EXPIRED_TOKEN));
} catch (UnsupportedJwtException e) {
request.setAttribute("exception", new AuthException(UNSUPPORTED_TOKEN));
} catch (IllegalArgumentException e) {
request.setAttribute("exception", new AuthException(INVALID_TOKEN));
} catch (UsernameNotFoundException e) {
request.setAttribute("exception", new MemberException(MEMBER_NOT_FOUND));
} catch (Exception e) {
request.setAttribute("exception", new Exception());
}

filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.project.capstone.auth.jwt;

import com.project.capstone.auth.service.PrincipalDetailService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtProvider {

@Value("${jwt.secret}")
private String secret;
private Key key;
private static final int EXPIRED_DURATION = 24;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String GRANT_TYPE = "Bearer ";
private final PrincipalDetailService principalDetailService;

@PostConstruct
private void init() {
key = Keys.hmacShaKeyFor(secret.getBytes());
}

public String generate(String id) {
Claims claims = Jwts.claims();
claims.put("id", id);
return generateToken(claims);
}

private String generateToken(Claims claims) {
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(issueAt())
.setExpiration(expireAt())
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}

private Date expireAt() {
LocalDateTime now = LocalDateTime.now();
log.info(Date.from(now.plusHours(EXPIRED_DURATION).atZone(ZoneId.systemDefault()).toInstant()).toString());
return Date.from(now.plusHours(EXPIRED_DURATION).atZone(ZoneId.systemDefault()).toInstant());
}

private Date issueAt() {
LocalDateTime now = LocalDateTime.now();
log.info(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()).toString());
return Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
}

public String resolveToken(HttpServletRequest request) {
String bearToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearToken) && bearToken.startsWith(GRANT_TYPE)) {
return bearToken.substring(7);
}
return null;
}

public String validateTokenAndGetId(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.get("id", String.class);
}

public Authentication createAuthentication(String id) {
UserDetails userDetails = principalDetailService.loadUserByUsername(id);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.project.capstone.auth.service;

import com.project.capstone.auth.controller.dto.SignupRequest;
import com.project.capstone.auth.controller.dto.TokenResponse;
import com.project.capstone.auth.exception.AuthException;
import com.project.capstone.auth.jwt.JwtProvider;
import com.project.capstone.member.domain.Member;
import com.project.capstone.member.domain.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.project.capstone.auth.exception.AuthExceptionType.*;

@RequiredArgsConstructor
@Service
public class AuthService {

private final MemberRepository memberRepository;
private final JwtProvider jwtProvider;
public void signup(SignupRequest request) {
if (memberRepository.findMemberByEmail(request.email()).isPresent()) {
throw new AuthException(ALREADY_EMAIL_EXIST);
}
memberRepository.save(new Member(request));
}
@Transactional
public TokenResponse login(String email) {
Member member = memberRepository.findMemberByEmail(email)
.orElseThrow(() -> new AuthException(EMAIL_NOT_FOUND));
return new TokenResponse(generateToken(member));
}

private String generateToken(Member member) {
return jwtProvider.generate(member.getId().toString());
}
}
Loading

0 comments on commit e5bfc48

Please sign in to comment.