Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
0d30884
refactor(VolunteerSignController): @Tag 수정
m-a-king Dec 1, 2024
d42b04f
refactor(JwtAuthenticationToken): IdPwFilter에서도 사용하기 위해서 패키지 이동
m-a-king Dec 1, 2024
ec7e64e
feat(CenterRepository): accountId로 Id, Password 조회, 다른 쿼리도 deleted 확인…
m-a-king Dec 1, 2024
6f3b420
feat(CenterSign): accountId로 Id, Password 조회하는 기능 추가
m-a-king Dec 1, 2024
de9335a
feat(GenerateTokensOnLogin): volunteer만 사용하던 usecase를 center도 사용 가능하도…
m-a-king Dec 1, 2024
ca7cdfe
feat(JwtAuthFilter): 센터 로그인 엔드포인트를 JwtAuthFilter가 작동하지 않도록 변경
m-a-king Dec 1, 2024
3475461
feat(IdPwAuthFilter): 기관을 위한 Id, Pw 기반 필터 구현
m-a-king Dec 1, 2024
4ba392a
feat(JwtExceptionFilter): 예외 처리를 일관성 있도록 변경
m-a-king Dec 1, 2024
9704394
feat(CustomAuthenticationProvider): AuthenticationManager 의 authentic…
m-a-king Dec 1, 2024
fb0e75f
feat(SecurityConfig): IdPwAuthFilter 추가 및 의존성 처리 개선
m-a-king Dec 1, 2024
a789f00
test(CenterRepository): 기관 accountId로 기관 Id, 기관 Password 조회 테스트 추가
m-a-king Dec 1, 2024
eb9beb7
test(CenterSignService): 기관 accountId로 기관 Id, 기관 Password 조회 테스트 추가
m-a-king Dec 1, 2024
9810c6b
test(CenterSignService): join assertion chain
m-a-king Dec 1, 2024
668ce3b
feat(JwtAuthenticationToken): @EqualsAndHashCode(callSuper = true) 추가
m-a-king Dec 1, 2024
c633e4b
Merge branch 'main' into feature/104-add-center-login
m-a-king Dec 2, 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
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.somemore.auth.jwt.filter;
package com.somemore.auth.authentication;

import lombok.EqualsAndHashCode;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.io.Serializable;
import java.util.Collection;

@EqualsAndHashCode(callSuper = true)
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private final Serializable principal;
private final transient Object credentials;
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/com/somemore/auth/idpw/filter/IdPwAuthFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.somemore.auth.idpw.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.somemore.auth.cookie.CookieUseCase;
import com.somemore.auth.jwt.domain.EncodedToken;
import com.somemore.auth.jwt.domain.UserRole;
import com.somemore.auth.jwt.usecase.GenerateTokensOnLoginUseCase;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.IOException;
import java.util.UUID;

@RequiredArgsConstructor
@Slf4j
public class IdPwAuthFilter extends UsernamePasswordAuthenticationFilter {

private final AuthenticationManager authenticationManager;
private final GenerateTokensOnLoginUseCase generateTokensOnLoginUseCase;
private final CookieUseCase cookieUseCase;
private final ObjectMapper objectMapper;

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String accountId = request.getParameter("account_id");
String accountPassword = request.getParameter("account_password");

UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(accountId, accountPassword);
return authenticationManager.authenticate(authToken);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
response.setStatus(HttpServletResponse.SC_OK);
EncodedToken accessToken = generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(UUID.fromString(authResult.getName()), UserRole.CENTER);
cookieUseCase.setAccessToken(response, accessToken.value());
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
ProblemDetail problemDetail = buildUnauthorizedProblemDetail(failed);
configureUnauthorizedResponse(response);

objectMapper.writeValue(response.getWriter(), problemDetail);
}

private void configureUnauthorizedResponse(HttpServletResponse response) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
}

private ProblemDetail buildUnauthorizedProblemDetail(AuthenticationException e) {
log.error("IdPwAuthFilter 예외 발생: {}", e.getMessage());

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "인증에서 오류가 발생했습니다.");
problemDetail.setTitle("인증 에러");
problemDetail.setProperty("timestamp", System.currentTimeMillis());
return problemDetail;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.somemore.auth.idpw.provider;

