Skip to content
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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,25 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.security:spring-security-test'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
}


tasks.named('test') {
useJUnitPlatform()
}
60 changes: 60 additions & 0 deletions src/main/java/config/JwtFilter.java
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UsernamePasswordAuthenticationToken을 이용해서 유저 인증을 하는 로직을 필터가 아니라 서비스 단에서 Login() 메소드안에 구현해보는건 어떨까요?

}
}
chain.doFilter(request, response);
}
}
52 changes: 52 additions & 0 deletions src/main/java/config/JwtUtil.java
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));
}
}
51 changes: 51 additions & 0 deletions src/main/java/config/SecurityConfig.java
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());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 모든 경로가 permitAll()로 열려있는거 같아요! 요구사항에 맞게 사용자 인증이 필요한 요청에는 인증이 요구되게 변경해주시면 좋을 거 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The 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();
}
}
2 changes: 2 additions & 0 deletions src/main/java/config/TokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package config;public class TokenProvider {
}
43 changes: 43 additions & 0 deletions src/main/java/controller/AuthController.java
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())
);
Copy link
Member

Choose a reason for hiding this comment

The 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));
}
}
34 changes: 34 additions & 0 deletions src/main/java/controller/UserController.java
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));
}
}
39 changes: 39 additions & 0 deletions src/main/java/domain/User.java
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;

Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
22 changes: 22 additions & 0 deletions src/main/java/dto/AuthRequest.java
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;
}
}
17 changes: 17 additions & 0 deletions src/main/java/dto/AuthResponse.java
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;
}
}
2 changes: 2 additions & 0 deletions src/main/java/dto/RegisterRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package dto;public class Registerrequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO명을 request와 response로 작성하신건 좋은 거 같아요!

}
13 changes: 0 additions & 13 deletions src/main/java/leets/attendance/AttendanceApplication.java

This file was deleted.

9 changes: 9 additions & 0 deletions src/main/java/repository/UserRepository.java
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional로 반환을 받는 것도 좋아요!

}
Loading