config: config set
parent
729d3e2704
commit
420302fee9
@ -1,172 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.controller;
|
|
||||||
|
|
||||||
import com.xit.core.api.IRestResponse;
|
|
||||||
import com.xit.core.api.RestResponse;
|
|
||||||
import com.xit.core.oauth2.api.dto.LoginRequestDto;
|
|
||||||
import com.xit.core.oauth2.api.dto.TokenRequestDto;
|
|
||||||
import com.xit.core.oauth2.api.service.IAuthService;
|
|
||||||
import com.xit.core.oauth2.oauth.JwtTokenProvider;
|
|
||||||
//import com.xit.core.support.jpa.EnumFindUtils;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
|
|
||||||
@Tag(name = "OAuth2LocalController", description = "인증 관리")
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/auth")
|
|
||||||
@Validated
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class OAuth2LocalController {
|
|
||||||
|
|
||||||
|
|
||||||
private final IAuthService authService;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
|
|
||||||
*
|
|
||||||
* 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
|
|
||||||
* 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
|
|
||||||
* AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
|
|
||||||
* authenticate 메소드 하나만 정의되어 있는 인터페이스
|
|
||||||
* Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
|
|
||||||
* 3. 인증정보로 JWT 토큰 생성
|
|
||||||
* 4. Refresh 토큰 저장 : : Redis에 저장
|
|
||||||
* 5. 토큰(Access + Refresh) return
|
|
||||||
* </pre>
|
|
||||||
* @see AuthenticationManagerBuilder
|
|
||||||
* @see JwtTokenProvider
|
|
||||||
*
|
|
||||||
* @param loginRequestDto LoginRequestDto
|
|
||||||
* @param request HttpServletRequest
|
|
||||||
* @param response HttpServletResponse
|
|
||||||
* @param session Session
|
|
||||||
* @return ResponseEntity
|
|
||||||
*/
|
|
||||||
@Operation(summary = "login" , description = "login")
|
|
||||||
// @Parameters({
|
|
||||||
// @Parameter(in = ParameterIn.QUERY, name = "providerType", description = "ProviderType", required = true, example = "GOOGLE"),
|
|
||||||
// @Parameter(in = ParameterIn.QUERY, name = "userId", description = "사용자ID", required = true, example = "minuk926926"),
|
|
||||||
// @Parameter(in = ParameterIn.QUERY, name = "password", description = "비밀번호", required = true, example = "minuk926926")
|
|
||||||
// })
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ResponseEntity<? extends IRestResponse> login(
|
|
||||||
//@Validated
|
|
||||||
@Valid
|
|
||||||
@RequestBody
|
|
||||||
final LoginRequestDto loginRequestDto,
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
HttpSession session
|
|
||||||
//Errors errors
|
|
||||||
|
|
||||||
) {
|
|
||||||
|
|
||||||
return RestResponse.of(
|
|
||||||
authService.login(
|
|
||||||
loginRequestDto,
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
session
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* JWT token 재발급
|
|
||||||
* access token - header, refresh - TokenRequestDto 에 담아 요청
|
|
||||||
*
|
|
||||||
* 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
|
|
||||||
* 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
|
|
||||||
* 3. 2번 항묵에 미 해당시 refresh token 값은 null로
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param tokenRequestDto TokenRequestDto
|
|
||||||
* @param request HttpServletRequest
|
|
||||||
* @return ResponseEntity IRestResponse
|
|
||||||
*/
|
|
||||||
@Operation(summary = "token 재발급 요청(header & DTO 사용)" , description = "token 재발급 :: accessToken - header, refreshToken - dto")
|
|
||||||
@Parameter(in = ParameterIn.QUERY, name = "refreshToken", description = "refresh token", required = true, example = " ")
|
|
||||||
@PostMapping("/jwt/reissue")
|
|
||||||
public ResponseEntity<? extends IRestResponse> reissueFromHeader(
|
|
||||||
@Parameter(hidden = true)
|
|
||||||
@NotNull
|
|
||||||
@RequestBody
|
|
||||||
final TokenRequestDto tokenRequestDto,
|
|
||||||
HttpServletRequest request) {
|
|
||||||
return RestResponse.of(authService.reissue(tokenRequestDto, request, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* JWT token 재발급
|
|
||||||
* access token - header, refresh - cookie 에 담아 요청
|
|
||||||
*
|
|
||||||
* 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
|
|
||||||
* 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
|
|
||||||
* 3. 2번 항묵에 미 해당시 refresh token 값은 null로
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param request HttpServletRequest
|
|
||||||
* @param response HttpServletResponse
|
|
||||||
* @return ResponseEntity IRestResponse
|
|
||||||
*/
|
|
||||||
@Operation(summary = "token 재발급(header & cookie 사용)" , description = "토큰 재발급 :: accessToken - header, refreshToken - cookie")
|
|
||||||
//@Parameter(in = ParameterIn.HEADER, name = "Authorization", description = "access token", required = true, example = " ")
|
|
||||||
//@Parameter(in = ParameterIn.COOKIE, name = "refreshToken", description = "refresh token", required = true, example = " ")
|
|
||||||
@PostMapping("/jwt/reissue/cookie")
|
|
||||||
public ResponseEntity<? extends IRestResponse> reissueFromCookie(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
return RestResponse.of(authService.reissue(null, request, response));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* JWT token 재발급
|
|
||||||
* access token - TokenRequestDto, refresh - TokenRequestDto 에 담아 요청
|
|
||||||
*
|
|
||||||
* 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
|
|
||||||
* 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
|
|
||||||
* 3. 2번 항묵에 미 해당시 refresh token 값은 null로
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param tokenRequestDto TokenRequestDto
|
|
||||||
* @return ResponseEntity IRestResponse
|
|
||||||
*/
|
|
||||||
@Operation(summary = "token 재발급(DTO 사용)" , description = "token 재발급 :: accessToken - dto, refreshToken - dto")
|
|
||||||
@PostMapping("/jwt/reissue/dto")
|
|
||||||
public ResponseEntity<? extends IRestResponse> reissueFromParam(
|
|
||||||
@NotNull
|
|
||||||
@RequestBody
|
|
||||||
final TokenRequestDto tokenRequestDto) {
|
|
||||||
return RestResponse.of(authService.reissue(tokenRequestDto, null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "토큰 체크" , description = "access token 체크")
|
|
||||||
@PostMapping("/jwt/validate")
|
|
||||||
public ResponseEntity<? extends IRestResponse> validate(@RequestParam final String accessToken, @RequestParam boolean isExceptionThrow) {
|
|
||||||
return RestResponse.of(authService.validationToken(accessToken, isExceptionThrow));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "토큰 정보 확인" , description = "토큰 정보 확인")
|
|
||||||
@PostMapping("/jwt/info")
|
|
||||||
public ResponseEntity<? extends IRestResponse> findAccessTokenInfo(@RequestParam final String accessToken) {
|
|
||||||
return RestResponse.of(authService.findAccessTokenInfo(accessToken));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.repository;
|
|
||||||
|
|
||||||
import com.xit.core.oauth2.api.entity.RefreshToken;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, String> {
|
|
||||||
public Optional<RefreshToken> findByKey(String key);
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.service;
|
|
||||||
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
import com.xit.core.oauth2.api.dto.LoginRequestDto;
|
|
||||||
import com.xit.core.oauth2.api.dto.TokenDto;
|
|
||||||
import com.xit.core.oauth2.api.dto.TokenRequestDto;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface IAuthService {
|
|
||||||
|
|
||||||
//TokenDto login(final CmmUserRequestDto cmmUserRequestDto);
|
|
||||||
TokenDto login(final LoginRequestDto loginRequestDto,
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
HttpSession session);
|
|
||||||
|
|
||||||
TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response);
|
|
||||||
|
|
||||||
boolean validationToken(final String accessToken, final boolean isExceptionThrow);
|
|
||||||
|
|
||||||
Map<String, Object> findAccessTokenInfo(final String accessToken);
|
|
||||||
|
|
||||||
Optional<CmmUser> findMyUserWithoutAuthorities();
|
|
||||||
|
|
||||||
Optional<CmmUser> findUserWithAuthorities(final String userId);
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.service;
|
|
||||||
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
|
|
||||||
public interface IUserService {
|
|
||||||
public CmmUser signup(CmmUser cmmUser);
|
|
||||||
public CmmUser getUser(String userId);
|
|
||||||
|
|
||||||
CmmUser getMemberInfo(String userId);
|
|
||||||
CmmUser getMyInfo();
|
|
||||||
}
|
|
@ -1,311 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.service.impl;
|
|
||||||
|
|
||||||
import com.xit.core.oauth2.api.entity.RefreshToken;
|
|
||||||
import com.xit.core.oauth2.api.repository.RefreshTokenRepository;
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
import com.xit.biz.cmm.repository.ICmmUserRepository;
|
|
||||||
import com.xit.core.constant.ErrorCode;
|
|
||||||
import com.xit.core.constant.XitConstants;
|
|
||||||
import com.xit.core.exception.TokenAuthException;
|
|
||||||
import com.xit.core.oauth2.api.dto.LoginRequestDto;
|
|
||||||
import com.xit.core.oauth2.api.dto.TokenDto;
|
|
||||||
import com.xit.core.oauth2.api.dto.TokenRequestDto;
|
|
||||||
import com.xit.core.oauth2.api.service.IAuthService;
|
|
||||||
import com.xit.core.oauth2.config.properties.AppProperties;
|
|
||||||
import com.xit.core.oauth2.oauth.JwtTokenProvider;
|
|
||||||
import com.xit.core.oauth2.utils.CookieUtil;
|
|
||||||
import com.xit.core.oauth2.utils.HeaderUtil;
|
|
||||||
import com.xit.core.util.Checks;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthService implements IAuthService {
|
|
||||||
@Value("${xit.auth.save.type:header}")
|
|
||||||
private String authSaveType;
|
|
||||||
|
|
||||||
@Value("${jwt.refresh.save.type:HEADER}")
|
|
||||||
private String tokenParamType;
|
|
||||||
|
|
||||||
private static final int EXPIRE_CONVERT_SECOND_FROM_DAY = 60 * 60 * 24;
|
|
||||||
private final static long THREE_DAYS_MILISECONDS = 3 * (1000 * 60 * 60 * 24);
|
|
||||||
|
|
||||||
private final AppProperties appProperties;
|
|
||||||
private final AuthenticationManager authenticationManager;
|
|
||||||
private final ICmmUserRepository cmmUserRepository;
|
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
|
||||||
private final RefreshTokenRepository refreshTokenRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
|
|
||||||
*
|
|
||||||
* 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
|
|
||||||
* 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
|
|
||||||
* AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
|
|
||||||
* authenticate 메소드 하나만 정의되어 있는 인터페이스
|
|
||||||
* Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
|
|
||||||
* 3. 인증정보로 JWT 토큰 생성
|
|
||||||
* 4. Refresh 토큰 저장 : : Redis에 저장
|
|
||||||
* 5. 토큰(Access + Refresh) return
|
|
||||||
*
|
|
||||||
* @see AuthenticationManagerBuilder
|
|
||||||
* @see JwtTokenProvider
|
|
||||||
* </pre>
|
|
||||||
* @param loginRequestDto LoginRequestDto
|
|
||||||
* @return TokenDto
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public TokenDto login(final LoginRequestDto loginRequestDto,
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
HttpSession session) {
|
|
||||||
TokenDto tokenDto = null;
|
|
||||||
Authentication authentication = null;
|
|
||||||
|
|
||||||
// 1. Login ID/PW 를 기반으로 AuthenticationToken 생성
|
|
||||||
UsernamePasswordAuthenticationToken authenticationToken = loginRequestDto.toAuthentication();
|
|
||||||
|
|
||||||
// 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분
|
|
||||||
authentication = authenticationManager.authenticate(authenticationToken);
|
|
||||||
|
|
||||||
// Authentication 저장
|
|
||||||
if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SECURITY.getCode())){
|
|
||||||
// TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
|
|
||||||
}else if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SESSION.getCode())){
|
|
||||||
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
CmmUser cmmUser = cmmUserRepository.findByUserId(loginRequestDto.getUserId()).orElseThrow(() -> new UsernameNotFoundException(loginRequestDto.getUserId() + " -> 사용자를 찾을 수 없습니다."));
|
|
||||||
Map<String, Object> infoMap = new HashMap<>();
|
|
||||||
infoMap.put(XitConstants.JwtToken.TOKEN_USER_NAME.getCode(), cmmUser.getUserName());
|
|
||||||
infoMap.put(XitConstants.JwtToken.TOKEN_USER_MAIL.getCode(), cmmUser.getEmail());
|
|
||||||
|
|
||||||
RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName())
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
// 저장된 refresh token not exists
|
|
||||||
if (Checks.isNull(savedRefreshToken) || Checks.isNull(savedRefreshToken.getValue())) {
|
|
||||||
// 없는 경우 새로 등록
|
|
||||||
tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap);
|
|
||||||
refreshTokenRepository.saveAndFlush(
|
|
||||||
RefreshToken.builder()
|
|
||||||
.key(loginRequestDto.getUserId())
|
|
||||||
.value(tokenDto.getRefreshToken())
|
|
||||||
.build()
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
// 저장된 토큰이 있다
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// 만료되지 않은 refresh token
|
|
||||||
if(!jwtTokenProvider.isExpiredToken(savedRefreshToken.getValue())) {
|
|
||||||
|
|
||||||
// refresh token 잔여기간 기준 도달여부 check
|
|
||||||
Date now = new Date();
|
|
||||||
long validTime = jwtTokenProvider.parseClaims(savedRefreshToken.getValue()).getExpiration().getTime() - now.getTime();
|
|
||||||
|
|
||||||
if (validTime <= THREE_DAYS_MILISECONDS) {
|
|
||||||
// 새로운 토큰 생성 : access token and refresh token
|
|
||||||
tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap);
|
|
||||||
|
|
||||||
// DB에 refresh 토큰 업데이트
|
|
||||||
savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken()));
|
|
||||||
|
|
||||||
}else{
|
|
||||||
tokenDto = TokenDto.builder()
|
|
||||||
.grantType(XitConstants.JwtToken.GRANT_TYPE.getCode())
|
|
||||||
.accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap))
|
|
||||||
.refreshToken(null)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
refreshTokenRepository.saveAndFlush(
|
|
||||||
RefreshToken.builder()
|
|
||||||
.key(loginRequestDto.getUserId())
|
|
||||||
.value(tokenDto.getRefreshToken())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh token 만료
|
|
||||||
}else{
|
|
||||||
tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap);
|
|
||||||
|
|
||||||
refreshTokenRepository.saveAndFlush(
|
|
||||||
RefreshToken.builder()
|
|
||||||
.key(loginRequestDto.getUserId())
|
|
||||||
.value(tokenDto.getRefreshToken())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// COOKIE 타입의 요청인 경우 COOKIE set
|
|
||||||
if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) {
|
|
||||||
CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode());
|
|
||||||
CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode(), tokenDto.getRefreshToken(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 토큰 발급
|
|
||||||
return tokenDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* 토큰 재발급 : Redis에 저장
|
|
||||||
* 1. Access 토큰 검증
|
|
||||||
* 2. Access Token 복호화 --> 유저 정보 (Member ID) GET
|
|
||||||
* 3. Refresh Token 만료 여부 검증
|
|
||||||
* 4. 저장소의 Refresh Token GET
|
|
||||||
* 5. 저장소의 Refresh Token == 전달받은 Refresh Token 의 일치 여부 확인
|
|
||||||
* 6. 토큰 생성
|
|
||||||
* 7. Refresh Token update & 저장소 저장
|
|
||||||
* 8. 토큰 return
|
|
||||||
*
|
|
||||||
* @see JwtTokenProvider
|
|
||||||
* </pre>
|
|
||||||
* @param tokenRequestDto TokenRequestDto
|
|
||||||
* @return TokenDto
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
TokenDto tokenDto = new TokenDto();
|
|
||||||
String sAccessToken = null;
|
|
||||||
String sRefreshToken = null;
|
|
||||||
Authentication authentication = null;
|
|
||||||
|
|
||||||
//-------------------------------------------------------
|
|
||||||
// access token 검증
|
|
||||||
//-------------------------------------------------------
|
|
||||||
// Get access token -------------------------------------
|
|
||||||
if(Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType))
|
|
||||||
sAccessToken = tokenRequestDto.getAccessToken();
|
|
||||||
|
|
||||||
else if(
|
|
||||||
Objects.nonNull(request) &&
|
|
||||||
(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)
|
|
||||||
|| Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType))
|
|
||||||
)
|
|
||||||
sAccessToken = HeaderUtil.getAccessToken(request);
|
|
||||||
else
|
|
||||||
throw new TokenAuthException("Token 전달 형태가 미 정의 되었습니다.");
|
|
||||||
|
|
||||||
// Access token check -------------------------------------
|
|
||||||
// 오염된 토큰 인지만 체크
|
|
||||||
jwtTokenProvider.validateTokenExcludeExpired(sAccessToken, false, true);
|
|
||||||
//if(!jwtTokenProvider.validateToken(sAccessToken, false, false)) throw new TokenAuthException(ErrorCode.INVALID_TOKEN);
|
|
||||||
// expired access token 여부 체크
|
|
||||||
// TODO :: 유효기간 만료된 경우에만 재발급 - 테스트를 위해 반대로 발급되도록 함
|
|
||||||
if(jwtTokenProvider.isExpiredToken(sAccessToken)) throw new TokenAuthException(ErrorCode.NOT_EXPIRED_TOKEN_YET);
|
|
||||||
// --------------------------------------------------------
|
|
||||||
|
|
||||||
authentication = jwtTokenProvider.getAuthentication(sAccessToken);
|
|
||||||
|
|
||||||
//---------------------------------------------------------
|
|
||||||
// refresh token 검증
|
|
||||||
//---------------------------------------------------------
|
|
||||||
// Get refresh token --------------------------------------
|
|
||||||
if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType))
|
|
||||||
sRefreshToken = CookieUtil.getCookie(request, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode())
|
|
||||||
.map(Cookie::getValue)
|
|
||||||
.orElse((null));
|
|
||||||
else if(Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType)
|
|
||||||
|| Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType))
|
|
||||||
sRefreshToken = tokenRequestDto.getRefreshToken();
|
|
||||||
|
|
||||||
// Refresh token check ------------------------------------
|
|
||||||
jwtTokenProvider.validateTokenExcludeExpired(sRefreshToken, true, true);
|
|
||||||
|
|
||||||
// userId refresh token 으로 DB 확인
|
|
||||||
RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName())
|
|
||||||
.orElse(null);
|
|
||||||
if (Checks.isEmpty(savedRefreshToken)) throw new TokenAuthException(ErrorCode.NOT_EXISTS_SAVED_REFRESH_TOKEN);
|
|
||||||
|
|
||||||
if (!Objects.equals(Objects.requireNonNull(savedRefreshToken).getValue(), sRefreshToken)) {
|
|
||||||
throw new TokenAuthException(ErrorCode.MISMATCH_REFRESH_ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 토큰 부가 정보 set
|
|
||||||
CmmUser cmmUser = cmmUserRepository.findByUserId(authentication.getName()).orElse(null);
|
|
||||||
if(Checks.isNull(cmmUser)) throw new TokenAuthException(ErrorCode.INVALID_ROLE_TOKEN);
|
|
||||||
Map<String, Object> infoMap = new HashMap<>();
|
|
||||||
infoMap.put(XitConstants.JwtToken.TOKEN_USER_NAME.getCode(), cmmUser.getUserName());
|
|
||||||
infoMap.put(XitConstants.JwtToken.TOKEN_USER_MAIL.getCode(), cmmUser.getEmail());
|
|
||||||
|
|
||||||
// TODO : refresh토큰 발급 기준인 정의 필요
|
|
||||||
// refresh 토큰 기간이 3일 이하로 남은 경우, refresh 토큰 갱신
|
|
||||||
Date now = new Date();
|
|
||||||
long validTime = jwtTokenProvider.parseClaims(sRefreshToken).getExpiration().getTime() - now.getTime();
|
|
||||||
|
|
||||||
if (validTime <= THREE_DAYS_MILISECONDS) {
|
|
||||||
// 토큰 생성 : access token and refresh token
|
|
||||||
tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap);
|
|
||||||
|
|
||||||
// TODO : DB 갱신 확인 필요
|
|
||||||
// DB refresh 토큰 업데이트
|
|
||||||
RefreshToken newRefreshToken = savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken()));
|
|
||||||
//refreshTokenRepository.saveAndFlush(newRefreshToken);
|
|
||||||
|
|
||||||
if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) {
|
|
||||||
CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode());
|
|
||||||
CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode(), newRefreshToken.getValue(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
}else{
|
|
||||||
tokenDto = TokenDto.builder()
|
|
||||||
.grantType(XitConstants.JwtToken.GRANT_TYPE.getCode())
|
|
||||||
.accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap))
|
|
||||||
.refreshToken(null)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
return tokenDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public boolean validationToken(final String accessToken, final boolean isExceptionThrow) {
|
|
||||||
return jwtTokenProvider.validateTokenExcludeExpired(accessToken, false, isExceptionThrow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO :: 적용 필요
|
|
||||||
@Override
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Map<String, Object> findAccessTokenInfo(String accessToken) {
|
|
||||||
return jwtTokenProvider.getAccessTokenInfo(accessToken); //authTokenProvider.getAccessTokenInfo(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Optional<CmmUser> findMyUserWithoutAuthorities() {
|
|
||||||
//cmmUserRepos
|
|
||||||
return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(SecurityUtil.getCurrentMemberId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public Optional<CmmUser> findUserWithAuthorities(final String userId) {
|
|
||||||
return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(userId);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package com.xit.core.oauth2.api.service.impl;
|
|
||||||
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
import com.xit.biz.cmm.repository.ICmmUserRepository;
|
|
||||||
import com.xit.core.constant.ErrorCode;
|
|
||||||
import com.xit.core.exception.AuthorizationException;
|
|
||||||
import com.xit.core.exception.CustomBaseException;
|
|
||||||
import com.xit.core.exception.UserExistedException;
|
|
||||||
import com.xit.core.oauth2.api.service.IUserService;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.ProviderType;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.RoleType;
|
|
||||||
import com.xit.core.oauth2.utils.SecurityUtil;
|
|
||||||
import com.xit.core.util.CommUtil;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class UserService implements IUserService {
|
|
||||||
@Value("${xit.auth.save.type:header}")
|
|
||||||
private String authSaveType;
|
|
||||||
|
|
||||||
private final ICmmUserRepository cmmUserRepository;
|
|
||||||
private final PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 회원 정보 저장
|
|
||||||
*
|
|
||||||
* @param cmmUser CmmUser
|
|
||||||
* @return CmmUser
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public CmmUser signup(CmmUser cmmUser) {
|
|
||||||
//if (cmmUserRepository.findByUserId(cmmUser.getUserId()).isPresent()) {
|
|
||||||
if (cmmUserRepository.existsByUserId(cmmUser.getUserId())) {
|
|
||||||
throw new UserExistedException(ErrorCode.MEMBER_EXISTS);
|
|
||||||
}
|
|
||||||
cmmUser.setCmmUserId(CommUtil.getStringFromUUID());
|
|
||||||
cmmUser.setCreatedBy(cmmUser.getUserId());
|
|
||||||
//cmmUser.setCreatedDate(LocalDateTime.now());
|
|
||||||
cmmUser.setRoleType(RoleType.USER);
|
|
||||||
|
|
||||||
if (Objects.equals(ProviderType.LOCAL, cmmUser.getProviderType())) {
|
|
||||||
cmmUser.setPassword(passwordEncoder.encode(cmmUser.getPassword()));
|
|
||||||
} else {
|
|
||||||
cmmUser.setPassword("NO_PASSWORD");
|
|
||||||
//cmmUser.setProviderType(cmmUser.getUserId());
|
|
||||||
}
|
|
||||||
return cmmUserRepository.save(cmmUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CmmUser getUser(String userId) {
|
|
||||||
return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public CmmUser getMemberInfo(String userId) {
|
|
||||||
return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 현재 SecurityContext 에 있는 유저 정보 가져오기
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public CmmUser getMyInfo() {
|
|
||||||
String userId = SecurityUtil.getCurrentUserId(authSaveType);
|
|
||||||
return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new AuthorizationException(ErrorCode.USER_NOT_FOUND));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package com.xit.core.oauth2.oauth.service;
|
|
||||||
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
import com.xit.biz.cmm.repository.ICmmUserRepository;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.ProviderType;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.RoleType;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.UserPrincipal;
|
|
||||||
import com.xit.core.oauth2.oauth.exception.OAuthProviderMissMatchException;
|
|
||||||
import com.xit.core.oauth2.oauth.info.AbstractOAuth2UserInfo;
|
|
||||||
import com.xit.core.oauth2.oauth.info.OAuth2UserInfoFactory;
|
|
||||||
import com.xit.core.util.CommUtil;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
|
|
||||||
|
|
||||||
private final ICmmUserRepository userRepository;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
|
||||||
OAuth2User user = super.loadUser(userRequest);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return this.process(userRequest, user);
|
|
||||||
} catch (AuthenticationException ex) {
|
|
||||||
throw ex;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
throw new InternalAuthenticationServiceException(ex.getLocalizedMessage(), ex.getCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) {
|
|
||||||
// TODO :: 적용 여부 파악 필요
|
|
||||||
ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase());
|
|
||||||
|
|
||||||
AbstractOAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes());
|
|
||||||
CmmUser savedUser = null;
|
|
||||||
Optional<CmmUser> optionalCmmUser = userRepository.findByUserId(userInfo.getId());
|
|
||||||
|
|
||||||
if(optionalCmmUser.isPresent()) {
|
|
||||||
savedUser = optionalCmmUser.get();
|
|
||||||
if (Objects.equals(providerType, savedUser.getProviderType())) {
|
|
||||||
throw new OAuthProviderMissMatchException(
|
|
||||||
"Looks like you're signed up with " + providerType +
|
|
||||||
" account. Please use your " + savedUser.getProviderType() + " account to login."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
updateUser(savedUser, userInfo);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
savedUser = createUser(userInfo, providerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (optionalCmmUser.isEmpty()) {
|
|
||||||
// if (providerType != savedUser.getProviderType()) {
|
|
||||||
// throw new OAuthProviderMissMatchException(
|
|
||||||
// "Looks like you're signed up with " + providerType +
|
|
||||||
// " account. Please use your " + savedUser.getProviderType() + " account to login."
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// updateUser(savedUser, userInfo);
|
|
||||||
// } else {
|
|
||||||
// savedUser = createUser(userInfo, providerType);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return UserPrincipal.create(savedUser, user.getAttributes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private CmmUser createUser(AbstractOAuth2UserInfo userInfo, ProviderType providerType) {
|
|
||||||
CmmUser user = new CmmUser(
|
|
||||||
userInfo.getId(),
|
|
||||||
userInfo.getName(),
|
|
||||||
userInfo.getEmail(),
|
|
||||||
"Y",
|
|
||||||
userInfo.getImageUrl(),
|
|
||||||
providerType,
|
|
||||||
RoleType.USER
|
|
||||||
);
|
|
||||||
LocalDateTime localDateTime = LocalDateTime.now();
|
|
||||||
user.setCmmUserId(CommUtil.getStringFromUUID());
|
|
||||||
user.setCreatedBy(userInfo.getId());
|
|
||||||
user.setModifiedBy(userInfo.getId());
|
|
||||||
user.setCreatedDtm(localDateTime);
|
|
||||||
user.setModifiedDtm(localDateTime);
|
|
||||||
|
|
||||||
return userRepository.saveAndFlush(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUser(CmmUser user, AbstractOAuth2UserInfo userInfo) {
|
|
||||||
if (Objects.nonNull(userInfo.getName()) && !Objects.equals(userInfo.getName(), user.getUserName())) {
|
|
||||||
user.setUserName(userInfo.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Objects.nonNull(userInfo.getImageUrl()) && !Objects.equals(userInfo.getImageUrl(), user.getProfileImageUrl())) {
|
|
||||||
user.setProfileImageUrl(userInfo.getImageUrl());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package com.xit.core.oauth2.oauth.service;
|
|
||||||
|
|
||||||
import com.xit.biz.cmm.entity.CmmUser;
|
|
||||||
import com.xit.biz.cmm.repository.ICmmUserRepository;
|
|
||||||
import com.xit.core.constant.ErrorCode;
|
|
||||||
import com.xit.core.exception.CustomBaseException;
|
|
||||||
import com.xit.core.oauth2.oauth.entity.UserPrincipal;
|
|
||||||
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;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
|
||||||
|
|
||||||
private final ICmmUserRepository cmmUserRepository;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
|
||||||
CmmUser user = cmmUserRepository.findByUserId(username).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND));
|
|
||||||
return UserPrincipal.create(user);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue