연습

api서버 & 람다 추가

atteri 2025. 8. 19. 00:31

백은 스프링부트에 시큐리티랑 jpa를 붙이긴할거다.

 

일단 테스트만 할거니

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

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                .csrf(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                ;


        return http.build();
    }

}

 

비활성만 하고

 

server:
  port: 8081
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true


spring:
  application:
    name: board

  datasource:
    url: jdbc:log4jdbc:mysql://${DBHOST}:${DBPORT}/${DB}
    username: ${USERNAME}
    password: ${PASSWORD}
    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    hikari:
      maximum-pool-size: 3
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        show-sql: false
        format_sql: false

logging:
  level:
    com:
      zaxxer:
        hikari: ERROR
    javax:
      sql:
        DataSource: OFF
    jdbc:
      audit: OFF
      resultset: OFF
      result settable: info
      sql only: false
      sliding: OFF
      connection: OFF
    org:
      hibernate:
        SQL: ERROR
        type:
          descriptor:
            sql:
              BasicBinder: OFF

common:
  jwt:
    secret: ${SECRET}
    issuer: ${ISSUER}

 

이거도 자동배포 시킬거니 환경변수에 다 입력하고

환경변수는 인텔리제이 기준

옵션수정 누르면 나오더라

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idx")
    private int idx;

    @Column(name = "user_id", unique = true)
    private String userId;

    @Column(name = "password")
    private String password;

}

 

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
    

}

 

연결만 확인하고

 

@Auth어노테이션을 만들어

controller에 @Auth가 붙으면 필수 @("isOptional=true") 이면 인증이 필수는아닌거로 구분

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auth {

    public boolean isOptional() default false;
}

 

@Slf4j
@Component
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {

    private final JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (!(handler instanceof HandlerMethod handlerMethod)) {
            return true;
        }

        Auth auth = handlerMethod.getMethodAnnotation(Auth.class);

        if(auth != null) {

            String authToken = request.getHeader("Authorization");
            if (StringUtils.hasText(authToken) && authToken.startsWith("Bearer ")) {
                authToken = authToken.substring(7);
            }

            if(!StringUtils.hasText(authToken)){
                if(!auth.isOptional()){
                    sendErrorResponse(response,new ResponseDTO(HttpStatus.FORBIDDEN, ErrorConst.REQUIRED_AUTH.getCode(), ErrorConst.REQUIRED_AUTH.getMessage()));
                    return false;
                }
            }
            else{
                ResponseDTO dto = jwtUtil.validateToken(authToken, request);
                if(!dto.isResultCode()){
                    sendErrorResponse(response,dto);
                    return false;
                }
            }

        }




        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    private void sendErrorResponse(HttpServletResponse response, ResponseDTO dto) throws IOException {
        response.setStatus(dto.getErrorCode());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.getWriter().write(new ObjectMapper().writeValueAsString(dto));

    }

}

 

public class ResponseDTO<T> {
    boolean resultCode   = true;
    String message      = "성공";
    T object;
    int errorCode = 0;

    public ResponseDTO(T object) {
        this.object = object;
    }

    public ResponseDTO(HttpStatus status, T object) {
        this.resultCode = false;
        this.message = "실패";
        this.object = object;
        this.errorCode = status.value();
    }

    public ResponseDTO(HttpStatus status, T object, String message) {
        this.resultCode = false;
        this.message = message;
        this.object = object;
        this.errorCode = status.value();
    }


}

 

@Component
public class JwtUtil {

    @Value("${common.jwt.secret}")
    String SECRET;

    public ResponseDTO validateToken(String token, HttpServletRequest request) {

        SecretKey key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));

        try {
            // Bearer 접두사 제거
            if (token.startsWith("Bearer ")) {
                token = token.substring(7);
            }

            // JWT 파싱 및 검증
            Claims claims = Jwts.parser()
                    .verifyWith(key)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();

            // 토큰 유효성 검사
            Date expiration = claims.getExpiration();
            if (expiration.before(new Date())) {
                return new ResponseDTO(HttpStatus.UNAUTHORIZED,ErrorConst.EXPRIED_TOKEN.getCode(),ErrorConst.EXPRIED_TOKEN.getMessage());
            }

            // 발행자(issuer) 검증
            String issuer = claims.getIssuer();
            if (!"board-auth".equals(issuer)) {
                return new ResponseDTO(HttpStatus.UNAUTHORIZED,ErrorConst.INVALID_TOKEN.getCode(),ErrorConst.INVALID_TOKEN.getMessage());
            }

            LoginDTO dto = new LoginDTO();
            dto.setIdx((Integer) claims.get("idx"));
            dto.setUserId((String) claims.get("user_id"));

            ArrayList<SimpleGrantedAuthority> authList = new ArrayList<>();
            authList.add(new SimpleGrantedAuthority("ROLE_" + "USER"));

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(dto, dto.getPassword(), authList);
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);

            return new ResponseDTO();

        } catch (ExpiredJwtException e) {
            return new ResponseDTO(HttpStatus.UNAUTHORIZED,ErrorConst.EXPRIED_TOKEN.getCode(),ErrorConst.EXPRIED_TOKEN.getMessage());
        } catch (UnsupportedJwtException | MalformedJwtException | SecurityException | IllegalArgumentException e) {
            return new ResponseDTO(HttpStatus.UNAUTHORIZED,ErrorConst.INVALID_TOKEN.getCode(),ErrorConst.INVALID_TOKEN.getMessage());
        } catch (Exception e) {
            return new ResponseDTO(HttpStatus.INTERNAL_SERVER_ERROR,ErrorConst.UNKNOWN_ERROR.getCode(),ErrorConst.UNKNOWN_ERROR.getMessage());
        }
    }


}

