스프링 부트 jwt mybatis 연동 설정

2024. 11. 11. 15:15웹개발

728x90

Spring Boot, MyBatis, JWT를 사용하여 로그인 기능을 구축하는 블로그 예제를 작성해보겠습니다. 이 프로젝트에서는 Spring Boot와 MyBatis로 MySQL에 연결하고, JWT를 사용해 인증 시스템을 구현합니다.

---

# Spring Boot와 MyBatis, JWT를 사용한 로그인 시스템 구현하기

웹 애플리케이션의 기본적인 로그인 및 인증 기능을 구축하는 것은 필수적입니다. 이번 글에서는 **Spring Boot**, **MyBatis**, **JWT(Json Web Token)**을 사용해 MySQL 기반의 인증 시스템을 간단히 구축해보겠습니다. 이 글을 통해 MyBatis로 데이터베이스를 연동하고, JWT 토큰을 발급하여 보안된 API 접근을 처리하는 방법을 배울 수 있습니다.

## 프로젝트 구성

### 주요 라이브러리 및 기술 스택
- **Spring Boot**: 애플리케이션의 빠른 개발을 위해 사용합니다.
- **MyBatis**: 데이터베이스와의 간단한 연동을 위해 사용합니다.
- **JWT**: 사용자 인증 및 권한 관리를 위한 토큰 기반의 인증 방식입니다.
- **MySQL**: 사용자 데이터를 저장하기 위한 데이터베이스로 사용합니다.

---

## Step 1: 프로젝트 생성 및 의존성 설정

Spring Boot 프로젝트를 생성하고 필요한 의존성을 `pom.xml` 파일에 추가합니다.

```xml
<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>

    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
</dependencies>
```

---

## Step 2: 데이터베이스 설정 및 테이블 생성

먼저 MySQL에 사용자 테이블을 생성합니다. 이번 예제에서는 `users` 테이블을 사용하여 사용자 정보를 저장합니다.

```sql
CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    role VARCHAR(20) NOT NULL
);
```

### `application.yml` 파일 설정

MySQL 데이터베이스에 연결하기 위한 설정을 `application.yml` 파일에 추가합니다.

```yaml
spring:
  datasource:
    driver-class-name: cohttp://m.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password
  mybatis:
    type-aliases-package: cohttp://m.example.demo.model
    mapper-locations: classpath:mapper/*.xml
  jwt:
    secret: your_jwt_secret_key
    expiration-time: 3600000 # 1시간 (밀리초)
```

---

## Step 3: MyBatis 설정

MyBatis를 통해 `users` 테이블에 접근할 수 있도록 매퍼를 설정합니다.

### MyBatis 매퍼 파일 생성

**src/main/resources/mapper/UserMapper.xml**

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cohttp://m.example.demo.mapper.UserMapper">
    <select id="findByUsername" resultType="cohttp://m.example.demo.model.User">
        SELECT * FROM users WHERE username = #{username}
    </select>
</mapper>
```

### MyBatis Mapper 인터페이스 생성

**UserMapper.java**

```java
package cohttp://m.example.demo.mapper;

import cohttp://m.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User findByUsername(String username);
}
```

---

## Step 4: JWT 유틸리티 클래스 작성

JWT 토큰을 생성하고 검증하는 유틸리티 클래스를 작성합니다.

**JwtUtil.java**

```java
package cohttp://m.example.demo.util;

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_KEY;

    @Value("${jwt.expiration-time}")
    private long EXPIRATION_TIME;

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    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_KEY).parseClaimsJws(token).getBody();
    }

    public Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public Boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }
}
```

---

## Step 5: 사용자 서비스와 모델 클래스 작성

MyBatis Mapper를 이용하여 사용자 정보를 데이터베이스에서 조회하는 서비스를 작성합니다.

### 사용자 모델 클래스

**User.java**

```java
package cohttp://m.example.demo.model;

public class User {
    private Long id;
    private String username;
    private String password;
    private String role;

    // Getters and Setters
}
```

### 사용자 서비스 클래스

**UserService.java**

```java
package cohttp://m.example.demo.service;

import cohttp://m.example.demo.mapper.UserMapper;
import cohttp://m.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User findByUsername(String username) {
        return userMapper.findByUsername(username);
    }
}
```

---

## Step 6: 인증 컨트롤러 작성

사용자 인증 요청을 받아 JWT 토큰을 발급하는 컨트롤러를 작성합니다.

**AuthController.java**

```java
package cohttp://m.example.demo.controller;

import cohttp://m.example.demo.model.User;
import cohttp://m.example.demo.service.UserService;
import cohttp://m.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private UserService userService;

    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody User user) {
        User foundUser = userService.findByUsername(user.getUsername());
        if (foundUser != null && foundUser.getPassword().equals(user.getPassword())) {
            String token = jwtUtil.generateToken(foundUser.getUsername());
            return ResponseEntity.ok(token);
        }
        return ResponseEntity.status(401).body("Invalid credentials");
    }
}
```

---

## Step 7: Spring Security 및 JWT 필터 설정

JWT 토큰을 인증 필터로 추가하여 사용자 요청을 보호합니다.

**JwtRequestFilter.java**

```java
package cohttp://m.example.demo.filter;

import cohttp://m.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @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);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            if (jwtUtil.validateToken(jwt, username)) {
                // JWT가 유효한 경우 Security Context에 인증 정보 설정
            }
        }
        chain.doFilter(request, response);
    }
}



- **Authorization 헤더 검사**: 요청의 `Authorization` 헤더에서 JWT 토큰을 추출합니다. `Bearer `로 시작하는 토큰인 경우에만 추출하여 사용합니다.
- **토큰 검증**: JWT 토큰이 존재하고, 토큰에서 사용자 이름이 추출되면 토큰의 유효성을 검증합니다.
- **Spring Security 컨텍스트에 인증 설정**: 토큰이 유효할 경우 사용자 정보를 기반으로 `UsernamePasswordAuthenticationToken` 객체를 생성하여 Spring Security의 컨텍스트에 설정합니다.

---

Spring Security 설정

이제 Spring Security 설정을 통해, JWT 필터가 모든 요청을 가로채어 유효한 토큰을 가진 요청만 접근할 수 있도록 설정합니다. `SecurityConfig.java` 파일에서 JWT 필터를 Spring Security 필터 체인에 추가합니다.

#### `SecurityConfig.java`

```java
package cohttp://m.example.demo.config;

import cohttp://m.example.demo.filter.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/auth/login").permitAll()  // 로그인은 인증 없이 접근 가능
            .anyRequest().authenticated()            // 나머지는 인증 필요
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 세션을 사용하지 않음

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
```

이 설정에서 중요한 부분은 JWT 필터를 `UsernamePasswordAuthenticationFilter` 앞에 추가하여 모든 요청에서 JWT 검증을 수행하는 것입니다.

---

이로써 JWT 필터와 Spring Security 설정이 완료되었습니다.

728x90
반응형

'웹개발' 카테고리의 다른 글

HTML5/CSS3 div태그 좌우 반으로 나누기  (1) 2024.11.15
tailwind로 만드는 키오스크  (1) 2024.11.12
무료 클라우드 서비스  (1) 2024.06.11
MSSQL 대용량 데이터 삭제  (0) 2023.10.16
자바 쿠키 설정 JAVA COOKIE  (0) 2023.09.13