-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
381 additions
and
1 deletion.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
src/main/java/com/cmc/suppin/global/security/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 |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package com.cmc.suppin.global.security; | ||
|
||
import com.cmc.suppin.global.security.jwt.JWTFilter; | ||
import com.cmc.suppin.global.security.jwt.JWTUtil; | ||
import com.cmc.suppin.global.security.jwt.LoginFilter; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.security.authentication.AuthenticationManager; | ||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
import org.springframework.web.cors.CorsConfiguration; | ||
import org.springframework.web.cors.CorsConfigurationSource; | ||
|
||
import java.util.Collections; | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
public class SecurityConfig { | ||
|
||
//AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입 | ||
private final AuthenticationConfiguration authenticationConfiguration; | ||
|
||
//JWTUtil 주입 | ||
private final JWTUtil jwtUtil; | ||
|
||
public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil) { | ||
|
||
this.authenticationConfiguration = authenticationConfiguration; | ||
this.jwtUtil = jwtUtil; | ||
} | ||
|
||
//AuthenticationManager Bean 등록 | ||
@Bean | ||
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { | ||
|
||
return configuration.getAuthenticationManager(); | ||
} | ||
|
||
@Bean | ||
public BCryptPasswordEncoder bCryptPasswordEncoder() { | ||
|
||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
|
||
http | ||
.cors((corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { | ||
|
||
@Override | ||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { | ||
|
||
CorsConfiguration configuration = new CorsConfiguration(); | ||
|
||
configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); | ||
configuration.setAllowedMethods(Collections.singletonList("*")); | ||
configuration.setAllowCredentials(true); | ||
configuration.setAllowedHeaders(Collections.singletonList("*")); | ||
configuration.setMaxAge(3600L); | ||
|
||
configuration.setExposedHeaders(Collections.singletonList("Authorization")); | ||
|
||
return configuration; | ||
} | ||
}))); | ||
|
||
//csrf disable | ||
http | ||
.csrf((auth) -> auth.disable()); | ||
|
||
//From 로그인 방식 disable | ||
http | ||
.formLogin((auth) -> auth.disable()); | ||
|
||
//http basic 인증 방식 disable | ||
http | ||
.httpBasic((auth) -> auth.disable()); | ||
|
||
//경로별 인가 작업 | ||
http | ||
.authorizeHttpRequests((auth) -> auth | ||
.requestMatchers("/login", "/", "/join").permitAll() | ||
.requestMatchers("/admin").hasRole("ADMIN") | ||
.anyRequest().authenticated()); | ||
|
||
//JWTFilter 추가 | ||
http | ||
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class); | ||
|
||
//LoginFilter 추가 LoginFilter()는 인자를 받음 (AuthenticationManager() 메소드에 authenticationConfiguration 객체를 넣어야 함) 따라서 등록 필요 | ||
http | ||
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); | ||
|
||
//세션 설정 | ||
http | ||
.sessionManagement((session) -> session | ||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); | ||
|
||
|
||
return http.build(); | ||
} | ||
} | ||
|
74 changes: 74 additions & 0 deletions
74
src/main/java/com/cmc/suppin/global/security/jwt/JWTFilter.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,74 @@ | ||
package com.cmc.suppin.global.security.jwt; | ||
|
||
import com.cmc.suppin.member.controller.dto.MemberDetails; | ||
import com.cmc.suppin.member.domain.Member; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
|
||
public class JWTFilter extends OncePerRequestFilter { | ||
|
||
private final JWTUtil jwtUtil; | ||
|
||
public JWTFilter(JWTUtil jwtUtil) { | ||
this.jwtUtil = jwtUtil; | ||
} | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
|
||
//request에서 Authorization 헤더를 찾음 | ||
String authorization = request.getHeader("Authorization"); | ||
|
||
//Authorization 헤더 검증(토큰이 없는 경우 처리해주는 부분) | ||
if (authorization == null || !authorization.startsWith("Bearer ")) { | ||
|
||
System.out.println("token null"); | ||
filterChain.doFilter(request, response); | ||
|
||
//조건이 해당되면 메소드 종료 (필수) | ||
return; | ||
} | ||
|
||
//Bearer 부분 제거 후 순수 토큰만 획득 | ||
String token = authorization.split(" ")[1]; | ||
|
||
//토큰 소멸 시간 검증(토큰 만료시 처리해주는 부분) | ||
if (jwtUtil.isExpired(token)) { | ||
|
||
System.out.println("token expired"); | ||
filterChain.doFilter(request, response); | ||
|
||
//조건이 해당되면 메소드 종료 (필수) | ||
return; | ||
} | ||
|
||
//토큰에서 username과 role 획득 | ||
String username = jwtUtil.getUsername(token); | ||
String role = jwtUtil.getRole(token); | ||
|
||
// Member Entity를 생성하여 값 set | ||
Member member = new Member(); | ||
member.setName(username); | ||
member.setPassword("tempPassword"); | ||
member.setRole(role); | ||
|
||
//MemberDetails에 회원 정보 객체 담기 | ||
MemberDetails customUserDetails = new MemberDetails(member); | ||
|
||
//스프링 시큐리티 인증 토큰 생성 | ||
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); | ||
//세션에 사용자 등록 | ||
SecurityContextHolder.getContext().setAuthentication(authToken); | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
} | ||
|
48 changes: 48 additions & 0 deletions
48
src/main/java/com/cmc/suppin/global/security/jwt/JWTUtil.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,48 @@ | ||
package com.cmc.suppin.global.security.jwt; | ||
|
||
import io.jsonwebtoken.Jwts; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.crypto.SecretKey; | ||
import javax.crypto.spec.SecretKeySpec; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Date; | ||
|
||
@Component | ||
public class JWTUtil { | ||
|
||
private SecretKey secretKey; | ||
|
||
public JWTUtil(@Value("${JWT_TOKEN_SECRET}") String secret) { | ||
|
||
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm()); | ||
} | ||
|
||
public String getUsername(String token) { | ||
|
||
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class); | ||
} | ||
|
||
public String getRole(String token) { | ||
|
||
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class); | ||
} | ||
|
||
public Boolean isExpired(String token) { | ||
|
||
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date()); | ||
} | ||
|
||
public String createJwt(String username, String role, Long expiredMs) { | ||
|
||
return Jwts.builder() | ||
.claim("username", username) | ||
.claim("role", role) | ||
.issuedAt(new Date(System.currentTimeMillis())) | ||
.expiration(new Date(System.currentTimeMillis() + expiredMs)) | ||
.signWith(secretKey) | ||
.compact(); | ||
} | ||
} | ||
|
77 changes: 77 additions & 0 deletions
77
src/main/java/com/cmc/suppin/global/security/jwt/LoginFilter.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,77 @@ | ||
package com.cmc.suppin.global.security.jwt; | ||
|
||
import com.cmc.suppin.member.controller.dto.MemberDetails; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
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.core.GrantedAuthority; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
import java.util.Collection; | ||
import java.util.Iterator; | ||
|
||
public class LoginFilter extends UsernamePasswordAuthenticationFilter { | ||
|
||
private final AuthenticationManager authenticationManager; | ||
|
||
private final JWTUtil jwtUtil; | ||
|
||
public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) { | ||
|
||
this.authenticationManager = authenticationManager; | ||
this.jwtUtil = jwtUtil; | ||
} | ||
|
||
@Override | ||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { | ||
|
||
//클라이언트 요청에서 username, password 추출 | ||
String username = obtainUsername(request); | ||
String password = obtainPassword(request); | ||
|
||
//스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함 | ||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null); | ||
|
||
//token에 담은 검증을 위한 AuthenticationManager로 전달 | ||
return authenticationManager.authenticate(authToken); | ||
} | ||
|
||
//로그인 성공시 실행하는 메소드 (여기서 JWT를 발급됨) | ||
@Override | ||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) { | ||
|
||
MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal(); | ||
|
||
String username = memberDetails.getUsername(); | ||
|
||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); | ||
if (authorities.isEmpty()) { | ||
throw new IllegalStateException("권한이 없습니다."); | ||
} | ||
|
||
Iterator<? extends GrantedAuthority> iterator = authorities.iterator(); | ||
GrantedAuthority auth = iterator.next(); | ||
|
||
String role = auth.getAuthority(); | ||
|
||
long expirationTime = 1000 * 60 * 60 * 24 * 7; // 7일 | ||
|
||
String token = jwtUtil.createJwt(username, role, expirationTime); | ||
|
||
response.addHeader("Authorization", "Bearer " + token); | ||
} | ||
|
||
|
||
//로그인 실패시 실행하는 메소드 | ||
@Override | ||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) { | ||
|
||
//로그인 실패시 401 응답코드 반환 | ||
response.setStatus(401); | ||
} | ||
} | ||
|
62 changes: 62 additions & 0 deletions
62
src/main/java/com/cmc/suppin/member/controller/dto/MemberDetails.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,62 @@ | ||
package com.cmc.suppin.member.controller.dto; | ||
|
||
import com.cmc.suppin.member.domain.Member; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
|
||
public class MemberDetails implements UserDetails { | ||
|
||
private final Member member; | ||
|
||
public MemberDetails(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(); | ||
} | ||
}); | ||
|
||
return collection; | ||
} | ||
|
||
@Override | ||
public String getPassword() { | ||
return member.getPassword(); | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return member.getName(); | ||
} | ||
|
||
@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.