일단 필요한거 만들었고

 

누가바로 테스트인 컨트롤러

@RestController
@RequiredArgsConstructor
@Slf4j
public class AuthController {

    private final UserService userService;

    @Auth
    @GetMapping("/test")
    public String loginCheck() {
        return "test";
    }

    @Auth(isOptional = true)
    @GetMapping("/test2")
    public String loginCheck2() {
        return "test2";
    }

    @GetMapping("/test3")
    public String loginCheck3() {
        return "test3";
    }


}

 

토큰없이

 

토큰포함

 

옵셔널에도 토큰없이 

 

근데 람다에 갱신을 안만들었구나

 

 

RefreshFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: auth/
    Handler: app.refresh
    Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref DYNAMODB
    Events:
      Login:
        Type: Api
        Properties:
          Path: /refresh
          Method: post
LogoutFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: auth/
    Handler: app.logout
    Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref DYNAMODB
    Events:
      Login:
        Type: Api
        Properties:
          Path: /logout
          Method: post

dev.yaml

 

export const refresh = async (event) => {
    const body = JSON.parse(event.body)
    const refreshToken = body.refreshToken;
    let connection = await createConnection();

    try {
        const decoded = jwt.verify(refreshToken, process.env.SECRET, {issuer: process.env.ISSUER});
        console.log(decoded)
        if (decoded.idx) {
            try {
                const dynamoDbSelectParams = {
                    TableName: dynamoDbTable,
                    Key: {
                        "user_idx": decoded.idx,
                        "refresh_token": refreshToken
                    }
                };

                const dynamoDbData = await docClient.get(dynamoDbSelectParams).promise();
                if (!dynamoDbData.Item) {
                    connection.destroy();
                    return createResponse(404, {message: 'NOT_FOUND'});
                }

                console.log("refresh userSelect idx : "+decoded.idx)

                const [user] = await connection.query(
                    'select * from user where idx = ?;',
                    [decoded.idx]
                )

                console.log("refresh user : "+user)

                if (user) {

                    delete user.password;

                    const token = jwt.sign(user, process.env.SECRET, tokenOptions);
                    const newRefreshToken = jwt.sign(user, process.env.SECRET, refreshTokenOptions);

                    const date = new Date();
                    date.setDate(date.getDate() + 30);
                    const dynamoDbParams = {
                        TableName: dynamoDbTable,
                        Item: {
                            "user_idx": user.idx,
                            "refresh_token": newRefreshToken,
                            "expireTimestamp": Math.floor(date.getTime() / 1000)
                        }
                    };
                    await docClient.put(dynamoDbParams).promise();

                    const dynamoDbDeleteParams = {
                        TableName: dynamoDbTable,
                        Key: {
                            "user_idx": decoded.idx,
                            "refresh_token": refreshToken
                        }
                    };
                    await docClient.delete(dynamoDbDeleteParams).promise();

                    connection.destroy();
                    return createResponse(200, {token, refreshToken: newRefreshToken, decoded});
                } else {
                    connection.destroy();
                    return createResponse(404, {message: 'NOT_FOUND'});
                }
            } catch (err) {
                connection.destroy();
                return createResponse(500, {message: err.message});
            }
        } else {
            connection.destroy();
            return createResponse(500, {message: 'PARAMETER_ERROR'});
        }
    } catch (err) {
        connection.destroy();
        if (err.name === 'TokenExpiredError') {
            return createResponse(403);
        }
        return createResponse(500);
    }

}

export const logout = async (event) => {
    const body = JSON.parse(event.body)
    const refreshToken = body.refreshToken;
    let connection = await createConnection();
    try {
        const decoded = jwt.verify(refreshToken, process.env.SECRET, {issuer: process.env.ISSUER});
        if (decoded.idx) {
            try {
                const dynamoDbDeleteParams = {
                    TableName: dynamoDbTable,
                    Key: {
                        "user_idx": decoded.idx,
                        "refresh_token": refreshToken
                    }
                };
                await docClient.delete(dynamoDbDeleteParams).promise();

                connection.destroy();
                return createResponse(200);

            } catch (err) {
                connection.destroy();
                return createResponse(500, err);
            }
        } else {
            connection.destroy();
            return createResponse(500, {message: 'PARAMETER_ERROR'});
        }
    } catch (err) {
        connection.destroy();
        return createResponse(500, err);
    }
}

app.js에 추가하고 

성공응답 확인 리액트에 붙이는건 내일하자...

'연습' 카테고리의 다른 글

백 개발 배포  (0) 2025.08.20
백+프론트 연결  (0) 2025.08.19
프론트  (0) 2025.08.16
람다 3  (3) 2025.08.15
람다 이어서  (3) 2025.08.14