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: memberService에서 인증관련 로직 분리 #878

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ backend
│   │   │   ├── request
│   │   │   └── response
│   │   ├── service
│   │   └── token
│   │   └── jwtToken
│   ├── common
│   │   ├── annotation
│   │   ├── aop
Expand Down
38 changes: 9 additions & 29 deletions backend/src/main/java/com/ody/auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ody.auth;

import com.ody.auth.token.AccessToken;
import com.ody.auth.token.JwtToken;
import com.ody.auth.token.RefreshToken;
import com.ody.common.exception.OdyBadRequestException;
import com.ody.common.exception.OdyUnauthorizedException;
Expand All @@ -9,20 +10,18 @@
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class JwtTokenProvider {

private final AuthProperties authProperties;

public JwtTokenProvider(AuthProperties authProperties) {
this.authProperties = authProperties;
}

public AccessToken createAccessToken(long memberId) {
return new AccessToken(memberId, authProperties);
}
Expand All @@ -45,36 +44,17 @@ public long parseAccessToken(AccessToken accessToken) {
}
}

public void validate(AccessToken accessToken) {
if (!isUnexpired(accessToken)) {
throw new OdyUnauthorizedException("만료된 액세스 토큰입니다.");
}
}

public void validate(RefreshToken refreshToken) {
if (!isUnexpired(refreshToken)) {
throw new OdyUnauthorizedException("만료된 리프레시 토큰입니다.");
}
}