import com.somemore.auth.authentication.JwtAuthenticationToken;
import com.somemore.auth.jwt.domain.EncodedToken;
import com.somemore.auth.jwt.domain.TokenType;
import com.somemore.auth.jwt.domain.UserRole;
import com.somemore.auth.jwt.usecase.JwtUseCase;
import com.somemore.center.usecase.query.CenterSignUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CustomAuthenticationProvider implements AuthenticationProvider {

private final CenterSignUseCase centerSignUseCase;
private final JwtUseCase jwtUseCase;
private final PasswordEncoder passwordEncoder;

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

String centerId = centerSignUseCase.getIdByAccountId(accountId).toString();
String encodedPassword = centerSignUseCase.getPasswordByAccountId(accountId);

if (passwordEncoder.matches(rawAccountPassword, encodedPassword)) {
EncodedToken accessToken = jwtUseCase.generateToken(
centerId,
UserRole.CENTER.getAuthority(),
TokenType.ACCESS
);

return new JwtAuthenticationToken(
centerId,
accessToken,
List.of(new SimpleGrantedAuthority(UserRole.CENTER.getAuthority()))
);
}

return null;
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
11 changes: 9 additions & 2 deletions src/main/java/com/somemore/auth/jwt/filter/JwtAuthFilter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.somemore.auth.jwt.filter;

import com.somemore.auth.authentication.JwtAuthenticationToken;
import com.somemore.auth.jwt.domain.EncodedToken;
import com.somemore.auth.jwt.exception.JwtErrorType;
import com.somemore.auth.jwt.exception.JwtException;
Expand Down Expand Up @@ -30,12 +31,18 @@ public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String token = request.getHeader("Authorization");
return token == null || token.isEmpty();
String path = request.getRequestURI();

return token == null
|| token.isEmpty()
|| path.equals("/api/center/sign-in");
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

EncodedToken accessToken = getAccessToken(request);
jwtUseCase.processAccessToken(accessToken, response);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public class JwtExceptionFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (JwtException e) {
Expand All @@ -42,8 +44,10 @@ private void configureUnauthorizedResponse(HttpServletResponse response) {
}

private ProblemDetail buildUnauthorizedProblemDetail(JwtException e) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, e.getMessage());
problemDetail.setTitle("Authentication Error");
log.error("JwtFilter 예외 발생: {}", e.getMessage());

ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "인증에서 오류가 발생했습니다.");
problemDetail.setTitle("인증 에러");
problemDetail.setProperty("timestamp", System.currentTimeMillis());
return problemDetail;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,27 @@ public class GenerateTokensOnLoginService implements GenerateTokensOnLoginUseCas
private final RefreshTokenManager refreshTokenManager;

