-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from kakao-tech-campus-2nd-step3/feature/ISSUE-22
username / jwt 인증 filter 구현
- Loading branch information
Showing
10 changed files
with
388 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
src/main/java/poomasi/domain/auth/config/SecurityBeanGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package poomasi.domain.auth.config; | ||
|
||
import jdk.jfr.Description; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; | ||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; | ||
import poomasi.domain.auth.util.JwtUtil; | ||
|
||
@Configuration | ||
public class SecurityBeanGenerator { | ||
|
||
@Bean | ||
@Description("AuthenticationProvider를 위한 Spring bean") | ||
public PasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
@Bean | ||
@Description("open endpoint를 위한 spring bean") | ||
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { | ||
return new MvcRequestMatcher.Builder(introspector); | ||
} | ||
|
||
@Bean | ||
@Description("jwt 토큰 발급을 위한 spring bean") | ||
JwtUtil jwtProvider() { | ||
return new JwtUtil(); | ||
} | ||
} | ||
|
80 changes: 75 additions & 5 deletions
80
src/main/java/poomasi/domain/auth/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,85 @@ | ||
package poomasi.domain.auth.config; | ||
|
||
import lombok.AllArgsConstructor; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.security.authentication.AuthenticationManager; | ||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; | ||
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; | ||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; | ||
import poomasi.domain.auth.security.filter.CustomLogoutFilter; | ||
import poomasi.domain.auth.security.filter.CustomUsernamePasswordAuthenticationFilter; | ||
import poomasi.domain.auth.security.filter.JwtAuthenticationFilter; | ||
import poomasi.domain.auth.util.JwtUtil; | ||
|
||
@AllArgsConstructor | ||
@Configuration | ||
@EnableWebSecurity | ||
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true) // 인가 처리에 대한 annotation | ||
public class SecurityConfig { | ||
|
||
private final AuthenticationConfiguration authenticationConfiguration; | ||
private final JwtUtil jwtUtil; | ||
private final MvcRequestMatcher.Builder mvc; | ||
|
||
@Bean | ||
public PasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { | ||
return configuration.getAuthenticationManager(); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
|
||
// TODO : 나중에 허용될 endpoint가 많아지면 whiteList로 관리 예정 | ||
// 임시로 GET : [api/farms, api/products, api/login, api/signup, /]은 열어둠 | ||
http.authorizeHttpRequests((authorize) -> authorize | ||
.requestMatchers(HttpMethod.GET, "/api/farms/**").permitAll() | ||
.requestMatchers(HttpMethod.GET, "/api/products/**").permitAll() | ||
.requestMatchers("/api/login", "/", "/api/signup").permitAll() | ||
.anyRequest(). | ||
authenticated() | ||
); | ||
|
||
//csrf 해제 | ||
http.csrf(AbstractHttpConfigurer::disable); | ||
|
||
//cors 해제 | ||
http.cors(AbstractHttpConfigurer::disable); | ||
|
||
//session 해제 -> jwt token 로그인 | ||
http.sessionManagement(session -> session | ||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) | ||
); | ||
|
||
//Oauth2.0 소셜 로그인 필터 구현 | ||
|
||
|
||
//jwt 인증 필터 구현 | ||
http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), CustomUsernamePasswordAuthenticationFilter.class); | ||
|
||
//로그인 filter 구현 | ||
http.addFilterAt(new CustomUsernamePasswordAuthenticationFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); | ||
|
||
//form login disable | ||
http.formLogin(AbstractHttpConfigurer::disable); | ||
|
||
//basic login disable | ||
http.httpBasic(AbstractHttpConfigurer::disable); | ||
|
||
//log out filter 추가 | ||
http.addFilterBefore(new CustomLogoutFilter(), CustomLogoutFilter.class); | ||
return http.build(); | ||
|
||
} | ||
} | ||
} | ||
|
||
|
||
|
||
|
18 changes: 18 additions & 0 deletions
18
src/main/java/poomasi/domain/auth/security/filter/CustomLogoutFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package poomasi.domain.auth.security.filter; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
|
||
public class CustomLogoutFilter extends OncePerRequestFilter { | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
// access token 블랙리스트 저장해야 함 | ||
// refresh token 삭제해야 함 | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
.../java/poomasi/domain/auth/security/filter/CustomUsernamePasswordAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package poomasi.domain.auth.security.filter; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.http.Cookie; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import jdk.jfr.Description; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
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 poomasi.domain.auth.security.userdetail.UserDetailsImpl; | ||
import poomasi.domain.auth.util.JwtUtil; | ||
|
||
@RequiredArgsConstructor | ||
public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { | ||
|
||
private final AuthenticationManager authenticationManager; | ||
private final JwtUtil jwtUtil; | ||
|
||
@Description("인증 시도 메서드") | ||
@Override | ||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { | ||
String username = obtainUsername(request); | ||
String password = obtainPassword(request); | ||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); | ||
return authenticationManager.authenticate(authToken); | ||
} | ||
|
||
@Override | ||
@Description("로그인 성공 시, accessToken과 refreshToken 발급") | ||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { | ||
UserDetailsImpl customUserDetails = (UserDetailsImpl) authentication.getPrincipal(); | ||
String username = customUserDetails.getUsername(); | ||
String role = customUserDetails.getAuthority(); | ||
|
||
String accessToken = jwtUtil.generateAccessToken(username, role); | ||
String refreshToken = jwtUtil.generateRefreshToken(username, role); | ||
|
||
response.setHeader("access", accessToken); | ||
response.addCookie(createCookie("refresh", refreshToken)); | ||
response.setStatus(HttpStatus.OK.value()); | ||
} | ||
|
||
@Override | ||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { | ||
response.setStatus(401); | ||
} | ||
|
||
private Cookie createCookie(String key, String value) { | ||
|
||
Cookie cookie = new Cookie(key, value); | ||
cookie.setMaxAge(24*60*60); | ||
cookie.setHttpOnly(true); | ||
return cookie; | ||
} | ||
|
||
} |
81 changes: 81 additions & 0 deletions
81
src/main/java/poomasi/domain/auth/security/filter/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package poomasi.domain.auth.security.filter; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import jdk.jfr.Description; | ||
import lombok.AllArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
import poomasi.domain.auth.util.JwtUtil; | ||
import poomasi.domain.member.entity.Member; | ||
import poomasi.domain.member.entity.Role; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
|
||
@Description("access token을 검증하는 필터") | ||
@AllArgsConstructor | ||
@Slf4j | ||
public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
private final JwtUtil jwtUtil; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
|
||
String accessToken = request.getHeader("access"); | ||
|
||
// refresh 재발급이나 다른 요청에 대해서 넘어감 | ||
// access <~token~> | ||
if (accessToken == null) { | ||
log.info("access token이 존재하지 않아서 다음 filter로 넘어갑니다."); | ||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
// 만료 검사 | ||
if(jwtUtil.isTokenExpired(accessToken)){ | ||
log.warn("[인증 실패] - 토큰이 만료되었습니다."); | ||
PrintWriter writer = response.getWriter(); | ||
writer.print("만료된 토큰입니다."); | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | ||
} | ||
|
||
// 유효성 검사 | ||
if(jwtUtil.validateToken(accessToken)) { | ||
log.warn("[인증 실패] - 위조된 토큰입니다."); | ||
PrintWriter writer = response.getWriter(); | ||
writer.print("위조된 토큰입니다."); | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | ||
} | ||
|
||
// access token 추출하기 | ||
String tokenType = jwtUtil.getTokenTypeFromToken(accessToken); | ||
|
||
if(!tokenType.equals("access")){ | ||
log.info("[인증 실패] - 위조된 토큰입니다."); | ||
PrintWriter writer = response.getWriter(); | ||
writer.print("위조된 토큰입니다."); | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | ||
} | ||
|
||
String username = jwtUtil.getEmailFromToken(accessToken); | ||
String role = jwtUtil.getRoleFromToken(accessToken); | ||
|
||
//TODO : Object, Object, Collection 형태 ..처리 해야 함 | ||
//TODO : userDetailsImpl(), null(password) | ||
//TODO : security context에 저장해야 함. | ||
Member member = new Member(username, Role.valueOf(role)); | ||
|
||
|
||
|
||
|
||
|
||
|
||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
src/main/java/poomasi/domain/auth/security/userdetail/UserDetailsImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package poomasi.domain.auth.security.userdetail; | ||
|
||
import lombok.Getter; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import poomasi.domain.member.entity.Member; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
||
|
||
|
||
@Getter | ||
public class UserDetailsImpl implements UserDetails { | ||
|
||
//private static final long serialVersionUID = 1L; | ||
private final Member member; | ||
|
||
public UserDetailsImpl(Member member) { | ||
this.member = member; | ||
} | ||
|
||
@Override | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
Collection<GrantedAuthority> collection = new ArrayList<>(); | ||
collection.add(new GrantedAuthority() { | ||
@Override | ||
public String getAuthority() { | ||
return member.getRole() | ||
.name(); | ||
} | ||
}); | ||
|
||
return collection; | ||
} | ||
|
||
public String getAuthority() { | ||
return member.getRole().name(); | ||
} | ||
|
||
@Override | ||
public String getPassword() { | ||
return this.member | ||
.getPassword(); | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return this.member | ||
.getEmail(); | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonLocked() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isCredentialsNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isEnabled() { | ||
return true; | ||
} | ||
|
||
} |
Oops, something went wrong.