2024. 11. 11. 15:15ㆍ웹개발
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 설정이 완료되었습니다.
'웹개발' 카테고리의 다른 글
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 |