Skip to content

Commit

Permalink
Merge pull request #687 from woowacourse-teams/develop
Browse files Browse the repository at this point in the history
release: v1.6.0
  • Loading branch information
jin7969 authored Oct 14, 2022
2 parents fd35940 + 9543a23 commit 1f60b80
Show file tree
Hide file tree
Showing 68 changed files with 1,568 additions and 262 deletions.
3 changes: 3 additions & 0 deletions back/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.projectreactor:reactor-spring:1.0.1.RELEASE'

//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// actuator for monitoring
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.woowacourse.teatime.auth.config;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;

@Configuration
@EnableCaching
@EnableRedisRepositories
public class RedisConfig {

@Value("${spring.redis.host}")
private String host;

@Value("${spring.redis.port}")
private int port;

@Value("${refresh-token.expire-length}")
private Long expireLength;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}

@Bean
public CacheManager cacheManager() {
RedisCacheManagerBuilder builder = RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory());
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(expireLength));
builder.cacheDefaults(configuration);
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package com.woowacourse.teatime.auth.controller;

import com.woowacourse.teatime.auth.controller.dto.GenerateTokenDto;
import com.woowacourse.teatime.auth.controller.dto.LoginRequest;
import com.woowacourse.teatime.auth.controller.dto.LoginResponse;
import com.woowacourse.teatime.auth.controller.dto.RefreshAccessTokenResponse;
import com.woowacourse.teatime.auth.controller.dto.UserAuthDto;
import com.woowacourse.teatime.auth.infrastructure.ResponseCookieTokenProvider;
import com.woowacourse.teatime.auth.service.AuthService;
import com.woowacourse.teatime.auth.service.LoginService;
import com.woowacourse.teatime.auth.service.UserAuthService;
import com.woowacourse.teatime.auth.support.UserAuthenticationPrincipal;
import com.woowacourse.teatime.auth.support.dto.UserRoleDto;
import com.woowacourse.teatime.util.AuthorizationExtractor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -17,10 +31,32 @@
public class AuthController {

private final AuthService authService;
private final LoginService loginService;
private final UserAuthService userAuthService;
private final ResponseCookieTokenProvider cookieTokenProvider;

@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
LoginResponse response = authService.login(request);
return ResponseEntity.ok(response);
}

@PostMapping("/login/v2")
public ResponseEntity<LoginResponse> loginV2(@Valid @RequestBody LoginRequest request,
HttpServletResponse response) {
UserAuthDto userAuthDto = loginService.login(request);
cookieTokenProvider.setCookie(response, userAuthDto.getRefreshToken());
return ResponseEntity.ok(LoginResponse.from(userAuthDto));
}

