Skip to content

Commit

Permalink
Merge pull request #287 from kookmin-sw/feature/284
Browse files Browse the repository at this point in the history
Feat: Login & Join
  • Loading branch information
ancy0 authored May 29, 2024
2 parents cd63bab + fc13dbf commit 2baa4b8
Show file tree
Hide file tree
Showing 17 changed files with 421 additions and 170 deletions.
5 changes: 5 additions & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ dependencies {
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.hibernate:hibernate-spatial:6.4.4.Final'
implementation 'net.nurigo:sdk:4.2.7' //sms 발송 관련 sdk
implementation 'org.springframework.boot:spring-boot-starter-security' // login
implementation 'io.jsonwebtoken:jjwt:0.9.1' // JWT
implementation 'com.sun.xml.bind:jaxb-impl:4.0.1' // com.sun.xml.bind
implementation 'com.sun.xml.bind:jaxb-core:4.0.1' // com.sun.xml.bind
implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359' // javax.xml.bind
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
10 changes: 8 additions & 2 deletions server/src/main/java/com/capstone/server/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public enum ErrorCode {

DATA_INTEGRITY_VALIDATION_ERROR(HttpStatus.BAD_REQUEST, "Invalid request."),

USER_NOT_FOUND(HttpStatus.NOT_FOUND, "user not found by id."),
MISSING_PEOPLE_NOT_FOUND_BY_ID(HttpStatus.NOT_FOUND, "Not found by id."),
RESULT_NOT_FOUND(HttpStatus.NOT_FOUND, "don't exist results"),

Expand All @@ -26,7 +25,14 @@ public enum ErrorCode {
MESSAGE_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "message sending fail"),
AI_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "AI SERVER ERROR"),
INVALID_REQUEST_DATA(HttpStatus.UNPROCESSABLE_ENTITY, "AI server request is wrong"),
CONNECTION_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "AI SERVER is CLOSE");
CONNECTION_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "AI SERVER is CLOSE"),

// Login Error
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "User not found by login id."),
USER_NOT_MATCH_PASSWORD(HttpStatus.UNAUTHORIZED, "Invalid password"),
JWT_CREATE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Can not create JWT token"),
USER_NOT_ADMIN(HttpStatus.UNAUTHORIZED, "User not Admin, Please contact probee team"),
DUPLICATE_USER_LOGIN_ID(HttpStatus.CONFLICT, "Duplicate user login id.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
13 changes: 13 additions & 0 deletions server/src/main/java/com/capstone/server/config/BCryptConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.capstone.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class BCryptConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.capstone.server.config;

import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.capstone.server.model.UserEntity;
import com.capstone.server.service.JwtTokenService;
import com.capstone.server.service.UserService;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;

// OncePerRequestFilter : 매번 들어갈 때 마다 체크 해주는 필터
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {

private final JwtTokenService jwtTokenService;
private final UserService userService;
private final String secretKey;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);

// Header의 Authorization 값이 비어있으면 => Jwt Token을 전송하지 않음 => 로그인 하지 않음
if(authorizationHeader == null) {
filterChain.doFilter(request, response);
return;
}

// Header의 Authorization 값이 'Bearer '로 시작하지 않으면 => 잘못된 토큰
if(!authorizationHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

// 전송받은 값에서 'Bearer ' 뒷부분(Jwt Token) 추출
String token = authorizationHeader.split(" ")[1];

// 전송받은 Jwt Token이 만료되었으면 => 다음 필터 진행(인증 X)
if (jwtTokenService.isExpired(token, secretKey)) {
filterChain.doFilter(request, response);
return;
}

// Jwt Token에서 loginId 추출
String loginId = jwtTokenService.getLoginId(token, secretKey);

// 추출한 loginId로 User 찾아오기
UserEntity loginUser = userService.getLoginUserByLoginId(loginId);

// loginUser 정보로 UsernamePasswordAuthenticationToken 발급
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginUser.getLoginId(), null, List.of(new SimpleGrantedAuthority(loginUser.getRole().name())));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

// 권한 부여
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.capstone.server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.capstone.server.model.enums.UserRole;
import com.capstone.server.service.JwtTokenService;
import com.capstone.server.service.UserService;

import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenService jwtTokenService;
private final UserService userService;

@Value("${jwt.secretKey}")
private String secretKey;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.httpBasic(http -> http.disable())
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JwtTokenFilter(jwtTokenService, userService, secretKey), UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/guardian/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/missing-people").permitAll()
.requestMatchers("/api/user/info/**").authenticated()
.requestMatchers("/api/user/**").permitAll()
.anyRequest().hasAuthority(UserRole.ADMIN.name())
)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.capstone.server.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.capstone.server.dto.login.JoinRequestDto;
import com.capstone.server.dto.login.LoginRequestDto;
import com.capstone.server.dto.login.LoginResponseDto;
import com.capstone.server.model.UserEntity;
import com.capstone.server.response.SuccessResponse;
import com.capstone.server.service.JwtTokenService;
import com.capstone.server.service.UserService;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class JwtLoginApiController {
@Value("${jwt.secretKey}")
private String secretKey;
@Value("${jwt.expireTime}")
private long expireTime;

@Autowired
private UserService userService;
@Autowired
private JwtTokenService jwtTokenService;

@PostMapping("/join")
public ResponseEntity<?> join(@RequestBody JoinRequestDto joinRequestDto) {

// loginId 중복 체크
userService.checkLoginIdDuplicate(joinRequestDto.getLoginId());

// 회원가입
userService.join(joinRequestDto);
return ResponseEntity.ok().body(new SuccessResponse("Join Success"));
}

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequestDto loginRequestDto) {
// 로그인
UserEntity user = userService.login(loginRequestDto);

// Jwt Token 발급
String jwtToken = jwtTokenService.createToken(user.getLoginId(), secretKey, expireTime);

LoginResponseDto loginResponseDto = new LoginResponseDto(user, jwtToken);
return ResponseEntity.ok().body(new SuccessResponse(loginResponseDto));
}

@GetMapping("/info")
public ResponseEntity<?> userInfo(Authentication auth) {
UserEntity loginUser = userService.getLoginUserByLoginId(auth.getName());

return ResponseEntity.ok().body(new SuccessResponse(String.format("loginId : %s, role : %s",
loginUser.getLoginId(), loginUser.getRole().name())));
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.capstone.server.dto.login;

import com.capstone.server.model.UserEntity;
import com.capstone.server.model.enums.UserRole;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class JoinRequestDto {
@NotBlank(message = "아이디가 비었습니다.")
private String loginId;

@NotBlank(message = "비밀번호가 비었습니다.")
private String password;

public UserEntity toEntity(String encodedPassword) {
return UserEntity.builder()
.loginId(this.loginId)
.password(encodedPassword)
.role(UserRole.USER)
.build();
}
}
Loading

0 comments on commit 2baa4b8

Please sign in to comment.