ํ์๊ฐ์ ๊ตฌํ
1. ํ๋ก์ ํธ ์ค์ ์ถ๊ฐ
// build.gradle ๋ด JPA, MySQL ์ถ๊ฐ
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// MySQL
runtimeOnly 'com.mysql:mysql-connector-j'
// application.properties ๋ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ณด ์ถ๊ฐ
spring.datasource.url=jdbc:mysql://localhost:3306/auth
spring.datasource.username=root
spring.datasource.password={๋น๋ฐ๋ฒํธ}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
2. auth ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ ํ ์ธํ ๋ฆฌ์ ์ด ์ฐ๋
CREATE DATABASE auth;
3. HTML, CSS, JS ์ฝ๋ ์ถ๊ฐ
src > main > resources > templates ์๋ HTML ์ฝ๋ ์ถ๊ฐ(index.html, login.html, signup.html)
src > main > resources > static ๋ด css, js ํด๋ ์ถ๊ฐ ํ style.css๊ณผ basic.js ์์ฑ
4. ํ์ด์ง Controller ์์ฑ
- HomeController
package com.sparta.springauth.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("username", "username");
return "index"; // templates > index.html์ธ ๋ฉ์ธ ํ์ด์ง ๋ฐํ
}
}
- UserController
package com.sparta.springauth.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/api")
public class UserController {
@GetMapping("/user/login-page")
public String loginPage() {
return "login"; // ๋ก๊ทธ์ธ ํ์ด์ง ๋ฐํ
}
@GetMapping("/user/signup")
public String signupPage() {
return "signup"; // ํ์๊ฐ์
ํ์ด์ง ๋ฐํ
}
}


5. User ์ํฐํฐ ์์ฑ
package com.sparta.springauth.entity;
public enum UserRoleEnum {
USER(Authority.USER), // ์ฌ์ฉ์ ๊ถํ
ADMIN(Authority.ADMIN); // ๊ด๋ฆฌ์ ๊ถํ
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
package com.sparta.springauth.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
}
- @Enumerated ์ด๋
ธํ
์ด์
์ ๋ฐ์ดํฐ enum ํ์
์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปฌ๋ผ์ ์ ์ฅํ ๋ ์ฌ์ฉํ๋ ์ด๋
ธํ
์ด์
- ์ต์ ์ผ๋ก EnumType.STRING์ ์ฌ์ฉํ๋ฉด enum์ ์ด๋ฆ ๊ทธ๋๋ก๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํจ
ํจ์ค์๋ ์ํธํ
ํ์ ๋ฑ๋ก ์ ๋น๋ฐ๋ฒํธ๋ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ฌธ์ ๊ทธ๋๋ก DB์ ๋ฑ๋กํ๋ฉด ์๋จ! '์ ๋ณดํต์ ๋ง๋ฒ, ๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ'์ ์ํด ๋น๋ฐ๋ฒํธ ์ํธํ(Encryption)๊ฐ ์๋ฌด์