@GetMapping("/refresh-token")
public ResponseEntity<RefreshAccessTokenResponse> generateToken(
@CookieValue(value = "refreshToken", required = false) Cookie cookie,
HttpServletRequest request,
HttpServletResponse response) {
String token = AuthorizationExtractor.extract(request);
GenerateTokenDto generateTokenDto = userAuthService.generateToken(cookie, token);
cookieTokenProvider.setCookie(response, generateTokenDto.getRefreshToken());
return ResponseEntity.ok(new RefreshAccessTokenResponse(generateTokenDto.getAccessToken()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.woowacourse.teatime.auth.controller.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class GenerateTokenDto {

private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ public class LoginResponse {
private Role role;
private String image;
private String name;

public static LoginResponse from(UserAuthDto userAuthDto) {
return new LoginResponse(userAuthDto.getAccessToken(), userAuthDto.getRole(), userAuthDto.getImage(),
userAuthDto.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.woowacourse.teatime.auth.controller.dto;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class RefreshAccessTokenResponse {

private String accessToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.woowacourse.teatime.auth.controller.dto;

import com.woowacourse.teatime.teatime.domain.Role;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class UserAuthDto {

private String accessToken;
private String refreshToken;
private Role role;
private String image;
private String name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.woowacourse.teatime.auth.domain;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
@RedisHash(value = "refreshToken", timeToLive = 1209600)
public class UserAuthInfo {

@Id
private String refreshToken;
private String accessToken;
private Long userId;
private String role;

public boolean isSameToken(String refreshToken, String accessToken) {
return this.refreshToken.equals(refreshToken)
&& this.accessToken.equals(accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.woowacourse.teatime.auth.exception;

import com.woowacourse.teatime.exception.BadRequestException;

public class WrongTokenException extends BadRequestException {

private static final String ERROR_MESSAGE = "토큰이 잘못되었습니다.";

public WrongTokenException() {
super(ERROR_MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.woowacourse.teatime.auth.infrastructure;

import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpCookie.SameSite;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;

@Component
public class ResponseCookieTokenProvider {

private final Long expireLength;

public ResponseCookieTokenProvider(@Value("${refresh-token.expire-length}") String expireLength) {
this.expireLength = Long.parseLong(expireLength);
}

public void setCookie(HttpServletResponse response, String refreshToken) {
ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken)
.maxAge(expireLength)
.path("/")
.secure(true)
.sameSite(SameSite.NONE.name())
.httpOnly(true)
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.woowacourse.teatime.auth.repository;

import com.woowacourse.teatime.auth.domain.UserAuthInfo;
import org.springframework.data.repository.CrudRepository;

public interface UserAuthInfoRepository extends CrudRepository<UserAuthInfo, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.woowacourse.teatime.auth.service;


import static com.woowacourse.teatime.teatime.domain.Role.COACH;
import static com.woowacourse.teatime.teatime.domain.Role.CREW;

import com.woowacourse.teatime.auth.controller.dto.LoginRequest;
import com.woowacourse.teatime.auth.controller.dto.UserAuthDto;
import com.woowacourse.teatime.auth.domain.UserAuthInfo;
import com.woowacourse.teatime.auth.infrastructure.JwtTokenProvider;
import com.woowacourse.teatime.auth.infrastructure.OpenIdAuth;
import com.woowacourse.teatime.teatime.controller.dto.request.SheetQuestionUpdateRequest;
import com.woowacourse.teatime.teatime.domain.Coach;
import com.woowacourse.teatime.teatime.domain.Crew;
import com.woowacourse.teatime.teatime.repository.CoachRepository;
import com.woowacourse.teatime.teatime.repository.CrewRepository;
import com.woowacourse.teatime.teatime.service.QuestionService;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional
@Service
public class LoginService {

private static final String COACH_EMAIL_DOMAIN = "woowahan";
private static final String DEFAULT_QUESTION_1 = "이번 면담을 통해 논의하고 싶은 내용";
private static final String DEFAULT_QUESTION_2 = "최근에 자신이 긍정적으로 보는 시도와 변화";
private static final String DEFAULT_QUESTION_3 = "이번 면담을 통해 생기기를 원하는 변화";

private final OpenIdAuth openIdAuth;
private final CrewRepository crewRepository;
private final CoachRepository coachRepository;
private final QuestionService questionService;
private final UserAuthService userAuthService;
private final JwtTokenProvider jwtTokenProvider;

@Value("${coaches}")
private List<String> emails;

public UserAuthDto login(LoginRequest loginRequest) {
String code = loginRequest.getCode();
String accessToken = openIdAuth.getAccessToken(code);
UserInfoDto userInfo = openIdAuth.getUserInfo(accessToken);
return getLoginResponse(userInfo);
}

private UserAuthDto getLoginResponse(UserInfoDto userInfo) {
String email = userInfo.getEmail();
String emailDomain = StringUtils.substringBetween(email, "@", ".");

List<String> emails = getEmails();
if (COACH_EMAIL_DOMAIN.equals(emailDomain) || emails.contains(email)) {
return getCoachLoginResponse(userInfo);
}
return getCrewLoginResponse(userInfo);
}

private List<String> getEmails() {
return emails.stream()
.map(String::trim)
.collect(Collectors.toList());
}

private UserAuthDto getCoachLoginResponse(UserInfoDto userInfo) {
Coach coach = coachRepository.findByEmail(userInfo.getEmail())
.orElseGet(() -> saveCoachAndDefaultQuestions(userInfo));
coach.setSlackId(userInfo.getSlackId());
coach.setImage(userInfo.getImage());

Map<String, Object> claims = Map.of("id", coach.getId(), "role", COACH);
String accessToken = jwtTokenProvider.createToken(claims);
String refreshToken = UUID.randomUUID().toString();
userAuthService.save(new UserAuthInfo(refreshToken, accessToken, coach.getId(), COACH.name()));
return new UserAuthDto(accessToken, refreshToken, COACH, coach.getImage(), coach.getName());
}

@NotNull
private Coach saveCoachAndDefaultQuestions(UserInfoDto userInfo) {
Coach coach = coachRepository.save(new Coach(
userInfo.getSlackId(),
userInfo.getName(),
userInfo.getEmail(),
userInfo.getImage()));

List<SheetQuestionUpdateRequest> defaultQuestionDtos = List.of(
new SheetQuestionUpdateRequest(1, DEFAULT_QUESTION_1, true),
new SheetQuestionUpdateRequest(2, DEFAULT_QUESTION_2, true),
new SheetQuestionUpdateRequest(3, DEFAULT_QUESTION_3, true));

questionService.update(coach.getId(), defaultQuestionDtos);
return coach;
}

private UserAuthDto getCrewLoginResponse(UserInfoDto userInfo) {
Crew crew = crewRepository.findByEmail(userInfo.getEmail())
.orElseGet(() -> saveCrew(userInfo));
crew.setSlackId(userInfo.getSlackId());
crew.setImage(userInfo.getImage());

Map<String, Object> claims = Map.of("id", crew.getId(), "role", CREW);
String accessToken = jwtTokenProvider.createToken(claims);
String refreshToken = UUID.randomUUID().toString();
userAuthService.save(new UserAuthInfo(refreshToken, accessToken, crew.getId(), CREW.name()));
return new UserAuthDto(accessToken, refreshToken, CREW, crew.getImage(), crew.getName());
}

@NotNull
private Crew saveCrew(UserInfoDto userInfo) {
Crew newCrew = new Crew(
userInfo.getSlackId(),
userInfo.getName(),
userInfo.getEmail(),
userInfo.getImage());
return crewRepository.save(newCrew);
}
}
Loading

0 comments on commit 1f60b80

Please sign in to comment.