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 17 commits into
base: develop
Choose a base branch
from
Open
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.logincontext.LoginContext;
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<LoginContext> 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이 있는 게 바로 이해가 안 가긴 하네요 🤔🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

처음에는 뗀 상태로 구현하긴 했어요.

그러나 조조의 다음 의견에 동의하여서 붙여주었습니다.

#878 (comment)

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

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

@Component
public class ExistingUserForExistingDevice implements LoginContext {

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

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

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

@Component
public class ExistingUserForNewDevice implements LoginContext {

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

@Override
public Member syncDevice(
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,19 @@
package com.ody.auth.domain.logincontext;

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

public interface LoginContext {

boolean match(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
);

Member syncDevice(
Optional<Member> sameDeviceMember,
Optional<Member> sameProviderIdMember,
Member requestMember
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.ody.auth.domain.logincontext;

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

@Component
public class NewUserForExistingDevice implements LoginContext {

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

@Override
public Member syncDevice(
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.logincontext;

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

@Component
public class NewUserForNewDevice implements LoginContext {

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

@Override
public Member syncDevice(
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.logincontext;

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

@Component
public class OtherUserForExistingDevice implements LoginContext {

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

@Override
public Member syncDevice(
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
Loading
Loading