-
Notifications
You must be signed in to change notification settings - Fork 1
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
[Docs] week7 & [Feat] Security & JWT 구현 #64
base: main
Are you sure you want to change the base?
Changes from all commits
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,10 +1,14 @@ | ||
|
||
package com.jiyunio.todolist.config; | ||
import com.jiyunio.todolist.jwt.CustomAuthenticationProvider; | ||
import com.jiyunio.todolist.jwt.CustomUserDetailsService; | ||
import com.jiyunio.todolist.jwt.JwtAuthenticationFilter; | ||
import com.jiyunio.todolist.jwt.JwtProvider; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.security.authentication.AuthenticationProvider; | ||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
|
@@ -27,16 +31,17 @@ | |
@Configuration | ||
@RequiredArgsConstructor | ||
public class SecurityConfig { | ||
|
||
private final CustomUserDetailsService userDetailsService; | ||
private final JwtProvider jwtProvider; | ||
@Bean | ||
public static BCryptPasswordEncoder bCryptPasswordEncoder() { | ||
public static BCryptPasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
http | ||
.httpBasic(AbstractHttpConfigurer::disable) | ||
.csrf(AbstractHttpConfigurer::disable) | ||
|
||
//접근 제어 | ||
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. 접근 제어를 다루는 부분이 100개면 100개의 requestMatchers를 적어줘야 할까? |
||
|
@@ -46,12 +51,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti | |
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll() | ||
.requestMatchers("/v3/**", "/swagger-ui/**").permitAll() | ||
.anyRequest().authenticated()) //외의 접근은 인증 필수! | ||
|
||
.sessionManagement(session -> session | ||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class); | ||
return http.build(); | ||
} | ||
|
||
|
||
@Bean | ||
public AuthenticationProvider authenticationProvider(){ | ||
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. 이거는 언제 쓰는 거야? |
||
return new CustomAuthenticationProvider(userDetailsService, passwordEncoder()); | ||
} | ||
} |
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. 해당 클래스는 네 코드에서 어떻게 동작하는거야? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.jiyunio.todolist.jwt; | ||
|
||
|
||
import com.jiyunio.todolist.customError.CustomException; | ||
import com.jiyunio.todolist.customError.ErrorCode; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
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.userdetails.UserDetails; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.stereotype.Service; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
@Slf4j | ||
public class CustomAuthenticationProvider implements AuthenticationProvider { | ||
private final CustomUserDetailsService userDetailsService; | ||
private final BCryptPasswordEncoder passwordEncoder; | ||
|
||
@Override | ||
public Authentication authenticate(Authentication authentication) throws AuthenticationException { | ||
String userId = authentication.getName(); | ||
String userPw = (String) authentication.getCredentials(); | ||
|
||
UserDetails user = userDetailsService.loadUserByUsername(userId); | ||
if (user == null || !this.passwordEncoder.matches(userPw, user.getPassword())) { | ||
throw new CustomException(HttpStatus.UNAUTHORIZED, ErrorCode.NOT_EXIST_MEMBER); | ||
} | ||
return new UsernamePasswordAuthenticationToken(userId, userPw); | ||
} | ||
|
||
@Override | ||
public boolean supports(Class<?> authentication) { | ||
return CustomAuthenticationProvider.class.isAssignableFrom(authentication); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,21 +2,29 @@ | |
|
||
import com.jiyunio.todolist.customError.CustomException; | ||
import com.jiyunio.todolist.customError.ErrorCode; | ||
import com.jiyunio.todolist.member.Member; | ||
import com.jiyunio.todolist.member.MemberRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.security.core.userdetails.User; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.stereotype.Service; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
@Slf4j | ||
public class CustomUserDetailsService implements UserDetailsService { | ||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public UserDetails loadUserByUsername(String userId) throws CustomException { | ||
return memberRepository.findByUserId(userId) | ||
.orElseThrow(()-> new CustomException(HttpStatus.UNAUTHORIZED, ErrorCode.NO_AUTHENTICATION_MEMBER)); | ||
Member member = memberRepository.findByUserId(userId) | ||
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. 해당 메서드는 결국 member의 정보를 가져오는 건데 리턴을 할 때는 왜 user.builder로 인스턴스화 해서 보내주는거야? |
||
.orElseThrow(()-> new CustomException(HttpStatus.UNAUTHORIZED, ErrorCode.NOT_EXIST_MEMBER)); | ||
return User.builder() | ||
.username(member.getUserId()) | ||
.password(member.getUserPw()) | ||
.authorities(member.getRole()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,13 @@ | |
@Getter | ||
@Setter | ||
public class JwtDTO { | ||
private String grantType; | ||
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. 여기서의 grantType이 의미하는 바가 뭐야? |
||
private String userId; | ||
private String accessToken; | ||
|
||
@Builder | ||
protected JwtDTO(String userId, String accessToken){ | ||
protected JwtDTO(String grantType, String userId, String accessToken){ | ||
this.grantType = grantType; | ||
this.userId = userId; | ||
this.accessToken = accessToken; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,31 @@ | ||
package com.jiyunio.todolist.jwt; | ||
|
||
import com.jiyunio.todolist.customError.CustomException; | ||
import com.jiyunio.todolist.customError.ErrorCode; | ||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.Jws; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.io.Decoders; | ||
import io.jsonwebtoken.security.Keys; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.crypto.SecretKey; | ||
import java.util.Date; | ||
import java.util.stream.Collectors; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
@Slf4j | ||
public class JwtProvider { | ||
@Value("${spring.jwt.secret}") | ||
private String secretKey; | ||
|
@@ -28,25 +36,37 @@ public SecretKey getSecretKey() { | |
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)); | ||
} | ||
|
||
public JwtDTO createToken(String userId) { | ||
public JwtDTO createToken(Authentication authentication) { | ||
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. 토큰을 만들 때 authentication 객체를 담는 이유가 뭐야? |
||
log.info("createToken 메소드 들어옴"); | ||
Date now = new Date(); | ||
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. Date 타입은 Java 8부터 Deprecated 됐는데 해당 타입을 사용한 이유가 있을까? |
||
String authorities = authentication.getAuthorities().stream() | ||
.map(GrantedAuthority::getAuthority) | ||
.collect(Collectors.joining(",")); | ||
System.out.println(authorities); | ||
String accessToken = Jwts.builder() | ||
.setHeaderParam("alg", "HS256") | ||
.setHeaderParam("typ", "JWT") | ||
.setSubject(userId) | ||
.claim("auth", authorities) | ||
.setSubject(authentication.getName()) | ||
.setIssuedAt(now) | ||
.setExpiration(new Date(now.getTime() + tokenValidTime)) | ||
.signWith(getSecretKey()) | ||
.compact(); | ||
|
||
return JwtDTO.builder() | ||
.userId(userId) | ||
.grantType("Bearer") | ||
.userId(authentication.getName()) | ||
.accessToken(accessToken) | ||
.build(); | ||
} | ||
|
||
//인증 (Authentication) 객체 반환 | ||
public Authentication getAuthentication(String accessToken) { | ||
Claims claims = parseClaims(accessToken); | ||
if (claims.get("auth") == null) { | ||
throw new CustomException(HttpStatus.UNAUTHORIZED, ErrorCode.NOT_EXIST_MEMBER); | ||
} | ||
|
||
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserId(accessToken)); | ||
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); | ||
} | ||
|
@@ -56,7 +76,11 @@ public String getUserId(String accessToken) { | |
} | ||
|
||
public String resolveToken(HttpServletRequest request) { | ||
return request.getHeader("AUTH-TOKEN"); | ||
String token = request.getHeader("Authorization"); | ||
if (token != null && token.startsWith("Bearer")) { | ||
return token.substring(7); | ||
} | ||
return null; | ||
} | ||
|
||
//token 유효기간 검사 | ||
|
@@ -65,7 +89,19 @@ public boolean validationToken(String accessToken) { | |
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(getSecretKey()).build().parseClaimsJws(accessToken); | ||
return !claims.getBody().getExpiration().before(new Date()); | ||
} catch (Exception e) { | ||
return false; | ||
throw new CustomException(HttpStatus.UNAUTHORIZED, ErrorCode.NOT_EXIST_MEMBER); | ||
} | ||
} | ||
|
||
private Claims parseClaims(String accessToken) { | ||
try { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(getSecretKey()) | ||
.build() | ||
.parseClaimsJwt(accessToken) | ||
.getBody(); | ||
} catch (ExpiredJwtException e) { | ||
return e.getClaims(); | ||
} | ||
} | ||
} |
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.
여기서 static class로 빈을 등록한 이유는?
그럴거면 따로 클래스를 만들어도 되지 않아?