public boolean isUnexpired(AccessToken accessToken) {
try {
Jwts.parser()
.setSigningKey(authProperties.getAccessKey())
.parseClaimsJws(accessToken.getValue());
return true;
} catch (ExpiredJwtException exception) {
return false;
} catch (JwtException exception) {
throw new OdyBadRequestException(exception.getMessage());
public void validate(JwtToken jwtToken) {
if (!isUnexpired(jwtToken)) {
throw new OdyUnauthorizedException("만료된 토큰입니다.");
}
}

public boolean isUnexpired(RefreshToken refreshToken) {
public boolean isUnexpired(JwtToken jwtToken) {
try {
Jwts.parser()
.setSigningKey(authProperties.getRefreshKey())
.parseClaimsJws(refreshToken.getValue());
.setSigningKey(jwtToken.getSecretKey(authProperties))
.parseClaimsJws(jwtToken.getValue());
return true;
} catch (ExpiredJwtException exception) {
return false;
Expand Down
30 changes: 30 additions & 0 deletions backend/src/main/java/com/ody/auth/domain/Authorizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ody.auth.domain;

import com.ody.auth.domain.authpolicy.AuthPolicy;
import com.ody.common.exception.OdyUnauthorizedException;
import com.ody.member.domain.Member;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
public class Authorizer {

private final List<AuthPolicy> authPolicies;

@Transactional
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이곳에 Transactional이 있는 게 바로 이해가 안 가긴 하네요 🤔🤔

public Member authorize(
Optional<Member> sameDeviceMember,
Optional<Member> samePidMember,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안]
Pid가 통용되는 표현이면 무시하셔도 됩니다.
sameProviderIdMember 축약어 사용 안 하는 건 어떤가요? 길어지긴 하지만, 처음 봤을때 이해가 한 번에 되지 않았어요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 반영 완료!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorizer의 authorize() 인자에는 반영이 안 되어 있어요!

Member requestMember
) {
return authPolicies.stream()
.filter(type -> type.match(sameDeviceMember, samePidMember, requestMember))
.findAny()
.orElseThrow(() -> new OdyUnauthorizedException("잘못된 인증 요청입니다."))
.authorize(sameDeviceMember, samePidMember, requestMember);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;

public interface AuthPolicy {

boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
);
Comment on lines +8 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안] 인터페이스랑 구현체에서 개행이 되었다 안 되었다 하네요?? 하나로 통일하면 좋을 것 같아요


Member authorize(
Optional<Member> sameDeviceMember,
Comment on lines +14 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[제안] 현재 authorize 메서드가 수행하고 있는 동작이 개인적으론 권한 부여로 보이지 않는데, 아래와 같은 네이밍은 어떤가요?

  • applyDevicePolicy
  • syncDeviceTokenWithLoginState
  • updateDeviceTokenBasedOnLoginContext
  • manageDeviceTokenOnLogin

Optional<Member> sameProviderIdMember,
Member requestMember
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class ExistingUserForExistingDevice implements AuthPolicy {

@Override
public boolean match(Optional<Member> sameDeviceMember, Optional<Member> sameProviderIdMember, Member requestMember) {
return sameDeviceMember.isPresent()
&& sameProviderIdMember.isPresent()
&& requestMember.isSame(sameDeviceMember.get());
}

@Override
public Member authorize(Optional<Member> sameDeviceMember, Optional<Member> sameProviderIdMember, Member requestMember) {
return sameProviderIdMember.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class ExistingUserForNewDevice implements AuthPolicy {

@Override
public boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
return sameDeviceMember.isEmpty()
&& sameProviderIdMember.isPresent();
}

@Override
public Member authorize(Optional<Member> sameDeviceMember, Optional<Member> sameProviderIdMember, Member requestMember) {
Member sameAuthProviderMember = sameProviderIdMember.get();
sameAuthProviderMember.updateDeviceToken(requestMember.getDeviceToken());
return sameAuthProviderMember;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class NewUserForExistingDevice implements AuthPolicy {

@Override
public boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
return sameDeviceMember.isPresent()
&& sameProviderIdMember.isEmpty();
}

@Override
public Member authorize(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
sameDeviceMember.get().updateDeviceTokenNull();
return requestMember;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class NewUserForNewDevice implements AuthPolicy {

@Override
public boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
return sameDeviceMember.isEmpty()
&& sameProviderIdMember.isEmpty();
}

@Override
public Member authorize(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
return requestMember;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ody.auth.domain.authpolicy;

import com.ody.member.domain.Member;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class OtherUserForExistingDevice implements AuthPolicy {

@Override
public boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
return sameDeviceMember.isPresent()
&& sameProviderIdMember.isPresent()
&& !requestMember.isSame(sameDeviceMember.get());
}

@Override
public Member authorize(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
) {
sameDeviceMember.get().updateDeviceTokenNull();
sameProviderIdMember.get().updateDeviceToken(requestMember.getDeviceToken());
return sameProviderIdMember.get();
}
}
20 changes: 15 additions & 5 deletions backend/src/main/java/com/ody/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

import com.ody.auth.JwtTokenProvider;
import com.ody.auth.domain.AuthorizationHeader;
import com.ody.auth.domain.Authorizer;
import com.ody.auth.dto.request.AuthRequest;
import com.ody.auth.dto.response.AuthResponse;
import com.ody.auth.token.AccessToken;
import com.ody.auth.token.RefreshToken;
import com.ody.common.exception.OdyBadRequestException;
import com.ody.member.domain.Member;
import com.ody.member.service.MemberService;
import lombok.AllArgsConstructor;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@AllArgsConstructor
@RequiredArgsConstructor
public class AuthService {

private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;
private final Authorizer authorizer;

public Member parseAccessToken(String rawAccessToken) {
AccessToken accessToken = new AccessToken(rawAccessToken);
Expand All @@ -31,8 +34,16 @@ public Member parseAccessToken(String rawAccessToken) {

@Transactional
public AuthResponse issueTokens(AuthRequest authRequest) {
Member member = memberService.save(authRequest.toMember());
return issueNewTokens(member.getId());
Member requestMember = authRequest.toMember();
Member authorizedMember = findAuthroizedMember(requestMember);
Member savedAuthorizedMember = memberService.save(authorizedMember) ;
return issueNewTokens(savedAuthorizedMember.getId());
}

private Member findAuthroizedMember(Member requestMember) {
Optional<Member> sameDeviceMember = memberService.findByDeviceToken(requestMember.getDeviceToken());
Optional<Member> samePidMember = memberService.findByAuthProvider(requestMember.getAuthProvider());
return authorizer.authorize(sameDeviceMember, samePidMember, requestMember);
}

@Transactional
Expand Down Expand Up @@ -69,7 +80,6 @@ private AuthResponse issueNewTokens(long memberId) {
public void logout(String rawAccessTokenValue) {
AccessToken accessToken = new AccessToken(rawAccessTokenValue);
jwtTokenProvider.validate(accessToken);

long memberId = jwtTokenProvider.parseAccessToken(accessToken);
memberService.updateRefreshToken(memberId, null);
}
Expand Down
8 changes: 6 additions & 2 deletions backend/src/main/java/com/ody/auth/token/AccessToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
import lombok.Getter;

@Getter
public class AccessToken {
public class AccessToken implements JwtToken {

private static final String ACCESS_TOKEN_PREFIX = "Bearer access-token=";
public static final String DELIMITER = " ";

private final String value;

Expand All @@ -38,4 +37,9 @@ private void validate(String value) {
private String parseAccessToken(String rawValue) {
return rawValue.substring(ACCESS_TOKEN_PREFIX.length()).trim();
}

@Override
public String getSecretKey(AuthProperties authProperties) {
return authProperties.getAccessKey();
}
}
10 changes: 10 additions & 0 deletions backend/src/main/java/com/ody/auth/token/JwtToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ody.auth.token;

import com.ody.auth.AuthProperties;

public interface JwtToken {

String getSecretKey(AuthProperties authProperties);

String getValue();
}
7 changes: 6 additions & 1 deletion backend/src/main/java/com/ody/auth/token/RefreshToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@Getter
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RefreshToken {
public class RefreshToken implements JwtToken {

public static final String REFRESH_TOKEN_PREFIX = "refresh-token=";

Expand Down Expand Up @@ -45,6 +45,11 @@ public RefreshToken(AuthProperties authProperties) {
.compact();
}

@Override
public String getSecretKey(AuthProperties authProperties) {
return authProperties.getRefreshKey();
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/java/com/ody/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public boolean isLogout() {
return this.refreshToken == null;
}

public boolean isSame(AuthProvider otherAuthProvider) {
return this.authProvider.equals(otherAuthProvider);
public boolean isSame(Member otherMember) {
return this.authProvider.equals(otherMember.getAuthProvider());
}

public void updateRefreshToken(RefreshToken refreshToken) {
Expand Down
Loading
Loading