-
Notifications
You must be signed in to change notification settings - Fork 15
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
[BE] 이지훈 로그인 #9
base: main
Are you sure you want to change the base?
[BE] 이지훈 로그인 #9
Changes from 8 commits
9fff636
fc3ca0f
f3eb376
0e25906
7ceca0b
f31fe56
b524ca5
02aaf8c
58270e9
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 |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package config; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
import io.jsonwebtoken.SignatureException; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
|
||
@Component | ||
public class JwtFilter extends OncePerRequestFilter { | ||
|
||
@Autowired | ||
private JwtUtil jwtUtil; | ||
|
||
@Autowired | ||
private UserDetailsService userDetailsService; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||
throws ServletException, IOException { | ||
final String authorizationHeader = request.getHeader("Authorization"); | ||
|
||
String username = null; | ||
String jwt = null; | ||
|
||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { | ||
jwt = authorizationHeader.substring(7); | ||
try { | ||
username = jwtUtil.extractUsername(jwt); | ||
} catch (ExpiredJwtException | IllegalArgumentException | MalformedJwtException | SignatureException e) { | ||
// log the exception or handle it in some way | ||
System.out.println("JWT token is not valid"); | ||
} | ||
} | ||
|
||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { | ||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); | ||
|
||
if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { | ||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( | ||
userDetails, null, userDetails.getAuthorities()); | ||
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); | ||
} | ||
} | ||
chain.doFilter(request, response); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package config; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.util.Date; | ||
import java.util.function.Function; | ||
|
||
@Component | ||
public class JwtUtil { | ||
|
||
@Value("${jwt.secret}") | ||
private String secret; | ||
|
||
public String extractUsername(String token) { | ||
return extractClaim(token, Claims::getSubject); | ||
} | ||
|
||
public Date extractExpiration(String token) { | ||
return extractClaim(token, Claims::getExpiration); | ||
} | ||
|
||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { | ||
final Claims claims = extractAllClaims(token); | ||
return claimsResolver.apply(claims); | ||
} | ||
|
||
private Claims extractAllClaims(String token) { | ||
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); | ||
} | ||
|
||
private Boolean isTokenExpired(String token) { | ||
return extractExpiration(token).before(new Date()); | ||
} | ||
|
||
public String generateToken(String username) { | ||
return Jwts.builder() | ||
.setSubject(userDetails.getUsername()) | ||
.setIssuedAt(new Date(System.currentTimeMillis())) | ||
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) | ||
.signWith(SignatureAlgorithm.HS256, secret) | ||
.compact(); | ||
} | ||
|
||
public Boolean validateToken(String token, String username) { | ||
final String extractedUsername = extractUsername(token); | ||
return (extractedUsername.equals(username) && !isTokenExpired(token)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package config; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
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.annotation.web.configurers.AbstractHttpConfigurer; | ||
import org.springframework.security.config.http.SessionCreationPolicy; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
@Configuration | ||
@RequiredArgsConstructor | ||
@EnableWebSecurity | ||
public class SecurityConfig { | ||
|
||
private final JwtFilter jwtFilter; | ||
|
||
|
||
@Bean | ||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
http | ||
.formLogin(AbstractHttpConfigurer::disable) | ||
.httpBasic(AbstractHttpConfigurer::disable) | ||
.csrf(AbstractHttpConfigurer::disable) | ||
.sessionManagement((sessionManagement)-> | ||
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); | ||
|
||
http.authorizeHttpRequests((authorize) -> authorize. | ||
requestMatchers("/**").permitAll() | ||
.anyRequest().permitAll()); | ||
|
||
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. 현재 모든 경로가 permitAll()로 열려있는거 같아요! 요구사항에 맞게 사용자 인증이 필요한 요청에는 인증이 요구되게 변경해주시면 좋을 거 같아요! 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. 2024-05-30 20:58 경로 login, register 사용자 허용 수정했습니다 ! |
||
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); | ||
return http.build(); | ||
} | ||
|
||
@Bean | ||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { | ||
return authenticationConfiguration.getAuthenticationManager(); | ||
} | ||
|
||
@Bean | ||
public PasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package config;public class TokenProvider { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package controller; | ||
|
||
import config.JwtUtil; | ||
import dto.AuthRequest; | ||
import dto.AuthResponse; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.authentication.AuthenticationManager; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequestMapping("/auth") | ||
public class AuthController { | ||
|
||
@Autowired | ||
private AuthenticationManager authenticationManager; | ||
|
||
@Autowired | ||
private JwtUtil jwtUtil; | ||
|
||
@Autowired | ||
private UserDetailsService userDetailsService; | ||
|
||
@PostMapping("/login") | ||
public ResponseEntity<AuthResponse> login(@RequestBody AuthRequest authRequest) { | ||
try { | ||
authenticationManager.authenticate( | ||
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) | ||
); | ||
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. 마찬가지로 컨트롤러에서 인증하는 로직이 돌아가는 것 보단 서비스 단에서 돌아가는게 어떨까요? |
||
} catch (AuthenticationException e) { | ||
return ResponseEntity.status(401).build(); | ||
} | ||
|
||
final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); | ||
final String jwt = jwtUtil.generateToken(userDetails); | ||
|
||
return ResponseEntity.ok(new AuthResponse(jwt)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package controller.UserController; | ||
|
||
import dto.RegisterRequest; | ||
import dto.AuthResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.authentication.AuthenticationManager; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import service.UserService; | ||
import config.JwtUtil; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class UserController { | ||
private final UserService userService; | ||
private final AuthenticationManager authenticationManager; | ||
private final JwtUtil jwtUtil; | ||
private final UserDetailsService userDetailsService; | ||
|
||
@PostMapping("/register") | ||
public ResponseEntity<AuthResponse> register(@RequestBody RegisterRequest registerRequest) { | ||
userService.register(registerRequest); | ||
authenticationManager.authenticate( | ||
new UsernamePasswordAuthenticationToken(registerRequest.getUserId(), registerRequest.getPassword()) | ||
); | ||
final var userDetails = userDetailsService.loadUserByUsername(registerRequest.getUserId()); | ||
final var jwt = jwtUtil.generateToken(userDetails.getUsername()); | ||
return ResponseEntity.ok(new AuthResponse(jwt)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package domain; | ||
|
||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
|
||
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. Lombok의 어노테이션을 사용하면 코드가 간략해져서 추천 드려요! |
||
@Entity | ||
public class User { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
private String username; | ||
private String password; | ||
|
||
public Long getId() { | ||
return id; | ||
} | ||
|
||
public void setId(Long id) { | ||
this.id = id; | ||
} | ||
|
||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
public void setUsername(String username) { | ||
this.username = username; | ||
} | ||
|
||
public String getPassword() { | ||
return password; | ||
} | ||
|
||
public void setPassword(String password) { | ||
this.password = password; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package dto; | ||
|
||
public class AuthRequest { | ||
private String username; | ||
private String password; | ||
|
||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
public void setUsername(String username) { | ||
this.username = username; | ||
} | ||
|
||
public String getPassword() { | ||
return password; | ||
} | ||
|
||
public void setPassword(String password) { | ||
this.password = password; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package dto; | ||
|
||
public class AuthResponse { | ||
private String jwt; | ||
|
||
public AuthResponse(String jwt) { | ||
this.jwt = jwt; | ||
} | ||
|
||
public String getJwt() { | ||
return jwt; | ||
} | ||
|
||
public void setJwt(String jwt) { | ||
this.jwt = jwt; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package dto;public class Registerrequest { | ||
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. DTO명을 request와 response로 작성하신건 좋은 거 같아요! |
||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package repository; | ||
import domain.User; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface UserRepository extends JpaRepository<User, Long> { | ||
Optional<User> findByUsername(String username); | ||
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. Optional로 반환을 받는 것도 좋아요! |
||
} |
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.
UsernamePasswordAuthenticationToken을 이용해서 유저 인증을 하는 로직을 필터가 아니라 서비스 단에서 Login() 메소드안에 구현해보는건 어떨까요?