Skip to content

Commit

Permalink
Merge pull request GDSC-snowflowerthon#16 from ri-naa/main
Browse files Browse the repository at this point in the history
[fix] 수신지역명, 재해구분명 추가 GDSC-snowflowerthon#3
  • Loading branch information
ri-naa authored Jan 10, 2024
2 parents 8d45875 + 1c79779 commit 9ab087a
Show file tree
Hide file tree
Showing 20 changed files with 636 additions and 38 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: AL-Go server CI/CD

on:
push:
branches: [ main ]

jobs:
SSH:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: ssh to ec2
uses: appleboy/ssh-action@master
with:
key: ${{ secrets.SSH_PRIVATE_KEY }}
host: ${{ secrets.HOST }}
username: ${{ secrets.USER }}
script: |
cd /home/ec2-user/apps/step1
sh deploy.sh
nohup java -jar wow_server-0.0.1-SNAPSHOT.jar > nohup.out 2> nohup.err < /dev/null &
11 changes: 10 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,20 @@ repositories {
}

dependencies {
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// JWT Token
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

//JSON parser
implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'

implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ALGo.ALGo_server.authentication.Controller;

import com.ALGo.ALGo_server.authentication.Dto.JoinRequestDto;
import com.ALGo.ALGo_server.authentication.Dto.LoginRequestDto;
import com.ALGo.ALGo_server.authentication.Jwt.JwtTokenProvider;
import com.ALGo.ALGo_server.authentication.Service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

@PostMapping("/join")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity join(@RequestBody JoinRequestDto requestDto){
userService.join(requestDto);
return new ResponseEntity(HttpStatus.OK);
}

@PostMapping("/login")
@ResponseStatus(HttpStatus.OK)
public ResponseEntity login(@RequestBody LoginRequestDto requestDto){
return new ResponseEntity(userService.login(requestDto), HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.ALGo.ALGo_server.authentication.Dto;

import com.ALGo.ALGo_server.entity.Role;
import com.ALGo.ALGo_server.entity.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JoinRequestDto {

private String email;
private String password;
private String language;
private String area;
private Role role;

@Builder
public User toEntity(){
return User.builder()
.email(email)
.password(password)
.language(language)
.area(area)
.role(Role.ROLE_USER)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ALGo.ALGo_server.authentication.Dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginRequestDto {
private String email;
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ALGo.ALGo_server.authentication.Dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenResponseDto {
private String grantType;
private String jwtAccessToken;
private String jwtRefreshToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.ALGo.ALGo_server.authentication.Jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

//token의 인증 정보를 검사한 후 security context에 저장
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtAuthenticationProvider;

public JwtAuthenticationFilter(JwtTokenProvider provider){
this.jwtAuthenticationProvider = provider;
}

/**
* 요청이 들어올 때마다 실행되는 메서드로,
* 요청의 헤더에서 JWT 토큰을 추출하여 유효성을 검사하고,
* 유효한 토큰이면 인증 정보를 추출하여 현재 보안 컨텍스트에 설정
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtAuthenticationProvider.resolveAccessToken(request); //request 헤더에서 토큰 가져옴

if (token != null && jwtAuthenticationProvider.validAccessToken(token)){
//유효한 토큰이면 jwtTokenProvider를 통해 Authentication 객체를 생성
Authentication authentication = jwtAuthenticationProvider.getAuthentication(token);

//현재 스레드의 security context에 인증 정보를 저장 -> 해당 요청을 처리하는 동안 인증된 사용자로서 권한이 부여
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.ALGo.ALGo_server.authentication.Jwt;

import com.ALGo.ALGo_server.authentication.Service.CustomUserDetailsService;
import io.jsonwebtoken.*;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Base64;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
private static final String AUTHORITIES_KEY = "auth";
private static final String BEARER_TYPE = "BEARER";
private final Long ACCESS_TOKEN_EXPIRE_TIME = 10000L * 60 * 60;
private final Long REFRESH_TOKEN_EXPIRE_TIME = 10000L * 60 * 60 * 24 * 7;

private final CustomUserDetailsService customUserDetailsService;

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

//객체 초기화, secretKey를 Base64로 인코딩
@PostConstruct
protected void init(){
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}

//JWT AccessToken 생성
public String createAccessToken(String email, String role){
Claims claims = Jwts.claims().setSubject(email); //JWT payload에 저장되는 정보 단위
claims.put("role", role); //정보는 key-value 쌍으로 저장
Date now = new Date();

return Jwts.builder()
.setClaims(claims) //정보 저장
.setIssuedAt(now) //토큰 발행 시간 정보
.setExpiration(new Date(now.getTime() + ACCESS_TOKEN_EXPIRE_TIME)) //만료 시간 설정
.signWith(SignatureAlgorithm.HS512, secretKey) //사용할 암호화 알고리즘, signature에 들어갈 secret 값 세팅
.compact();
}

//Refresh Token 생성
public String createRefreshToken(){
Date now = new Date();
return Jwts.builder()
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}


//토큰에서 회원 정보(이메일) 추출
public String getUserEmail(String token){
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}

//JWT 토큰에서 인증 정보 조회 - DB 조회 1번 일어남
public Authentication getAuthentication(String token){
UserDetails userDetails = customUserDetailsService.loadUserByUsername(this.getUserEmail(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

//Request Header에서 token값 가져오기. "X-ACCESS-TOKEN" : "TOKEN 값"
public String resolveAccessToken(HttpServletRequest request){
return request.getHeader("X-ACCESS-TOKEN");
}

public String resolveRefreshToken(HttpServletRequest request){
return request.getHeader("X-REFRESH-TOKEN");
}

//Access Token의 만료일자 가져오기
public Long getAccessTokenExpiration(String accessToken){
Date expiration = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(accessToken).getBody().getExpiration();
Long now = new Date().getTime();
return (expiration.getTime() - now);
}

//Access Token의 유효성 + 만료일자 확인
public boolean validAccessToken(String accessToken){
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(accessToken);
return !claims.getBody().getExpiration().before(new Date());
}catch (Exception e){
return false;
}
}

//RefreshToken의 유효성 + 만료일자 확인
public boolean validRefreshToken(String refreshToken){
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(refreshToken);
return !claims.getBody().getExpiration().before(new Date());
}catch (ExpiredJwtException e){
return true;
}catch (Exception e){
return false;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ALGo.ALGo_server.authentication.Service;

import com.ALGo.ALGo_server.entity.User;
import com.ALGo.ALGo_server.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return (UserDetails) userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
}
}
Loading

0 comments on commit 9ab087a

Please sign in to comment.