diff --git a/build.gradle b/build.gradle index 39a075d..0832030 100644 --- a/build.gradle +++ b/build.gradle @@ -36,8 +36,9 @@ dependencies { runtimeOnly 'com.h2database:h2' /* Security */ - testImplementation 'org.springframework.security:spring-security-test' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + testImplementation 'org.springframework.security:spring-security-test' /* Lombok */ compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java b/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java new file mode 100644 index 0000000..ea4510a --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java @@ -0,0 +1,10 @@ +package gdsc.binaryho.imhere.constant; + +public class UrlConstant { + + public static final String PROD_CLIENT_URL = "https://imhere.im"; + + public static final String LOCAL_CLIENT_URL = "http://localhost:3000"; + + private UrlConstant() {} +} diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java b/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java new file mode 100644 index 0000000..b10d2fa --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java @@ -0,0 +1,28 @@ +package gdsc.binaryho.imhere.core.member; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class GitHubResource { + + @Column(unique = true, name = "git_hub_id", nullable = false) + private String id; + + @Column(name = "git_hub_handle", nullable = false) + private String handle; + + @Column(name = "git_hub_profile") + private String profile; + + protected void updateHandle(String gitHubHandle) { + this.handle = gitHubHandle; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java index f2e4809..455564e 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/Member.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/Member.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import java.time.LocalDateTime; import javax.persistence.Column; +import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -27,8 +28,13 @@ public class Member { @Column(name = "member_id") private Long id; + // TODO : nullable false 조건 제거 @Column(unique = true, nullable = false) private String univId; + + @Embedded + private GitHubResource gitHubResource; + @Column(nullable = false) private String name; private String password; @@ -41,10 +47,14 @@ public class Member { @CreatedDate private LocalDateTime createdAt; - public String getRoleKey() { - return role.getKey(); + public static Member createGuestMember(String gitHubId, String gitHubHandle, String gitHubProfile) { + Member member = new Member(); + member.gitHubResource = new GitHubResource(gitHubId, gitHubHandle, gitHubProfile); + member.setRole(Role.GUEST); + return member; } + // TODO : OAUth2 도입 이후 사용하지 않을 기능 public static Member createMember(String univId, String name, String password, Role role) { Member member = new Member(); member.setUnivId(univId); @@ -53,4 +63,12 @@ public static Member createMember(String univId, String name, String password, R member.setRole(role); return member; } + + public void updateGitHubHandle(String gitHubHandle) { + this.gitHubResource.updateHandle(gitHubHandle); + } + + public String getRoleKey() { + return role.getKey(); + } } diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/Role.java b/src/main/java/gdsc/binaryho/imhere/core/member/Role.java index 80fa052..b9521fe 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/Role.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/Role.java @@ -1,7 +1,12 @@ package gdsc.binaryho.imhere.core.member; public enum Role { - ADMIN("ROLE_ADMIN"), LECTURER("ROLE_LECTURER"), STUDENT("ROLE_STUDENT"); + GUEST("ROLE_GUEST"), + ADMIN("ROLE_ADMIN"), + LECTURER("ROLE_LECTURER"), + STUDENT("ROLE_STUDENT"), + + ; private final String key; diff --git a/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java b/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java index 3e80b1a..974e4a6 100644 --- a/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java +++ b/src/main/java/gdsc/binaryho/imhere/core/member/infrastructure/MemberRepository.java @@ -8,4 +8,5 @@ public interface MemberRepository extends JpaRepository { Optional findById(Long id); Optional findByUnivId(String univId); + Optional findByGitHubResource_Id(String gitHubId); } diff --git a/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java b/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java new file mode 100644 index 0000000..965f94c --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/SignUpProcessRedirectionPath.java @@ -0,0 +1,26 @@ +package gdsc.binaryho.imhere.security; + +import gdsc.binaryho.imhere.core.member.Member; +import lombok.Getter; + +@Getter +public enum SignUpProcessRedirectionPath { + + BEFORE_MEMBER_INFO_INPUT("/signup"), + SIGN_UP_DONE("/main"), + ; + + private final String redirectUrlPath; + + public static SignUpProcessRedirectionPath of(Member member) { + if (member.getName() == null) { + return BEFORE_MEMBER_INFO_INPUT; + } + + return SIGN_UP_DONE; + } + + SignUpProcessRedirectionPath(String redirectUrlPath) { + this.redirectUrlPath = redirectUrlPath; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java index 6b12005..070b2ee 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java +++ b/src/main/java/gdsc/binaryho/imhere/security/config/SecurityConfig.java @@ -2,14 +2,17 @@ import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; -import gdsc.binaryho.imhere.security.filter.JwtAuthenticationFilter; import gdsc.binaryho.imhere.security.filter.JwtAuthorizationFilter; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2SuccessHandler; +import gdsc.binaryho.imhere.security.oauth.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -22,7 +25,7 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.channel.ChannelProcessingFilter; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.filter.CorsFilter; @@ -35,8 +38,11 @@ public class SecurityConfig { private final MemberRepository memberRepository; private final CorsFilter corsFilter; + private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; + private final CustomOAuth2UserService customOAuth2UserService; - private final TokenService tokenService; + private final TokenUtil tokenUtil; + private final TokenPropertyHolder tokenPropertyHolder; @Value("${actuator.username}") private String ACTUATOR_USERNAME; @@ -89,9 +95,16 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .formLogin().disable() .httpBasic().disable() - .authorizeRequests() + .oauth2Login(configurer -> { + configurer.userInfoEndpoint( + endpoint -> endpoint.userService(customOAuth2UserService)); + configurer.successHandler(customOAuth2SuccessHandler); + configurer.failureHandler(setStatusUnauthorized()); + } + ) - .antMatchers("/login", "/logout", "/member/**", + .authorizeRequests() + .antMatchers("/login/**", "/logout", "/member/**", "/swagger*/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**") .permitAll() @@ -103,11 +116,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().authenticated(); - http.addFilterBefore(new JwtAuthenticationFilter( - authenticationManager(authenticationConfiguration), tokenService), UsernamePasswordAuthenticationFilter.class); - http.addFilterBefore(new JwtAuthorizationFilter( - authenticationManager(authenticationConfiguration), tokenService, memberRepository), BasicAuthenticationFilter.class); + authenticationManager(authenticationConfiguration), + tokenUtil, memberRepository, tokenPropertyHolder), + BasicAuthenticationFilter.class); return http.build(); } @@ -121,4 +133,9 @@ private UserDetailsService getActuatorUserDetailsService() { return new InMemoryUserDetailsManager(userDetails); } + + private AuthenticationFailureHandler setStatusUnauthorized() { + int unauthorized = HttpStatus.UNAUTHORIZED.value(); + return (request, response, exception) -> response.setStatus(unauthorized); + } } diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java index f313046..5fc9922 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthenticationFilter.java @@ -2,7 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.security.jwt.Token; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -20,14 +21,15 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +// TODO : 사용하지 않을 예정인 클래스 @RequiredArgsConstructor public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; - private static final String ACCESS_TOKEN_PREFIX = "Token "; private final AuthenticationManager authenticationManager; - private final TokenService tokenService; + private final TokenUtil tokenUtil; + private final TokenPropertyHolder tokenPropertyHolder; @Override public Authentication attemptAuthentication( @@ -59,11 +61,17 @@ private SignInRequest getSignInRequest(ServletInputStream inputStream) throws IO public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { - String grantedAuthority = authResult.getAuthorities().stream().findAny().orElseThrow().toString(); - Token jwtToken = tokenService.createToken(authResult.getPrincipal().toString(), grantedAuthority); + String grantedAuthority = authResult.getAuthorities() + .stream() + .findAny() + .orElseThrow() + .toString(); + Token jwtToken = tokenUtil.createToken(authResult.getPrincipal().toString(), grantedAuthority); + + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); - response.addHeader(HEADER_STRING, ACCESS_TOKEN_PREFIX + jwtToken.getAccessToken()); + response.addHeader(HEADER_STRING, accessTokenPrefix + jwtToken.getAccessToken()); } @Override diff --git a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java index c04323e..291c3a6 100644 --- a/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java +++ b/src/main/java/gdsc/binaryho/imhere/security/filter/JwtAuthorizationFilter.java @@ -1,10 +1,13 @@ package gdsc.binaryho.imhere.security.filter; +import gdsc.binaryho.imhere.core.auth.exception.MemberNotFoundException; import gdsc.binaryho.imhere.core.member.Member; import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; -import gdsc.binaryho.imhere.security.jwt.TokenService; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; import gdsc.binaryho.imhere.security.principal.PrincipalDetails; import java.io.IOException; +import java.util.Objects; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -18,51 +21,56 @@ public class JwtAuthorizationFilter extends BasicAuthenticationFilter { - private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; - private static final String ACCESS_TOKEN_PREFIX = "Token "; + private static final String TOKEN_HEADER_STRING = HttpHeaders.AUTHORIZATION; - private final TokenService tokenService; + private final TokenUtil tokenUtil; private final MemberRepository memberRepository; + private final TokenPropertyHolder tokenPropertyHolder; public JwtAuthorizationFilter( AuthenticationManager authenticationManager, - TokenService tokenService, MemberRepository memberRepository) { + TokenUtil tokenUtil, MemberRepository memberRepository, + TokenPropertyHolder tokenPropertyHolder) { super(authenticationManager); - this.tokenService = tokenService; + this.tokenUtil = tokenUtil; this.memberRepository = memberRepository; + this.tokenPropertyHolder = tokenPropertyHolder; } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws ServletException, IOException { - if (isNullToken(request)) { + String jwtToken = request.getHeader(TOKEN_HEADER_STRING); + if (isTokenNullOrInvalidate(jwtToken)) { chain.doFilter(request, response); return; } - String jwtToken = request.getHeader(HEADER_STRING) - .replace(ACCESS_TOKEN_PREFIX, ""); - - if (tokenService.validateTokenExpirationTimeNotExpired(jwtToken)) { - - String univId = tokenService.getUnivId(jwtToken); - Member member = memberRepository.findByUnivId(univId).orElseThrow(); - PrincipalDetails principalDetails = new PrincipalDetails(member); - - Authentication authentication = - new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); - - SecurityContextHolder.getContext().setAuthentication(authentication); + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); + String tokenValue = jwtToken.replace(accessTokenPrefix, ""); + if (tokenUtil.validateTokenExpirationTimeNotExpired(tokenValue)) { + setAuthentication(tokenValue); } - chain.doFilter(request, response); } - private boolean isNullToken(HttpServletRequest request) { - String jwtHeader = request.getHeader(HEADER_STRING); - if (jwtHeader == null || !jwtHeader.startsWith(ACCESS_TOKEN_PREFIX)) { - return true; - } - return false; + private boolean isTokenNullOrInvalidate(String token) { + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); + return Objects.isNull(token) + || (!token.startsWith(accessTokenPrefix)); + } + + private void setAuthentication(String jwtToken) { + Long id = tokenUtil.getId(jwtToken); + Member member = memberRepository.findById(id) + .orElseThrow(() -> MemberNotFoundException.EXCEPTION); + + PrincipalDetails principalDetails = new PrincipalDetails(member); + Authentication authentication = + new UsernamePasswordAuthenticationToken(principalDetails, "", + principalDetails.getAuthorities()); + + SecurityContextHolder.getContext().setAuthentication(authentication); } } diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java deleted file mode 100644 index 4c2f9fc..0000000 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereSecretHolder.java +++ /dev/null @@ -1,16 +0,0 @@ -package gdsc.binaryho.imhere.security.jwt; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class ImhereSecretHolder implements SecretHolder { - - @Value("${jwt.access-token-prefix}") - private String SECRET; - - @Override - public String getSecret() { - return SECRET; - } -} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java new file mode 100644 index 0000000..5510240 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/ImhereTokenPropertyHolder.java @@ -0,0 +1,33 @@ +package gdsc.binaryho.imhere.security.jwt; + +import java.time.Duration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ImhereTokenPropertyHolder implements TokenPropertyHolder { + + @Value("${token.secret}") + private String secret; + + @Value("${token.access-token-expire-minute}") + private Integer accessTokenExpireMinute; + + @Value("${token.access-token-prefix}") + private String accessTokenPrefix; + + @Override + public String getSecret() { + return secret; + } + + @Override + public Duration getAccessTokenExpiration() { + return Duration.ofMinutes(accessTokenExpireMinute); + } + + @Override + public String getAccessTokenPrefix() { + return accessTokenPrefix; + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java deleted file mode 100644 index 085f0f3..0000000 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/SecretHolder.java +++ /dev/null @@ -1,6 +0,0 @@ -package gdsc.binaryho.imhere.security.jwt; - -public interface SecretHolder { - - String getSecret(); -} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java new file mode 100644 index 0000000..d8dca34 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenPropertyHolder.java @@ -0,0 +1,12 @@ +package gdsc.binaryho.imhere.security.jwt; + +import java.time.Duration; + +public interface TokenPropertyHolder { + + String getSecret(); + + Duration getAccessTokenExpiration(); + + String getAccessTokenPrefix(); +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java deleted file mode 100644 index dc6daa2..0000000 --- a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenService.java +++ /dev/null @@ -1,66 +0,0 @@ -package gdsc.binaryho.imhere.security.jwt; - -import gdsc.binaryho.imhere.util.SeoulDateTimeHolder; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import java.time.Duration; -import java.util.Date; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; - -@Log4j2 -@Service -@RequiredArgsConstructor -public class TokenService { - - private final SecretHolder secretHolder; - private final SeoulDateTimeHolder seoulDateTimeHolder; - - private static final Duration ACCESS_TOKEN_EXPIRATION_TIME = Duration.ofMinutes(30); - - public Token createToken(String univId, String roleKey) { - Claims claims = Jwts.claims().setSubject(univId); - claims.put("role", roleKey); - - long seoulTimeNow = seoulDateTimeHolder.getSeoulMilliseconds(); - - String jwt = Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date(seoulTimeNow)) - .setExpiration(new Date(seoulTimeNow + ACCESS_TOKEN_EXPIRATION_TIME.toMillis())) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) - .compact(); - - return new Token(jwt); - } - - public boolean validateTokenExpirationTimeNotExpired(String token) { - if (token == null || token.isEmpty()) { - return false; - } - - try { - Jwts.parser() - .setSigningKey(secretHolder.getSecret()) - .parseClaimsJws(token); - return true; - } catch (ExpiredJwtException exception) { - return false; - } catch (JwtException | IllegalArgumentException exception) { - log.info("[토큰 에러] {}", exception::getMessage); - return false; - } - } - - public String getUnivId(String token) { - return Jwts.parser() - .setSigningKey(secretHolder.getSecret()) - .parseClaimsJws(token) - .getBody() - .getSubject(); - } -} diff --git a/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java new file mode 100644 index 0000000..63a5b85 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/jwt/TokenUtil.java @@ -0,0 +1,95 @@ +package gdsc.binaryho.imhere.security.jwt; + +import gdsc.binaryho.imhere.core.member.Role; +import gdsc.binaryho.imhere.util.SeoulDateTimeHolder; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.time.Duration; +import java.util.Date; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Component; + +@Log4j2 +@Component +@RequiredArgsConstructor +public class TokenUtil { + + private final SeoulDateTimeHolder seoulDateTimeHolder; + private final TokenPropertyHolder tokenPropertyHolder; + + private static final String ROLE_KEY = "role"; + + // TODO : 기존 토큰 생성 방식은 삭제할 예정 + public Token createToken(String univId, String roleKey) { + Claims claims = Jwts.claims().setSubject(univId); + return createToken(claims, roleKey); + } + + public Token createToken(Long memberId, Role role) { + Claims claims = Jwts.claims().setSubject(memberId.toString()); + return createToken(claims, role.getKey()); + } + + public boolean validateTokenExpirationTimeNotExpired(String token) { + if (isNullOrEmpty(token)) { + return false; + } + + try { + parseToValidateToken(token); + return true; + } catch (ExpiredJwtException exception) { + return false; + } catch (JwtException | IllegalArgumentException exception) { + log.info("[토큰 에러] {}", exception::getMessage); + return false; + } + } + + public Long getId(String token) { + String tokenSecret = tokenPropertyHolder.getSecret(); + String subject = Jwts.parser() + .setSigningKey(tokenSecret) + .parseClaimsJws(token) + .getBody() + .getSubject(); + + // TODO : Exception Check + return Long.parseLong(subject); + } + + private Token createToken(Claims claims, String roleKey) { + claims.put(ROLE_KEY, roleKey); + + String jwtToken = buildJwtToken(claims); + return new Token(jwtToken); + } + + private Boolean isNullOrEmpty(String token) { + return token == null || token.isEmpty(); + } + + private String buildJwtToken(Claims claims) { + long seoulTimeNow = seoulDateTimeHolder.getSeoulMilliseconds(); + Duration accessTokenExpiration = tokenPropertyHolder.getAccessTokenExpiration(); + String tokenSecret = tokenPropertyHolder.getSecret(); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date(seoulTimeNow)) + .setExpiration(new Date(seoulTimeNow + accessTokenExpiration.toMillis())) + .signWith(SignatureAlgorithm.HS256, tokenSecret) + .compact(); + } + + private void parseToValidateToken(String token) { + String tokenSecret = tokenPropertyHolder.getSecret(); + Jwts.parser() + .setSigningKey(tokenSecret) + .parseClaimsJws(token); + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java new file mode 100644 index 0000000..67fd348 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2SuccessHandler.java @@ -0,0 +1,49 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; +import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; +import gdsc.binaryho.imhere.util.ClientUrlUtil; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private static final String HEADER_STRING = HttpHeaders.AUTHORIZATION; + + private final TokenUtil tokenUtil; + private final ClientUrlUtil clientUrlUtil; + private final TokenPropertyHolder tokenPropertyHolder; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); + + setRedirectUrl(request, response, customOAuth2User.getSignUpProcessRedirectionPath()); + setAccessToken(response, customOAuth2User); + } + + private void setRedirectUrl(HttpServletRequest request, HttpServletResponse response, + SignUpProcessRedirectionPath signupProcessRedirectionPath) throws IOException { + String redirectUrl = clientUrlUtil.getClientUrl() + signupProcessRedirectionPath.getRedirectUrlPath(); + this.getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } + + private void setAccessToken(HttpServletResponse response, CustomOAuth2User oAuthUser) { + response.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION); + + String accessTokenPrefix = tokenPropertyHolder.getAccessTokenPrefix(); + Token jwtToken = tokenUtil.createToken(oAuthUser.getMemberId(), oAuthUser.getRole()); + response.addHeader(HEADER_STRING, accessTokenPrefix + jwtToken.getAccessToken()); + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java new file mode 100644 index 0000000..b79b9d3 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2User.java @@ -0,0 +1,22 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.core.member.Member; +import gdsc.binaryho.imhere.core.member.Role; +import gdsc.binaryho.imhere.security.SignUpProcessRedirectionPath; +import lombok.Getter; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +@Getter +public class CustomOAuth2User extends DefaultOAuth2User { + + private final Long memberId; + private final Role role; + private final SignUpProcessRedirectionPath signUpProcessRedirectionPath; + + public CustomOAuth2User(GitHubUser gitHubUser, Member member) { + super(gitHubUser.getAuthorities(), gitHubUser.getAttributes(), GitHubUser.GITHUB_NAME_ATTRIBUTE_KEY); + this.memberId = member.getId(); + this.role = member.getRole(); + this.signUpProcessRedirectionPath = SignUpProcessRedirectionPath.of(member); + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java new file mode 100644 index 0000000..b60a5b2 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/CustomOAuth2UserService.java @@ -0,0 +1,44 @@ +package gdsc.binaryho.imhere.security.oauth; + +import gdsc.binaryho.imhere.core.member.Member; +import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; +import lombok.RequiredArgsConstructor; +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 org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final MemberRepository memberRepository; + + @Override + @Transactional + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + GitHubUser gitHubUser = new GitHubUser(super.loadUser(userRequest)); + + Member member = memberRepository.findByGitHubResource_Id(gitHubUser.getId()) + .orElseGet(() -> { + Member guest = createGuestMember(gitHubUser); + return memberRepository.save(guest); + }); + + updateGitHubHandleIfChanged(member, gitHubUser.getHandle()); + return new CustomOAuth2User(gitHubUser, member); + } + + private static Member createGuestMember(GitHubUser gitHubUser) { + return Member.createGuestMember( + gitHubUser.getId(), gitHubUser.getHandle(), gitHubUser.getAvatarUrl()); + } + + private void updateGitHubHandleIfChanged(Member member, String gitHubHandle) { + if (!member.getGitHubResource().getHandle().equals(gitHubHandle)) { + member.updateGitHubHandle(gitHubHandle); + } + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java b/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java new file mode 100644 index 0000000..43f3297 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/security/oauth/GitHubUser.java @@ -0,0 +1,39 @@ +package gdsc.binaryho.imhere.security.oauth; + +import java.util.Collection; +import java.util.Map; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public class GitHubUser { + + public static final String GITHUB_NAME_ATTRIBUTE_KEY = "id"; + private static final String GIT_HUB_HANDLE_ATTRIBUTE_NAME = "login"; + private static final String GIT_HUB_AVATAR_URL_ATTRIBUTE_NAME = "avatar_url"; + + private final OAuth2User oAuth2User; + + public GitHubUser(OAuth2User oAuth2User) { + this.oAuth2User = oAuth2User; + } + + public String getId() { + return oAuth2User.getName(); + } + + public String getHandle() { + return oAuth2User.getAttribute(GIT_HUB_HANDLE_ATTRIBUTE_NAME); + } + + public String getAvatarUrl() { + return oAuth2User.getAttribute(GIT_HUB_AVATAR_URL_ATTRIBUTE_NAME); + } + + public Collection getAuthorities() { + return oAuth2User.getAuthorities(); + } + + public Map getAttributes() { + return oAuth2User.getAttributes(); + } +} diff --git a/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java b/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java new file mode 100644 index 0000000..a1f8964 --- /dev/null +++ b/src/main/java/gdsc/binaryho/imhere/util/ClientUrlUtil.java @@ -0,0 +1,29 @@ +package gdsc.binaryho.imhere.util; + +import static gdsc.binaryho.imhere.constant.UrlConstant.LOCAL_CLIENT_URL; +import static gdsc.binaryho.imhere.constant.UrlConstant.PROD_CLIENT_URL; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ClientUrlUtil { + + private final static String PROD = "prod"; + private final Environment environment; + + public String getClientUrl() { + if (isProd()) { + return PROD_CLIENT_URL; + } + return LOCAL_CLIENT_URL; + } + + private boolean isProd() { + return List.of(environment.getActiveProfiles()) + .contains(PROD); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index f453f60..89e7146 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -27,6 +27,14 @@ spring: hibernate: format_sql: true + security: + oauth2: + client: + registration: + github: + client-id: ENC(cwYhhVQPF7EA6AsPSNX7BkV4o1Q+kWM1SqFh6SfDoxQ=) + client-secret: ENC(sI6HRt2XDATOJWZvrUqzqrdtO++dR9ttm69l3hwsJX8=) + logging.level: org.hibernate.SQL: debug diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index e32ade5..dd3b80a 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -23,6 +23,14 @@ spring: ddl-auto: none dialect: -- org.hibernate.dialect.PostgreSQLDialect + security: + oauth2: + client: + registration: + github: + client-id: ENC(18FNZ6cjSHBH889DgERn3ZscMgAY8R02b6DBg9l47Xs=) + client-secret: ENC(hUvBA0g45hgzXQ2Ia/ovO8dQWDR67BsVt6Um0h1cSh/qrwKaWdVND06XApXIJMSaZy5Kem6nJx4=) + admin: univ-id: END(MnnJJNaW4by9rR1oo90AfA==) name: END(74r3AHMoJBHegbbayNVTRbM3GACH/df5) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b404cda..c1c7261 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,11 +7,20 @@ spring: jpa: open-in-view: false -jwt: + security: + oauth2: + client: + registration: + github: + client-id: ENC(18FNZ6cjSHBH889DgERn3ZscMgAY8R02b6DBg9l47Xs=) + client-secret: ENC(hUvBA0g45hgzXQ2Ia/ovO8dQWDR67BsVt6Um0h1cSh/qrwKaWdVND06XApXIJMSaZy5Kem6nJx4=) + +token: secret: ENC(Wi5BD1R9VNidIJj2xI10Ww==) prefix: "Bearer " header-string: "Authorization" access-token-prefix: "Token " + access-token-expire-minute: 30 # SMTP mail: diff --git a/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java b/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java index 41d3634..0248db2 100644 --- a/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java +++ b/src/test/java/gdsc/binaryho/imhere/mock/FixedSeoulTimeHolder.java @@ -7,8 +7,6 @@ public class FixedSeoulTimeHolder implements SeoulDateTimeHolder { public static final LocalDateTime FIXED_LOCAL_DATE_TIME = LocalDateTime.now(); -// LocalDateTime -// .of(2023, Month.MAY, 6, 11, 0, 0); public static final Long FIXED_MILLISECONDS = FIXED_LOCAL_DATE_TIME .toInstant(ZoneOffset.UTC).toEpochMilli(); diff --git a/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java b/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java deleted file mode 100644 index ab8795f..0000000 --- a/src/test/java/gdsc/binaryho/imhere/mock/TestSecretHolder.java +++ /dev/null @@ -1,17 +0,0 @@ -package gdsc.binaryho.imhere.mock; - -import gdsc.binaryho.imhere.security.jwt.SecretHolder; - -public class TestSecretHolder implements SecretHolder { - - private final String secret; - - public TestSecretHolder(String secret) { - this.secret = secret; - } - - @Override - public String getSecret() { - return secret; - } -} diff --git a/src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java b/src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java new file mode 100644 index 0000000..d01e1fe --- /dev/null +++ b/src/test/java/gdsc/binaryho/imhere/security/FakeTokenPropertyHolder.java @@ -0,0 +1,33 @@ +package gdsc.binaryho.imhere.security; + +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import java.time.Duration; + +public class FakeTokenPropertyHolder implements TokenPropertyHolder { + + private final String secret; + private final Duration accessTokenExpiration; + private final String accessTokenPrefix; + + + public FakeTokenPropertyHolder(String secret, Duration accessTokenExpiration, String accessTokenPrefix) { + this.secret = secret; + this.accessTokenExpiration = accessTokenExpiration; + this.accessTokenPrefix = accessTokenPrefix; + } + + @Override + public String getSecret() { + return secret; + } + + @Override + public Duration getAccessTokenExpiration() { + return accessTokenExpiration; + } + + @Override + public String getAccessTokenPrefix() { + return accessTokenPrefix; + } +} diff --git a/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java new file mode 100644 index 0000000..eebb047 --- /dev/null +++ b/src/test/java/gdsc/binaryho/imhere/security/SecurityConfigTest.java @@ -0,0 +1,88 @@ +package gdsc.binaryho.imhere.security; + +import static gdsc.binaryho.imhere.mock.fixture.MemberFixture.MOCK_LECTURER; +import static gdsc.binaryho.imhere.mock.fixture.MemberFixture.MOCK_STUDENT; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import gdsc.binaryho.imhere.core.lecture.infrastructure.LectureRepository; +import gdsc.binaryho.imhere.core.member.Role; +import gdsc.binaryho.imhere.core.member.infrastructure.MemberRepository; +import gdsc.binaryho.imhere.security.jwt.Token; +import gdsc.binaryho.imhere.security.jwt.TokenPropertyHolder; +import gdsc.binaryho.imhere.security.jwt.TokenUtil; +import java.util.Collections; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest +@AutoConfigureMockMvc +public class SecurityConfigTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private TokenUtil tokenUtil; + + @Autowired + private TokenPropertyHolder tokenPropertyHolder; + + @MockBean + private MemberRepository memberRepository; + + @MockBean + private LectureRepository lectureRepository; + + @Test + public void 인증이_필요한_경로에_접근하면_깃허브_로그인_페이지로_Redirection_된다() throws Exception { + mockMvc.perform(post("/") + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().is3xxRedirection()) + .andExpect(header().string("Location", containsString("/oauth2/authorization/github"))); + } + + @Test + public void 토큰을_통해_인가_할_수_있다() throws Exception { + given(memberRepository.findById(any())) + .willReturn(Optional.of(MOCK_LECTURER)); + given(lectureRepository.findAllByLectureStateNot(any())) + .willReturn(Collections.emptyList()); + + String prefix = tokenPropertyHolder.getAccessTokenPrefix(); + Token token = tokenUtil.createToken(MOCK_LECTURER.getId(), Role.LECTURER); + + mockMvc.perform(get("/api/lecture") + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.AUTHORIZATION) + .header(HttpHeaders.AUTHORIZATION, prefix + token.getAccessToken()) + ) + .andExpect(status().is2xxSuccessful()); + } + + @Test + public void 권한이_없는_토큰_요청은_403_응답을_반환한다() throws Exception { + given(memberRepository.findById(any())) + .willReturn(Optional.of(MOCK_STUDENT)); + Token token = tokenUtil.createToken(1L, Role.STUDENT); + + mockMvc.perform(post("/api/admin/role/1") + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token.getAccessToken()) + ) + .andExpect(status().isForbidden()); + } +} diff --git a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java similarity index 68% rename from src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java rename to src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java index 374b2dd..42e7964 100644 --- a/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenServiceTest.java +++ b/src/test/java/gdsc/binaryho/imhere/security/jwt/TokenUtilTest.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gdsc.binaryho.imhere.core.member.Role; import gdsc.binaryho.imhere.mock.FixedSeoulTimeHolder; -import gdsc.binaryho.imhere.mock.TestSecretHolder; +import gdsc.binaryho.imhere.security.FakeTokenPropertyHolder; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -20,24 +20,19 @@ import java.util.UUID; import org.junit.jupiter.api.Test; -public class TokenServiceTest { +public class TokenUtilTest { private static final Role ROLE = MOCK_STUDENT.getRole(); private static final String SECRET = "TEST_SECRET"; private static final long ACCESS_TOKEN_EXPIRATION_TIME = 1000L * 60L * 20L; private static final long TIME_NOW = FixedSeoulTimeHolder.FIXED_MILLISECONDS; -// public static long getTimeNowByMillis() { -// ZonedDateTime seoulTimeNow = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); -// return seoulTimeNow.toInstant().toEpochMilli(); -// } - - SecretHolder secretHolder = new TestSecretHolder(SECRET); - TokenService tokenService = new TokenService(secretHolder, new FixedSeoulTimeHolder()); + TokenPropertyHolder tokenPropertyHolder = new FakeTokenPropertyHolder(SECRET, Duration.ofDays(999L), "prefix"); + TokenUtil tokenUtil = new TokenUtil(new FixedSeoulTimeHolder(), tokenPropertyHolder); @Test void 이메일과_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { - Token token = tokenService.createToken(UNIV_ID, ROLE.getKey()); + Token token = tokenUtil.createToken(UNIV_ID, ROLE.getKey()); String accessToken = token.getAccessToken(); String[] splitToken = accessToken.split("\\."); @@ -46,12 +41,6 @@ public class TokenServiceTest { JsonNode payloadJson = new ObjectMapper().readTree(payload); String subject = payloadJson.get("sub").asText(); String role = payloadJson.get("role").asText(); -// Claims claims = Jwts.parser() -// .setSigningKey(secretHolder.getSecret()) -// .set -// .setAllowedClockSkewSeconds(Long.MAX_VALUE) -// .parseClaimsJws(accessToken) -// .getBody(); assertAll( () -> assertThat(subject).isEqualTo(UNIV_ID), @@ -59,6 +48,25 @@ public class TokenServiceTest { ); } + @Test + void 맴버_아이디와_권한을_넣어_토큰을_만들_수_있다() throws JsonProcessingException { + Long id = MOCK_STUDENT.getId(); + Token token = tokenUtil.createToken(id, ROLE); + String accessToken = token.getAccessToken(); + + String[] splitToken = accessToken.split("\\."); + String payload = new String(Base64.getDecoder().decode(splitToken[1])); + System.out.println(payload); + JsonNode payloadJson = new ObjectMapper().readTree(payload); + Long subject = payloadJson.get("sub").asLong(); + String role = payloadJson.get("role").asText(); + + assertAll( + () -> assertThat(subject).isEqualTo(id), + () -> assertThat(role).isEqualTo(ROLE.getKey()) + ); + } + @Test void 토큰의_유효_시간의_만료_여부를_확인할_수_있다() { // given @@ -69,14 +77,14 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW + ACCESS_TOKEN_EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isNotNull(); } @@ -90,14 +98,14 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW + ACCESS_TOKEN_EXPIRATION_TIME)) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isTrue(); } @@ -111,14 +119,14 @@ public class TokenServiceTest { .setIssuedAt(new Date(TIME_NOW)) .setExpiration( new Date(TIME_NOW - Duration.ofDays(7777L).toMillis())) - .signWith(SignatureAlgorithm.HS256, secretHolder.getSecret()) + .signWith(SignatureAlgorithm.HS256, tokenPropertyHolder.getSecret()) .compact(); Token token = new Token(jwt); // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -130,7 +138,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -142,7 +150,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } @@ -154,7 +162,7 @@ public class TokenServiceTest { // when // then assertThat( - tokenService.validateTokenExpirationTimeNotExpired(token.getAccessToken())) + tokenUtil.validateTokenExpirationTimeNotExpired(token.getAccessToken())) .isFalse(); } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index b834b61..61325af 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -11,6 +11,14 @@ spring: profiles: active: test + security: + oauth2: + client: + registration: + github: + client-id: ENC(cwYhhVQPF7EA6AsPSNX7BkV4o1Q+kWM1SqFh6SfDoxQ=) + client-secret: ENC(sI6HRt2XDATOJWZvrUqzqrdtO++dR9ttm69l3hwsJX8=) + jpa: open-in-view: false @@ -30,11 +38,12 @@ spring: port: 6378 password: ENC(t9qeZ6WjZb8nTCX6USgT0Q==) -jwt: +token: secret: ENC(Wi5BD1R9VNidIJj2xI10Ww==) - prefix: "Bearer " - header-string: "Authorization" - access-token-prefix: "Token " + prefix: "" + header-string: "" + access-token-prefix: "" + access-token-expire-minute: 9999 admin: univ-id: ENC(MnnJJNaW4by9rR1oo90AfA==)