Skip to content

Commit

Permalink
Merge pull request #11 from TRIP-Side-Project/dev
Browse files Browse the repository at this point in the history
Dockerfile + workflow 생성 및 작성
  • Loading branch information
gkfktkrh153 authored Dec 3, 2023
2 parents 27e7841 + 520c75d commit fc99831
Show file tree
Hide file tree
Showing 16 changed files with 465 additions and 5 deletions.
63 changes: 63 additions & 0 deletions .github/workflows/work.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: 'work'

on:
push:
branches:
- 'main'

jobs:
build:
name: 이미지 빌드 및 도커허브 푸시
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && github.base_ref == 'main'
needs: update
steps:
- uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: application-prod.yml 생성
env:
ACTIONS_STEP_DEBUG: true
APPLICATION_PROD: ${{ secrets.APPLICATION_PROD_YML }}
run: echo "$APPLICATION_PROD" > src/main/resources/application-prod.yml
- name: jdk 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'gradle'
- name: gradlew 실행 권한 부여
run: chmod +x gradlew
- name: gradle 빌드
run: ./gradlew build --no-daemon
- name: build 폴더를 캐시에 저장
uses: actions/upload-artifact@v3
with:
name: build-artifact
path: build
retention-days: 1
- name: 도커 이미지 빌드 및 푸시
run: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
docker build -t ${{ secrets.DOCKER_REPO }}/trip .
docker push ${{ secrets.DOCKER_REPO }}/trip
deploy:
name: 원격 서버에 배포
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true && github.base_ref == 'main'
needs: build
steps:
- name: 원격 서버에 배포하기
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
password: ${{ secrets.SSH_PASSWORD }}
port: 2222
script: |
docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }}
docker stop trip_app || true
docker rm trip_app || true
docker pull ${{ secrets.DOCKER_REPO }}/trip
docker run --name=trip_app --restart unless-stopped \
-p 80:8080 -e TZ=Asia/Seoul -d ${{ secrets.DOCKER_REPO }}/trip
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM openjdk:17-jdk-alpine
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","-Dspring.profiles.active=prod","/app.jar"]
19 changes: 16 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,31 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

compileOnly 'org.projectlombok:lombok'

// mysql
runtimeOnly 'com.mysql:mysql-connector-j'

// h2
runtimeOnly 'com.h2database:h2'

// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.projectlombok:lombok'

// Jwt
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
}

tasks.named('test') {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/api/trip/common/security/JwtToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.api.trip.common.security;


import lombok.Getter;

@Getter
public class JwtToken {
private String accessToken;
private String refreshToken;

public JwtToken(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/api/trip/common/security/JwtTokenFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.api.trip.common.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@RequiredArgsConstructor
@Slf4j
public class JwtTokenFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

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


if (jwtTokenProvider.validateAccessToken(accessToken)) { // 토큰이 유효한 경우 and 로그인 상태
Authentication authentication = jwtTokenProvider.getAuthenticationByAccessToken(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication); // 세션설정
}

filterChain.doFilter(request, response);
}
}
96 changes: 96 additions & 0 deletions src/main/java/com/api/trip/common/security/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.api.trip.common.security;


import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtTokenProvider {
/**
* JWT 생성, 검증, 변환
*/

@Value("${custom.jwt.token.access-expiration-time}")
private long accessExpirationTime;

private final Key key;

@Autowired
public JwtTokenProvider(@Value("${custom.jwt.token.secret}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

public JwtToken createJwtToken(String email, String authorities) {

Date expirationTime = new Date(System.currentTimeMillis() + accessExpirationTime);
Claims claims = Jwts.claims().setSubject(email);
claims.put("roles", authorities); //

String accessToken = Jwts.builder()
.setClaims(claims) // 아이디, 권한정보
.setExpiration(expirationTime) // 만료기간
.signWith(SignatureAlgorithm.HS256, key)
.compact();

return new JwtToken(accessToken, "refreshToken");
}

public boolean validateAccessToken(String accessToken) {
try {
parseToken(accessToken);
return true;
}
catch (final JwtException | IllegalArgumentException exception) {
return false;
}
}

private Claims parseToken(final String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token).getBody();
}

public Authentication getAuthenticationByAccessToken(String accessToken) {

Claims claims = parseToken(accessToken);

/**
* TODO 예외처리
* if (claims.get("roles") == null)
* 권한정보 없을 떄 예외처리
*/


Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("roles").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

UserDetails principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.api.trip.common.security;

import com.api.trip.domain.member.repository.MemberRepository;
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;

@RequiredArgsConstructor
@Service
public class MemberSecurityService implements UserDetailsService {

private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return memberRepository.findByEmail(email).orElseThrow();
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/api/trip/common/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.api.trip.common.security;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;


@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
return http
.authorizeHttpRequests(
auth -> auth
.requestMatchers("/api/members/login", "/api/members/join").permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.anyRequest().authenticated()

)
.csrf(csrf -> csrf.ignoringRequestMatchers(PathRequest.toH2Console()).disable())
.cors(cors -> cors.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
.build();
}




@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.api.trip.domain.member.controller;

import com.api.trip.domain.member.controller.dto.JoinRequest;
import com.api.trip.domain.member.controller.dto.LoginRequest;
import com.api.trip.domain.member.controller.dto.LoginResponse;
import com.api.trip.domain.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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;

Expand All @@ -11,4 +17,16 @@
public class MemberController {

private final MemberService memberService;

@PostMapping("/join")
public ResponseEntity<Void> joinMember(@RequestBody JoinRequest joinRequest){
memberService.join(joinRequest);
return ResponseEntity.ok().build();
}

@PostMapping("/login")
public ResponseEntity<LoginResponse> loginMember(@RequestBody LoginRequest loginRequest){
LoginResponse loginResponse = memberService.login(loginRequest);
return ResponseEntity.ok().body(loginResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.api.trip.domain.member.controller.dto;

import com.api.trip.domain.member.model.Member;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
public class JoinRequest {

private String email;
private String password;
private String nickname;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.api.trip.domain.member.controller.dto;

import lombok.Getter;

@Getter
public class LoginRequest {
private String email;
private String password;
}
Loading

0 comments on commit fc99831

Please sign in to comment.