- 'ํ๋ฌธ → (์ํธํ ์๊ณ ๋ฆฌ์ฆ) → ์ํธ๋ฌธ'๋ก ์ํธํ ํ ํจ์ค์๋ ์ ์ฅ ํ์ํจ
- ํด์ปค๊ฐ DB์ ์๋ ํจ์ค์๋ ์ ๋ณด๋ฅผ ๊ฐ์ทจํ๋๋ผ๋ ์ค์ ์ํธ๋ฅผ ์ ์ ์๊ณ , ๋ณตํธํ๊ฐ ๋ถ๊ฐ๋ฅํ ๋จ๋ฐฉํฅ ์ํธ ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ์ด ํ์ํจ
์๋ฐฉํฅ ์ํธ ์๊ณ ๋ฆฌ์ฆ
- ์ํธํ: ํ๋ฌธ → (์ํธํ ์๊ณ ๋ฆฌ์ฆ) → ์ํธ๋ฌธ
- ๋ณตํธํ: ์ํธ๋ฌธ → (์ํธํ ์๊ณ ๋ฆฌ์ฆ) → ํ๋ฌธ
- ์ฆ, ์ํธํ์ ๋ณตํธํ๊ฐ ๋ชจ๋ ๊ฐ๋ฅํ ์๊ณ ๋ฆฌ์ฆ
๋จ๋ฐฉํฅ ์ํธ ์๊ณ ๋ฆฌ์ฆ
- ์ํธํ: ํ๋ฌธ → (์ํธํ ์๊ณ ๋ฆฌ์ฆ) → ์ํธ๋ฌธ
- ๋ณตํธํ:
์ํธ๋ฌธ→ (์ํธํ ์๊ณ ๋ฆฌ์ฆ)→ ํ๋ฌธโบ ๋ถ๊ฐ๋ฅ - ์ฆ, ์ํธํ๋ ๊ฐ๋ฅํ์ง๋ง ๋ณตํธํ๋ ๋ถ๊ฐ๋ฅํ ์๊ณ ๋ฆฌ์ฆ
โ ์ฌ์ฉ์๋ ๋ก๊ทธ์ธํ ๋ ์ํธํ๋ ํจ์ค์๋๋ฅผ ๊ธฐ์ตํด์ผ ํ ๊น?
Spring Security๋ผ๋ ํ๋ ์์ํฌ์์ boolean matches(CharSequence rawPassword, String encodedPassword);๋ฅผ ์ ๊ณตํด ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํด ์ ์ฅ๋ ๋น๋ฐ๋ฒํธ์ ๋น๊ตํด ์ผ์น์ฌ๋ถ ํ์ธํ ์ ์์
1. ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์ํด "์์ด๋, ํจ์ค์๋ (ํ๋ฌธ)" ์ ๋ ฅํด ์๋ฒ์ ๋ก๊ทธ์ธ ์์ฒญ
2. ์๋ฒ์์ ํจ์ค์๋ (ํ๋ฌธ)์ ์ํธํ ์ํ: ํ๋ฌธ → (์ํธํ ์๊ณ ๋ฆฌ์ฆ) → ์ํธ๋ฌธ
3. DB์ ์ ์ฅ๋ "์์ด๋, ํจ์ค์๋ (์ํธ๋ฌธ)"์ ์ผ์น ์ฌ๋ถ ํ์ธ
ํ์๊ฐ์ API ๊ตฌํ
| Name | Method | URL | ์ค๋ช |
| ํ์๊ฐ์ ํ์ด์ง | GET | /api/user/signup | ํ์๊ฐ์ ํ์ด์ง ํธ์ถ |
| ํ์๊ฐ์ | POST | /api/user/signup | ํ์๊ฐ์ |
6. ํ์๊ฐ์ API ๋ง๋ค๊ธฐ
6-1) RequestDto ์์ฑ
package com.sparta.springauth.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SignupRequestDto {
private String username;
private String password;
private String email;
private boolean admin = false;
private String adminToken = "";
}
6-2) ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌํ Service ๋ง๋ค๊ธฐ
package com.sparta.springauth.service;
import com.sparta.springauth.dto.SignupRequestDto;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
// ADMIN_TOKEN
private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";
public void signup(SignupRequestDto requestDto) {
String username = requestDto.getUsername();
String password = passwordEncoder.encode(requestDto.getPassword());
// ํ์ ์ค๋ณต ํ์ธ
Optional<User> checkUsername = userRepository.findByUsername(username);
if (checkUsername.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ ์ฌ์ฉ์๊ฐ ์กด์ฌํฉ๋๋ค.");
}
// email ์ค๋ณตํ์ธ
String email = requestDto.getEmail();
Optional<User> checkEmail = userRepository.findByEmail(email);
if (checkEmail.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ Email ์
๋๋ค.");
}
// ์ฌ์ฉ์ ROLE ํ์ธ
UserRoleEnum role = UserRoleEnum.USER;
if (requestDto.isAdmin()) {
if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
throw new IllegalArgumentException("๊ด๋ฆฌ์ ์ํธ๊ฐ ํ๋ ค ๋ฑ๋ก์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
role = UserRoleEnum.ADMIN;
}
// ์ฌ์ฉ์ ๋ฑ๋ก
User user = new User(username, password, email, role);
userRepository.save(user);
}
}
- ADMIN_TOKEN์ ์ผ๋ฐ ์ฌ์ฉ์์ธ์ง ๊ด๋ฆฌ์์ธ์ง ๊ตฌ๋ถํ ๋ ์ฌ์ฉ
- ์ค์ ๋ก ํ์ ์์๋ ๊ด๋ฆฌ์ ๊ถํ์ ์ค ๋ ์ ๋ฐ์์ผ๋ก ์ฃผ์ง ์๊ณ , ๊ด๋ฆฌ์ ํ์ด์ง๋ฅผ ๋ฐ๋ก ๊ตฌํํ๊ฑฐ๋ ์น์ธ์์ ์ํด์ ๊ฒฐ์ฌํ๋ ๊ณผ์ ์ ๊ตฌํํจ
6-3) Repository ์์ฑ
package com.sparta.springauth.repository;
import com.sparta.springauth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
}
6-4) Controller ๋ด ํ์๊ฐ์ API ์ถ๊ฐ
@PostMapping("/user/signup")
public String signup(@ModelAttribute SignupRequestDto signupRequestDto) {
userService.signup(signupRequestDto);
return "redirect:/api/user/login-page"; // ๋ก๊ทธ์ธ ํ๋ฉด ๋ฆฌํด
}




