Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐Ÿ› ๏ธ [refactor] ์ธ์ฆ ๋ฐฉ์‹์„ OAuth2๋กœ ๋ณ€๊ฒฝ #115

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e427a26
feat : GitHubResource๋ฅผ ์ €์žฅํ•˜๋Š” Embedded ๊ฐ์ฒด ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
8d58613
feat : OAuth2 ์˜์กด์„ฑ ์ถ”๊ฐ€ (#112)
binary-ho Mar 8, 2024
23b31a5
feat : OAuth2User๋ฅผ Wrapping ํ•˜๋Š” GitHubUser ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
8a9a2b0
feat : ์ธ๊ฐ€์‹œ ์ €์žฅ๋˜๋Š” CustomOAuth2User ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
bd8241e
refactor : Token Service์— id๋กœ ํ† ํฐ์„ ๋งŒ๋“œ๋Š” ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (#112)
binary-ho Mar 8, 2024
406987b
feat : OAuth2 ์ธ์ฆ ์„ฑ๊ณต์‹œ Response์— RedirectionURL๊ณผ ํ† ํฐ์„ ์„ธํŒ…ํ•˜๋Š” SuccessHandleโ€ฆ
binary-ho Mar 8, 2024
8d0fc13
refactor : id๋ฅผ ๋งฅ๋ฝ์— ๋งž๊ฒŒ memberId๋กœ ๋ณ€๊ฒฝ (#112)
binary-ho Mar 8, 2024
8ee03d5
feat : UrlConstant ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
a433d9a
feat : ํšŒ์›๊ฐ€์ž… ํ”„๋กœ์„ธ์Šค๋ฅผ ๋งˆ์น˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋  Role Guest ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
2f7b98a
feat : ์ธ์ฆ์‹œ ์‚ฌ์šฉ๋  CustomOAuth2User๋ฅผ ์ƒ์„ฑํ•˜๋Š” CustomOAuth2UserService ๊ตฌํ˜„ (#112)
binary-ho Mar 8, 2024
24d6be4
feat : SecurityConfig์— OAuth2 ์„ค์ • ์ถ”๊ฐ€ (#112)
binary-ho Mar 8, 2024
e028080
chore : ์ง€์šธ ์˜ˆ์ • ํด๋ž˜์Šค ํ‘œ์„œ (#112)
binary-ho Mar 10, 2024
43a3fcd
feat : github oauth2 ์„ค์ • ํŒŒ์ผ ์ถ”๊ฐ€ (#112)
binary-ho Mar 10, 2024
e17b1f9
refactor : oauth login path permission (#112)
binary-ho Mar 11, 2024
6b75530
refactor : JwtAuthorizationFilter ๋‹ค์–‘ํ•œ ๊ฐ€๋…์„ฑ ๊ฐœ์„  (#112)
binary-ho Mar 11, 2024
ad5bf95
refactor : token service์— id๋ฅผ parsing ํ•˜๋Š” getId ์ถ”๊ฐ€ (#112)
binary-ho Mar 11, 2024
548e2a1
test : token service getId test ์ž‘์„ฑ (#112)
binary-ho Mar 11, 2024
05e79f0
refactor : JwtAuthenticationFilter์— ํ† ํฐ parse ById ์ ์šฉ (#112)
binary-ho Mar 11, 2024
b6ecacb
test : SecurityConfig Test ์ฝ”๋“œ ์ž‘์„ฑ (#112)
binary-ho Mar 12, 2024
ef23d2a
refactor : Token ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ํฌํ•จํ•œ Property๋ฅผ TokenPropertyHolder๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋กœ ์˜์กด์„ฑโ€ฆ
binary-ho Mar 12, 2024
12bc99b
refactor : ๋„๋ฉ”์ธ๊ณผ ์†Œํ†ต์ด ์—†๋Š” TokenService์˜ ์ด๋ฆ„์„ TokenUtil๋กœ ๋ณ€๊ฒฝ (#112)
binary-ho Mar 12, 2024
a877b25
test : Token Member Id๋ฅผ Fixture์˜ id๋กœ ๋ณ€๊ฒฝ (#112)
binary-ho Mar 12, 2024
0102f77
test : ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ ์‚ญ์ œ (#112)
binary-ho Mar 12, 2024
6126c4f
test : ์ธ๊ฐ€ ํ…Œ์ŠคํŠธ 300๋ฒˆ ์‘๋‹ต ๊ทธ๋ ˆ๋“ค ๋นŒ๋“œ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ…Œ์ŠคํŠธ์šฉ commit (#112)
binary-ho Mar 12, 2024
0b29e9b
test : ์ธ๊ฐ€ ํ…Œ์ŠคํŠธ 300๋ฒˆ ์‘๋‹ต ๊ทธ๋ ˆ๋“ค ๋นŒ๋“œ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ…Œ์ŠคํŠธ์šฉ commit 2 (#112)
binary-ho Mar 12, 2024
d1fc4ad
Merge branch 'develop' into feature/github-oauth-#112
binary-ho Mar 31, 2024
588f5a9
test : SecurityConfigTest์— token prefix ์ถ”๊ฐ€ (#112)
binary-ho Mar 31, 2024
3b6dc09
test : ํ† ํฐ ์ธ๊ฐ€ ํ…Œ์ŠคํŠธ์— LectureRepository Mocking ์ถ”๊ฐ€ (#112)
binary-ho Mar 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/gdsc/binaryho/imhere/constant/UrlConstant.java
Original file line number Diff line number Diff line change
@@ -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() {}
}
28 changes: 28 additions & 0 deletions src/main/java/gdsc/binaryho/imhere/core/member/GitHubResource.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
22 changes: 20 additions & 2 deletions src/main/java/gdsc/binaryho/imhere/core/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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();
}
}
7 changes: 6 additions & 1 deletion src/main/java/gdsc/binaryho/imhere/core/member/Role.java
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findById(Long id);
Optional<Member> findByUnivId(String univId);
Optional<Member> findByGitHubResource_Id(String gitHubId);
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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()

Expand All @@ -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();
}
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}
Loading
Loading