-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Feat] change login to OIDC #52
Changes from 8 commits
1dbaf3c
09ecf91
a9e3a0b
26266db
4cb89c1
46483d0
bf9d5c4
26c45ec
e5a96a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,38 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode; | ||
import gdsc.konkuk.platformcore.application.member.exceptions.UserNotFoundException; | ||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; | ||
import java.nio.charset.StandardCharsets; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.oauth2.core.oidc.user.OidcUser; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public void onAuthenticationSuccess( | ||
HttpServletRequest request, HttpServletResponse response, Authentication authentication) {} | ||
HttpServletRequest request, HttpServletResponse response, Authentication authentication) { | ||
|
||
OidcUser oidcUser = (OidcUser) authentication.getPrincipal(); | ||
Member member = memberRepository.findByEmail(oidcUser.getEmail()) | ||
.orElseThrow(() -> UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND)); | ||
String token = jwtTokenProvider.createToken(member); | ||
|
||
response.addHeader("Authorization", "Bearer " + token); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding(StandardCharsets.UTF_8.name()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.UserNotFoundException; | ||
|
||
import java.util.List; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; | ||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; | ||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||
import org.springframework.security.oauth2.core.OAuth2Error; | ||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; | ||
import org.springframework.security.oauth2.core.oidc.user.OidcUser; | ||
import org.springframework.stereotype.Service; | ||
|
||
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode; | ||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class CustomOAuthUserService extends OidcUserService { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { | ||
try { | ||
OidcUser oidcUser = super.loadUser(userRequest); | ||
return processOAuth2User(oidcUser); | ||
} catch (Exception ex) { | ||
throw new OAuth2AuthenticationException( | ||
new OAuth2Error("processing_error", "Failed to process user info", null)); | ||
} | ||
} | ||
|
||
private OidcUser processOAuth2User(OidcUser oidcUser) { | ||
String email = oidcUser.getEmail(); | ||
Member member = memberRepository.findByEmail(email) | ||
.orElseThrow(() -> UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND)); | ||
|
||
return new DefaultOidcUser( | ||
List.of(new SimpleGrantedAuthority(member.getRole().toString())), | ||
oidcUser.getIdToken(), | ||
oidcUser.getUserInfo()); | ||
} | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import io.jsonwebtoken.JwtException; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.StringUtils; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) | ||
throws ServletException, IOException { | ||
try { | ||
String token = getJwtFromRequest(request); | ||
Authentication authentication = jwtTokenProvider.getAuthentication(token); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} catch (JwtException | IllegalArgumentException e) { | ||
SecurityContextHolder.clearContext(); | ||
} | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
|
||
private String getJwtFromRequest(HttpServletRequest request) { | ||
String bearerToken = request.getHeader("Authorization"); | ||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { | ||
return bearerToken.substring(7); | ||
} | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package gdsc.konkuk.platformcore.application.auth; | ||
|
||
import gdsc.konkuk.platformcore.domain.member.entity.Member; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.security.Keys; | ||
import jakarta.annotation.PostConstruct; | ||
import java.util.Base64; | ||
import java.util.Collection; | ||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.stereotype.Component; | ||
import io.jsonwebtoken.Claims; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtTokenProvider { | ||
|
||
@Value("${jwt.secret}") | ||
private String secretKey; | ||
|
||
@Value("${jwt.expiration}") | ||
private long validityInMilliseconds; | ||
|
||
@PostConstruct | ||
protected void init() { | ||
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); | ||
} | ||
|
||
public String createToken(Member member) { | ||
Claims claims = Jwts.claims() | ||
.subject(member.getId().toString()) | ||
.add("studentId", member.getStudentId()) | ||
.add("email", member.getEmail()) | ||
.add("roles", List.of(member.getRole())) | ||
.build(); | ||
|
||
Date now = new Date(); | ||
Date validity = new Date(now.getTime() + validityInMilliseconds); | ||
|
||
return Jwts.builder() | ||
.claims(claims) | ||
.issuedAt(now) | ||
.expiration(validity) | ||
.signWith(Keys.hmacShaKeyFor(secretKey.getBytes())) | ||
.compact(); | ||
} | ||
|
||
public Authentication getAuthentication(String token) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [제안-답할필요X] import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public Claims extractClaimsFromToken(String token) {
return parseClaims(token);
}
public Authentication getAuthenticationFromTokenClaims(Claims claims) {
authorities = getAuthroritiesFromClaims(claims);
principal = createPrincipalFromClaims(claims);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반영했습니다! 더해서 실수로 삭제한 코멘트를 반영해서 인증 실패 시 log를 추가했습니다.
단, 로그인하지 않은 경우( |
||
Claims claims = parseClaims(token); | ||
|
||
Collection<? extends GrantedAuthority> authorities = | ||
((List<?>) claims.get("roles")) | ||
.stream() | ||
.map(role -> new SimpleGrantedAuthority("ROLE_" + role)) | ||
.collect(Collectors.toList()); | ||
|
||
Map<String, Object> principal = new HashMap<>(); | ||
principal.put("memberId", claims.getSubject()); | ||
principal.put("studentId", claims.get("studentId", String.class)); | ||
principal.put("email", claims.get("email", String.class)); | ||
return new UsernamePasswordAuthenticationToken(principal, "", authorities); | ||
} | ||
|
||
private Claims parseClaims(String token) { | ||
return Jwts.parser() | ||
.verifyWith(Keys.hmacShaKeyFor(secretKey.getBytes())) | ||
.build() | ||
.parseSignedClaims(token) | ||
.getPayload(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문] Exception은 범위가 조금 큰 것 같습니당 혹시 좁힐 수 있을까요? 아니면 그냥 어떤 에러라도 처리하는게 맞을까요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분에 대해서는 저 역시 고민입니다, 하지만 우선 기능상 Critical하지 않고, Filter 특성 상
ControllerAdvice
로 처리할 수 없기 때문에 발생 가능한 모든 Case에 대비하려고 합니다.