@Override
public EncodedToken saveRefreshTokenAndReturnAccessToken(UUID volunteerId) {
EncodedToken accessToken = generateToken(volunteerId, TokenType.ACCESS);
RefreshToken refreshToken = generateRefreshToken(volunteerId, accessToken);
public EncodedToken saveRefreshTokenAndReturnAccessToken(UUID userId, UserRole role) {
EncodedToken accessToken = jwtGenerator.generateToken(
userId.toString(),
role.getAuthority(),
TokenType.ACCESS
);
RefreshToken refreshToken = generateRefreshToken(userId, role, accessToken);
saveRefreshToken(refreshToken);

return accessToken;
}

private EncodedToken generateToken(UUID volunteerId, TokenType tokenType) {
return jwtGenerator.generateToken(
volunteerId.toString(),
UserRole.VOLUNTEER.getAuthority(),
tokenType);
}

private RefreshToken generateRefreshToken(UUID volunteerId, EncodedToken accessToken) {
private RefreshToken generateRefreshToken(UUID userId, UserRole role, EncodedToken accessToken) {
return new RefreshToken(
volunteerId.toString(),
userId.toString(),
accessToken,
generateToken(
volunteerId,
TokenType.REFRESH));
jwtGenerator.generateToken(
userId.toString(),
role.getAuthority(),
TokenType.REFRESH)
);
}

private void saveRefreshToken(RefreshToken refreshToken) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.somemore.auth.jwt.usecase;

import com.somemore.auth.jwt.domain.EncodedToken;
import com.somemore.auth.jwt.domain.UserRole;

import java.util.UUID;

public interface GenerateTokensOnLoginUseCase {
EncodedToken saveRefreshTokenAndReturnAccessToken(UUID volunteerId);
EncodedToken saveRefreshTokenAndReturnAccessToken(UUID userId, UserRole role);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.somemore.auth.cookie.CookieUseCase;
import com.somemore.auth.jwt.domain.EncodedToken;
import com.somemore.auth.jwt.domain.UserRole;
import com.somemore.auth.jwt.usecase.GenerateTokensOnLoginUseCase;
import com.somemore.auth.oauth.OAuthProvider;
import com.somemore.auth.oauth.naver.service.query.ProcessNaverOAuthUserService;
Expand Down Expand Up @@ -46,7 +47,10 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
}

UUID volunteerId = volunteerQueryUseCase.getVolunteerIdByOAuthId(oAuthId);
EncodedToken accessToken = generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(volunteerId);
EncodedToken accessToken =
generateTokensOnLoginUseCase.saveRefreshTokenAndReturnAccessToken(
volunteerId, UserRole.VOLUNTEER
);

cookieUseCase.setAccessToken(response, accessToken.value());
redirectUseCase.redirect(request, response, frontendRootUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface CenterJpaRepository extends JpaRepository<Center, Long> {
boolean existsById(UUID id);
Optional<Center> findCenterById(UUID id);
Optional<Center> findByName(String name);
boolean existsByIdAndDeletedIsFalse(UUID id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ default boolean doesNotExistById(UUID id) {
}
Optional<Center> findCenterById(UUID id);
List<CenterOverviewInfo> findCenterOverviewsByIds(List<UUID> ids);
UUID findIdByAccountId(String accountId);
String findPasswordByAccountId(String accountId);
void deleteAllInBatch();

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.somemore.center.repository;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.somemore.center.domain.Center;
import com.somemore.center.domain.QCenter;
Expand All @@ -19,24 +20,32 @@ public class CenterRepositoryImpl implements CenterRepository {
private final JPAQueryFactory queryFactory;
private final CenterJpaRepository centerJpaRepository;

private static final QCenter center = QCenter.center;

@Override
public Center save(Center center) {
return centerJpaRepository.save(center);
}

@Override
public boolean existsById(UUID id) {
return centerJpaRepository.existsById(id);
return centerJpaRepository.existsByIdAndDeletedIsFalse(id);
}

@Override
public Optional<Center> findCenterById(UUID id) {
return centerJpaRepository.findCenterById(id);

return Optional.ofNullable(
queryFactory
.selectFrom(center)
.where(center.id.eq(id)
.and(isNotDeleted()))
.fetchOne()
);
}

@Override
public List<CenterOverviewInfo> findCenterOverviewsByIds(List<UUID> ids) {
QCenter center = QCenter.center;

return queryFactory
.select(Projections.constructor(
Expand All @@ -47,12 +56,39 @@ public List<CenterOverviewInfo> findCenterOverviewsByIds(List<UUID> ids) {
))
.from(center)
.where(center.id.in(ids)
.and(center.deleted.eq(false)))
.and(isNotDeleted())
)
.fetch();
}

@Override
public UUID findIdByAccountId(String accountId) {

return queryFactory
.select(center.id)
.from(center)
.where(center.accountId.eq(accountId)
.and(isNotDeleted()))
.fetchOne();
}

@Override
public String findPasswordByAccountId(String accountId) {

return queryFactory
.select(center.accountPw)
.from(center)
.where(center.accountId.eq(accountId)
.and(isNotDeleted()))
.fetchOne();
}

@Override
public void deleteAllInBatch() {
centerJpaRepository.deleteAllInBatch();
}

private static BooleanExpression isNotDeleted() {
return center.deleted.isFalse();
}
}
Loading