๋ก๊ทธ์ธ ๊ตฌํ: JWT
๋ก๊ทธ์ธ API ๊ตฌํ
| Name | Method | URL | ์ค๋ช |
| ๋ก๊ทธ์ธ ํ์ด์ง | GET | /api/user/login-page | ๋ก๊ทธ์ธ ํ์ด์ง ํธ์ถ |
| ๋ก๊ทธ์ธ | POST | /api/user/login | ๋ก๊ทธ์ธ |
7. ๋ก๊ทธ์ธ API ๋ง๋ค๊ธฐ
7-1) ๋ก๊ทธ์ธ RequestDto ์์ฑ
package com.sparta.springauth.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class LoginRequestDto {
private String username;
private String password;
}
7-2) ๋ก๊ทธ์ธ Controller ์์ฑ
@PostMapping("/user/login")
public String login(@ModelAttribute LoginRequestDto requestDto, HttpServletResponse res) {
try {
userService.login(requestDto, res);
} catch (Exception e) {
return "redirect:/api/user/login-page?error";
}
return "redirect:/"; // ๋ฉ์ธ ํ๋ฉด์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
}
7-3) ๋ก๊ทธ์ธ ๋น์ฆ๋์ค ๋ก์ง Service์ ์ถ๊ธฐ
public void login(LoginRequestDto requestDto, HttpServletResponse res) {
String username = requestDto.getUsername();
String password = requestDto.getPassword();
// ์ฌ์ฉ์ ํ์ธ
User user = userRepository.findByUsername(username).orElseThrow(
() -> new IllegalArgumentException("๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค.")
);
// ๋น๋ฐ๋ฒํธ ํ์ธ
if(!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException("๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
}
// JWT ์์ฑ ๋ฐ ์ฟ ํค์ ์ ์ฅ ํ Response ๊ฐ์ฒด์ ์ถ๊ฐ
String token = jwtUtil.createToken(user.getUsername(), user.getRole());
jwtUtil.addJwtToCookie(token, res);
}


ํํฐ(Filter)

ํํฐ(Filter)๋ Web Application์์ ๊ด๋ฆฌ๋๋ ์์ญ์ผ๋ก, ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์ค๋ ์์ฒญ๊ณผ ์๋ต์ ๋ํด์ ์ต์ด, ์ต์ด ๋จ๊ณ์ ์์นํด ์ด๋ฅผ ํตํด์ ์์ฒญ๊ณผ ์๋ต์ ์ ๋ณด๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ๋ถ๊ฐ์ ์ธ ๊ธฐ๋ฅ ์ถ๊ฐํ ์ ์์
- ์ฃผ๋ก ๋ฒ์ฉ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ์์ ์ธ Logging ํน์ ๋ณด์ ์ฒ๋ฆฌ์ ํ์ฉํจ
- ์ธ์ฆ, ์ธ๊ฐ์ ๊ด๋ จ๋ ๋ก์ง ์ฒ๋ฆฌ๋ ๊ฐ๋ฅํจ. ์ฆ, ํํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ธ์ฆ/์ธ๊ฐ์ ๊ด๋ จ๋ ๋ก์ง์ ์ฒ๋ฆฌํจ์ผ๋ก์จ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋ถ๋ฆฌํ ์ ์๋ค๋ ์ฅ์ ์กด์ฌ
- Filter๋ ํ๋๋ง ์กด์ฌํ๋ ๊ฒ์ด ์๋๋ผ ์ฌ๋ฌ ๊ฐ๋ก Chain ํ์์ผ๋ก ๋ฌถ์ฌ์ ์ฒ๋ฆฌ๋๊ณ ์์

URL์ Loggingํ๋ Filter
package com.sparta.springauth.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j(topic = "LoggingFilter") // ๋ก๊น
๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋ก๊น
์์ topic ์ด๋ฆ์ผ๋ก ์ฐํ
@Component
@Order(1) // Chain ํ์์ผ๋ก ๋์ด์๋ Filter์ ์์ ์ง์ ๊ฐ๋ฅ
public class LoggingFilter implements Filter {
// Filter ์ธํฐํ์ด์ค ๋ด์ doFilter ๋ฉ์๋ ์กด์ฌํจ
// FilterChain์ Filter๋ฅผ ์ด๋ํ ๋ ์ฌ์ฉํ๋ ค๊ณ ๋ฐ์์ด
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// ์ ์ฒ๋ฆฌ
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI(); // URL ์ ๋ณด ๊ฐ์ ธ์ด
log.info(url); // log ์ฐ๊ธฐ
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
// ํ์ฒ๋ฆฌ
log.info("๋น์ฆ๋์ค ๋ก์ง ์๋ฃ");
}
}
์ธ์ฆ ๋ฐ ์ธ๊ฐ๋ฅผ ์ฒ๋ฆฌํ๋ Filter
- ์ด์ ์ Filter๋ฅผ ๊ฑฐ์น์ง ์๊ณ Controller๋ก ์์ฒญ์ด ๋ค์ด์ฌ ๋๋, @CookieValue ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ ์ ์์ด์ Request ๊ฐ์ฒด์ Cookie์ ๋ค์ด์๋ Value๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์์
- Filter๋ DispatcherServlet๋ณด๋ค ์๋จ์ด๊ธฐ ๋๋ฌธ์ @CookieValue ์ด๋ ธํ ์ด์ ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์, Cookie๋ฅผ ๋ฝ์๋ด๋ ๋ฉ์๋ ์ง์ ๊ตฌํ ํ์ํจ
JwtUtil์ Cookie ๊ฐ์ ธ์ค๋ ๋ฉ์๋ ์ถ๊ฐ
// HttpServletRequest ์์ Cookie Value : JWT ๊ฐ์ ธ์ค๊ธฐ
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode ๋์ด ๋์ด๊ฐ Value ๋ค์ Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null;
}
์ธ์ฆ FIlter ์์ฑ
package com.sparta.springauth.filter;
import com.sparta.springauth.entity.User;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.IOException;
@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
// ํ์๊ฐ์
, ๋ก๊ทธ์ธ ๊ด๋ จ API ๋ ์ธ์ฆ ํ์์์ด ์์ฒญ ์งํ
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
} else {
// ๋๋จธ์ง API ์์ฒญ์ ์ธ์ฆ ์ฒ๋ฆฌ ์งํ
// ํ ํฐ ํ์ธ
// ์ด์ ์ Filter๋ฅผ ๊ฑฐ์น์ง ์๊ณ Controller๋ก ์์ฒญ์ด ๋ค์ด์ฌ ๋๋,
// @CookieValue ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ ์ ์์ด์ Request ๊ฐ์ฒด์ Cookie์ ๋ค์ด์๋ Value๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์์
// Filter๋ DispatcherServlet๋ณด๋ค ์๋จ์ด๊ธฐ ๋๋ฌธ์ @CookieValue ์ด๋
ธํ
์ด์
์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์,
// Cookie๋ฅผ ๋ฝ์๋ด๋ ๋ฉ์๋ ์ง์ ๊ตฌํ ํ์ํจ
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // ํ ํฐ์ด ์กด์ฌํ๋ฉด ๊ฒ์ฆ ์์
// JWT ํ ํฐ substring
String token = jwtUtil.substringToken(tokenValue);
// ํ ํฐ ๊ฒ์ฆ
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}

