Skip to content

Commit ef9c1b2

Browse files
authored
[Feature] 봉사자 ID, PW 로그인 추가 (#282)
* feat(user): email 사용 철회에 따라서 accountId로 변경 * feat(user): accountId 기준 User 조회 기능 추가 * feat(auth): 기관에만 한정되지 않도록 수정 * feat(auth): sign 컨트롤러 수정 * feat(auth): id, pw 인증 과정 수정, 개선 * feat(auth): id, pw 인증/인가 성공 로직 수정 - 쿠키가 아닌 응답값의 헤더로 jwt 액세스 토큰을 전달 * feat(auth): 인증 토큰 정적 팩토리 메서드 추가 * feat(auth): id, pw 인증 엔드포인트 수정, 불필요한 라인 삭제 * test(user): 프로덕션 코드 변화에 따라서 테스트 개선 * feat(auth): idpw 필터 의존성 주입 변경 * test(user): 유저 계정 아이디 기반 조회 테스트 추가 * refactor: 깃허브 개행 문제 해결 * refactor: 빈 파일 삭제 * refactor: 소나 이슈 해결 - 사용하지 않는 import - 테스트 코드 개선 * refactor: 코드 리뷰 반영 - 불필요한 라인 삭제 및 수정 * refactor: 코드 리뷰 반영 - from에서 of로 변경
1 parent f427183 commit ef9c1b2

File tree

16 files changed

+153
-96
lines changed

16 files changed

+153
-96
lines changed

src/main/java/com/somemore/global/auth/authentication/JwtAuthenticationToken.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package com.somemore.global.auth.authentication;
22

3+
import com.somemore.global.auth.jwt.domain.EncodedToken;
4+
import com.somemore.user.domain.User;
35
import lombok.EqualsAndHashCode;
46
import org.springframework.security.authentication.AbstractAuthenticationToken;
57
import org.springframework.security.core.GrantedAuthority;
8+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
69

710
import java.io.Serializable;
811
import java.util.Collection;
12+
import java.util.List;
913

1014
@EqualsAndHashCode(callSuper = true)
1115
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
@@ -30,4 +34,20 @@ public Object getCredentials() {
3034
public Object getPrincipal() {
3135
return principal;
3236
}
37+
38+
public static JwtAuthenticationToken of(User user, EncodedToken accessToken) {
39+
return new JwtAuthenticationToken(
40+
user.getId(),
41+
accessToken,
42+
List.of(new SimpleGrantedAuthority(user.getRole().getAuthority()))
43+
);
44+
}
45+
46+
public static JwtAuthenticationToken of(String userId, String role, EncodedToken accessToken) {
47+
return new JwtAuthenticationToken(
48+
userId,
49+
accessToken,
50+
List.of(new SimpleGrantedAuthority(role))
51+
);
52+
}
3353
}

src/main/java/com/somemore/domains/center/controller/CenterSignController.java renamed to src/main/java/com/somemore/global/auth/controller/SignController.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
1-
package com.somemore.domains.center.controller;
1+
package com.somemore.global.auth.controller;
22

3-
import com.somemore.domains.center.dto.request.CenterSignRequestDto;
3+
import com.somemore.global.auth.dto.SignRequestDto;
44
import com.somemore.global.auth.signout.usecase.SignOutUseCase;
55
import com.somemore.global.common.response.ApiResponse;
66
import io.swagger.v3.oas.annotations.tags.Tag;
77
import jakarta.servlet.http.HttpServletResponse;
88
import lombok.RequiredArgsConstructor;
9-
import lombok.extern.slf4j.Slf4j;
109
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1110
import org.springframework.web.bind.annotation.PostMapping;
1211
import org.springframework.web.bind.annotation.RequestMapping;
1312
import org.springframework.web.bind.annotation.RequestParam;
1413
import org.springframework.web.bind.annotation.RestController;
1514

1615
@RestController
17-
@Slf4j
1816
@RequiredArgsConstructor
1917
@RequestMapping("/api/center")
20-
@Tag(name = "center Sign API", description = "기관 로그인, 로그아웃")
21-
public class CenterSignController {
18+
@Tag(name = "Sign API", description = "ID,PW 로그인, 로그아웃")
19+
public class SignController {
2220

2321
private final SignOutUseCase signOutUseCase;
2422

2523
/*
26-
* 기관 로그인 엔드포인트
24+
* ID 및 PW 기반 로그인 엔드포인트 (Swagger 문서화를 위한 엔드포인트)
25+
*
26+
* 이 엔드포인트는 실제로 작동하지 않으며, Spring Security 필터 체인에 의해 요청이 처리됩니다.
27+
* 로그인 요청은 필터에서 가로채어 인증 절차를 수행하며, 이 엔드포인트로 전달되지 않습니다.
28+
* 따라서 이 엔드포인트는 로그인 요청의 처리를 위한 진입점이 아니라,
29+
* Swagger API 문서화를 위해 정의된 엔드포인트입니다.
30+
*
2731
* 실제 로그인 절차는 필터에서 처리됩니다.
28-
* 이 엔드포인트는 로그인 요청을 받아 필터에 의한 인증 절차를 수행합니다.
2932
*/
3033
@PostMapping("/sign-in")
3134
public ApiResponse<String> signIn(
32-
@RequestParam CenterSignRequestDto requestDto
35+
@RequestParam SignRequestDto signRequestDto
3336
) {
3437
return ApiResponse.ok("로그인되었습니다.");
3538
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1-
package com.somemore.domains.center.dto.request;
1+
package com.somemore.global.auth.dto;
22

33
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
44
import com.fasterxml.jackson.databind.annotation.JsonNaming;
55
import io.swagger.v3.oas.annotations.media.Schema;
6-
import lombok.Builder;
76

87
@JsonNaming(SnakeCaseStrategy.class)
9-
@Builder
10-
public record CenterSignRequestDto(
11-
@Schema(description = "기관 아이디", example = "somemore")
8+
public record SignRequestDto(
9+
@Schema(description = "ID", example = "somemore")
1210
String accountId,
13-
@Schema(description = "기관 패스워드", example = "password1234")
11+
@Schema(description = "PW", example = "password1234")
1412
String accountPassword
1513
) {
16-
1714
}

src/main/java/com/somemore/global/auth/idpw/filter/IdPwAuthFilter.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import com.somemore.global.auth.cookie.CookieUseCase;
55
import com.somemore.global.auth.jwt.domain.EncodedToken;
6-
import com.somemore.user.domain.UserRole;
76
import com.somemore.global.auth.jwt.usecase.GenerateTokensOnLoginUseCase;
7+
import com.somemore.user.domain.UserRole;
88
import jakarta.servlet.FilterChain;
99
import jakarta.servlet.http.HttpServletRequest;
1010
import jakarta.servlet.http.HttpServletResponse;
@@ -17,6 +17,7 @@
1717
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1818
import org.springframework.security.core.Authentication;
1919
import org.springframework.security.core.AuthenticationException;
20+
import org.springframework.security.core.GrantedAuthority;
2021
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2122

2223
import java.io.IOException;
@@ -28,7 +29,7 @@ public class IdPwAuthFilter extends UsernamePasswordAuthenticationFilter {
2829

2930
private final AuthenticationManager authenticationManager;
3031
private final GenerateTokensOnLoginUseCase generateTokensOnLoginUseCase;
31-
private final CookieUseCase cookieUseCase;
32+
// private final CookieUseCase cookieUseCase;
3233
private final ObjectMapper objectMapper;
3334

3435
@Override
@@ -43,8 +44,16 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
4344
@Override
4445
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
4546
response.setStatus(HttpServletResponse.SC_OK);
46-
EncodedToken accessToken = generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(UUID.fromString(authResult.getName()), UserRole.CENTER);
47-
cookieUseCase.setAccessToken(response, accessToken.value());
47+
EncodedToken accessToken =
48+
generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(
49+
UUID.fromString(authResult.getName()),
50+
UserRole.from(authResult.getAuthorities().stream()
51+
.findFirst()
52+
.map(GrantedAuthority::getAuthority)
53+
.orElseThrow(() -> new IllegalStateException("유저 권한 자체가 존재하지 않습니다."))));
54+
55+
response.setHeader("Authorization", accessToken.getValueWithPrefix());
56+
// cookieUseCase.setAccessToken(response, accessToken.value());
4857
}
4958

5059
@Override
@@ -64,8 +73,8 @@ private void configureUnauthorizedResponse(HttpServletResponse response) {
6473
private ProblemDetail buildUnauthorizedProblemDetail(AuthenticationException e) {
6574
log.error("IdPwAuthFilter 예외 발생: {}", e.getMessage());
6675

67-
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "인증에서 오류가 발생했습니다.");
68-
problemDetail.setTitle("인증 에러");
76+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "ID, PW 인증/인가에서 오류가 발생했습니다.");
77+
problemDetail.setTitle("인증/인가 에러");
6978
problemDetail.setProperty("timestamp", System.currentTimeMillis());
7079
return problemDetail;
7180
}

src/main/java/com/somemore/global/auth/idpw/provider/CustomAuthenticationProvider.java

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,60 @@
33
import com.somemore.global.auth.authentication.JwtAuthenticationToken;
44
import com.somemore.global.auth.jwt.domain.EncodedToken;
55
import com.somemore.global.auth.jwt.domain.TokenType;
6-
import com.somemore.user.domain.UserRole;
76
import com.somemore.global.auth.jwt.usecase.JwtUseCase;
8-
import com.somemore.domains.center.usecase.query.CenterSignUseCase;
7+
import com.somemore.user.domain.User;
8+
import com.somemore.user.usecase.UserQueryUseCase;
99
import lombok.RequiredArgsConstructor;
1010
import org.springframework.security.authentication.AuthenticationProvider;
11+
import org.springframework.security.authentication.BadCredentialsException;
1112
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
1213
import org.springframework.security.core.Authentication;
1314
import org.springframework.security.core.AuthenticationException;
14-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1515
import org.springframework.security.crypto.password.PasswordEncoder;
1616
import org.springframework.stereotype.Component;
1717
import org.springframework.transaction.annotation.Transactional;
1818

19-
import java.util.List;
20-
2119
@Component
2220
@RequiredArgsConstructor
2321
@Transactional(readOnly = true)
2422
public class CustomAuthenticationProvider implements AuthenticationProvider {
2523

26-
private final CenterSignUseCase centerSignUseCase;
2724
private final JwtUseCase jwtUseCase;
2825
private final PasswordEncoder passwordEncoder;
26+
private final UserQueryUseCase userQueryUseCase;
2927

3028
@Override
3129
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
3230
String accountId = authentication.getName();
3331
String rawAccountPassword = authentication.getCredentials().toString();
3432

35-
String centerId = centerSignUseCase.getIdByAccountId(accountId).toString();
36-
String encodedPassword = centerSignUseCase.getPasswordByAccountId(accountId);
37-
38-
if (passwordEncoder.matches(rawAccountPassword, encodedPassword)) {
39-
EncodedToken accessToken = jwtUseCase.generateToken(
40-
centerId,
41-
UserRole.CENTER.getAuthority(),
42-
TokenType.ACCESS
43-
);
44-
45-
return new JwtAuthenticationToken(
46-
centerId,
47-
accessToken,
48-
List.of(new SimpleGrantedAuthority(UserRole.CENTER.getAuthority()))
49-
);
50-
}
33+
User user = userQueryUseCase.getByAccountId(accountId);
34+
35+
validatePassword(rawAccountPassword, user.getPassword());
5136

52-
return null;
37+
EncodedToken accessToken = generateAccessToken(user);
38+
39+
return JwtAuthenticationToken.of(user, accessToken);
5340
}
5441

5542
@Override
5643
public boolean supports(Class<?> authentication) {
5744
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
5845
}
46+
47+
private EncodedToken generateAccessToken(User user) {
48+
return jwtUseCase.generateToken(
49+
user.getId().toString(),
50+
user.getRole().getAuthority(),
51+
TokenType.ACCESS
52+
);
53+
}
54+
55+
private void validatePassword(String rawPassword, String encodedPassword) {
56+
if (!passwordEncoder.matches(rawPassword, encodedPassword)) {
57+
throw new BadCredentialsException("비밀번호가 일치하지 않습니다.");
58+
}
59+
60+
// TODO 비활성 계정 검증
61+
}
5962
}

src/main/java/com/somemore/global/auth/jwt/filter/JwtAuthFilter.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515
import lombok.RequiredArgsConstructor;
1616
import lombok.extern.slf4j.Slf4j;
1717
import org.springframework.security.core.Authentication;
18-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1918
import org.springframework.security.core.context.SecurityContextHolder;
2019
import org.springframework.stereotype.Component;
2120
import org.springframework.web.filter.OncePerRequestFilter;
2221

2322
import java.io.IOException;
2423
import java.util.Arrays;
25-
import java.util.List;
2624
import java.util.Objects;
2725

2826
@RequiredArgsConstructor
@@ -101,10 +99,6 @@ private JwtAuthenticationToken createAuthenticationToken(Claims claims,
10199
String userId = claims.get("id", String.class);
102100
String role = claims.get("role", String.class);
103101

104-
return new JwtAuthenticationToken(
105-
userId,
106-
accessToken,
107-
List.of(new SimpleGrantedAuthority(role))
108-
);
102+
return JwtAuthenticationToken.of(userId, role, accessToken);
109103
}
110104
}

src/main/java/com/somemore/global/config/SecurityConfig.java

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.somemore.global.config;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import com.somemore.global.auth.cookie.CookieUseCase;
54
import com.somemore.global.auth.idpw.filter.IdPwAuthFilter;
65
import com.somemore.global.auth.jwt.filter.JwtAuthFilter;
76
import com.somemore.global.auth.jwt.filter.JwtExceptionFilter;
@@ -12,8 +11,8 @@
1211
import lombok.RequiredArgsConstructor;
1312
import org.springframework.context.annotation.Bean;
1413
import org.springframework.context.annotation.Configuration;
15-
import org.springframework.security.config.Customizer;
1614
import org.springframework.security.authentication.AuthenticationManager;
15+
import org.springframework.security.config.Customizer;
1716
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
1817
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
1918
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -46,11 +45,11 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration a
4645
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,
4746
AuthenticationManager authenticationManager,
4847
GenerateTokensOnLoginUseCase generateTokensOnLoginUseCase,
49-
CookieUseCase cookieUseCase,
48+
// CookieUseCase cookieUseCase,
5049
ObjectMapper objectMapper) throws Exception {
5150

52-
IdPwAuthFilter idPwAuthFilter = new IdPwAuthFilter(authenticationManager, generateTokensOnLoginUseCase, cookieUseCase, objectMapper);
53-
idPwAuthFilter.setFilterProcessesUrl("/api/center/sign-in");
51+
IdPwAuthFilter idPwAuthFilter = new IdPwAuthFilter(authenticationManager, generateTokensOnLoginUseCase, objectMapper);
52+
idPwAuthFilter.setFilterProcessesUrl("/api/sign-in/id-pw");
5453

5554
httpSecurity
5655
.csrf(AbstractHttpConfigurer::disable)
@@ -62,20 +61,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,
6261
.cors(Customizer.withDefaults())
6362

6463
.authorizeHttpRequests(request ->
65-
request
66-
.requestMatchers(
67-
"/api/center/sign-in",
68-
"/**"
69-
// "/login",
70-
// "/oauth2/**",
71-
// "/api/auth/**",
72-
// "/v3/api-docs/**",
73-
// "/swagger/**",
74-
// "/swagger-ui.html",
75-
// "/swagger-ui/**"
76-
)
77-
.permitAll()
78-
.anyRequest().authenticated()
64+
request
65+
.requestMatchers("/**").permitAll()
66+
.anyRequest().authenticated()
7967
)
8068

8169
.oauth2Login(oauth2 ->

src/main/java/com/somemore/user/domain/User.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public class User extends BaseEntity {
2828
@Column(name = "id", nullable = false, columnDefinition = "BINARY(16)")
2929
private UUID id;
3030

31-
@Column(name = "email", nullable = false)
32-
private String email;
31+
@Column(name = "account_id", nullable = false)
32+
private String accountId;
3333

3434
@Column(name = "password", nullable = false)
3535
private String password;
@@ -41,15 +41,15 @@ public class User extends BaseEntity {
4141

4242
public static User from(UserAuthInfo userAuthInfo, UserRole role) {
4343
return User.builder()
44-
.email(userAuthInfo.email())
44+
.accountId(userAuthInfo.accountId())
4545
.password(userAuthInfo.password())
4646
.role(role)
4747
.build();
4848
}
4949

5050
@Builder
51-
private User(String email, String password, UserRole role) {
52-
this.email = email;
51+
private User(String accountId, String password, UserRole role) {
52+
this.accountId = accountId;
5353
this.password = password;
5454
this.role = role;
5555
}

src/main/java/com/somemore/user/dto/UserAuthInfo.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
import java.util.UUID;
66

7-
public record UserAuthInfo(String email,
7+
public record UserAuthInfo(String accountId,
88
String password) {
99

1010
public static UserAuthInfo createForOAuth(OAuthProvider provider) {
11-
String email = provider.getProviderName() + UUID.randomUUID();
11+
String accountId = provider.getProviderName() + UUID.randomUUID();
1212
String password = String.valueOf(UUID.randomUUID());
13-
return new UserAuthInfo(email, password);
13+
return new UserAuthInfo(accountId, password);
1414
}
1515
}

src/main/java/com/somemore/user/repository/user/UserRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@
88
public interface UserRepository {
99

1010
Optional<User> findById(UUID id);
11+
12+
Optional<User> findByAccountId(String accountId);
13+
1114
User save(User user);
1215
}

0 commit comments

Comments
 (0)