스프링부트 회원가입, 로그인

카사바·2026년 2월 2일

백엔드

목록 보기
1/1

참고 강의: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/dashboard?cid=328073
Repository: https://github.com/goodispotato/spring_boot_security


root 로그인 후 입력

application.properties >

spring.application.name=security1

server.port=8080
server.servlet.context-path=/
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
spring.datasource.username=cos
spring.datasource.password=cos1234

spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

Spring Security를 추가하면 어떤 페이지를 들어가든 인증 페이지가 먼저 뜬다. login, logout 페이지가 이미 만들어져 있음

@ResponseBody는 문자열 그 자체, 혹은 JSON 데이터를 반환할 때 사용

SecurityConfig > # 웹 요청이 들어왔을 때 어떤 보안 규칙을 적용할지에 관한 클래스

package com.cos.security1.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 보안 필터를 활성화. 이게 없으면 보안 필터가 동작하지 않는다.
public class SecurityConfig {

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable()) // 실서비스에서는 켜야 함
                .authorizeHttpRequests(auth -> auth // URL 별로 접근 권한 설정
                .requestMatchers("/user").authenticated() // 로그인한 사용자만 허용
                .requestMatchers("/manager") // 로그인+권한 필요
                        .hasAnyRole("ADMIN", "MANAGER")
                .requestMatchers("/admin") // 로그인+권한 필요
                        .hasRole("ADMIN")
                .anyRequest().permitAll() // 위 주소들만 아니면 누구나 어디든 들어갈 수 있음
                )
                .formLogin(form -> form
                        .loginPage("/loginForm") // 로그인 안 한 사람이 접근 허가되지 않은 곳에 접근하면 로그인 페이지로 리다이렉트
                        .permitAll() // 로그인 페이지는 누구에게나 접근 허용
                );

        return http.build();
    }
}

어떤 url로 접근하든 /login 페이지로 리다이렉트됨

User >

package com.cos.security1.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

@Entity
@Data // Getter, Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String username;
    private String password;
    private String email;
    private String role; // ROLE_USER, ROLE_ADMIN
    @CreationTimestamp
    private Timestamp createDate;
}

IndexController >

package com.cos.security1.controller;

import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {

    @Autowired
    private UserRepository userRepository;

    //localhost:8080/
    //localhost:8080
    @GetMapping({"","/"})
    public String index() {
        return "index";
    }

    @GetMapping("/admin")
    public @ResponseBody String admin() {
        return "admin";
    }

    @GetMapping("/user")
    public @ResponseBody String user() {
        return "user";
    }

    @GetMapping("/manager")
    public @ResponseBody String manager() {
        return "manager";
    }

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

    @GetMapping("/joinForm") // 회원가입 페이지
    public String joinForm() {
        return "joinForm";
    }

    @PostMapping("/join") // 회원가입
    public @ResponseBody String join(User user) {
        System.out.println(user);
        user.setRole("ROLE_USER");
        userRepository.save(user);
        return "join";
    }
}

아래 코드처럼 하면 안 된다. 패스워드가 암호화되지 않았기 때문에 시큐리티로 로그인 불가능.

        userRepository.save(user);

SecurityConfig > 비밀번호 암호화 도구만 추가함

package com.cos.security1.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public BCryptPasswordEncoder encodePwd() { // 비밀번호 암호화 도구
        return new BCryptPasswordEncoder();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                .requestMatchers("/user").authenticated()
                .requestMatchers("/manager")
                        .hasAnyRole("ADMIN", "MANAGER")
                .requestMatchers("/admin")
                        .hasRole("ADMIN")
                .anyRequest().permitAll() // 위 주소들만 아니면 누구나 어디든 들어갈 수 있음
                )
                .formLogin(form -> form
                        .loginPage("/loginForm")
                        .permitAll()
                );

        return http.build();
    }
}

IndexController > 비밀번호 암호화 후 set

package com.cos.security1.controller;

import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    //localhost:8080/
    //localhost:8080
    @GetMapping({"","/"})
    public String index() {
        return "index";
    }

    @GetMapping("/admin")
    public @ResponseBody String admin() {
        return "admin";
    }

    @GetMapping("/user")
    public @ResponseBody String user() {
        return "user";
    }

    @GetMapping("/manager")
    public @ResponseBody String manager() {
        return "manager";
    }

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

    @GetMapping("/joinForm") // 회원가입 페이지
    public String joinForm() {
        return "joinForm";
    }

    @PostMapping("/join") // 회원가입
    public String join(User user) {
        System.out.println(user);
        user.setRole("ROLE_USER");
        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encPassword);

        userRepository.save(user);
        return "redirect:/loginForm"; // 회원가입 완료 후에는 다시 로그인 페이지로 리다이렉트
    }
}

SecurityConfig >

package com.cos.security1.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public BCryptPasswordEncoder encodePwd() { // 비밀번호 암호화 도구
        return new BCryptPasswordEncoder();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                .requestMatchers("/user").authenticated()
                .requestMatchers("/manager")
                        .hasAnyRole("ADMIN", "MANAGER")
                .requestMatchers("/admin")
                        .hasRole("ADMIN")
                .anyRequest().permitAll() // 위 주소들만 아니면 누구나 어디든 들어갈 수 있음
                )
                .formLogin(form -> form
                        .loginPage("/loginForm")
                        .permitAll()
                        .loginProcessingUrl("/login") // 시큐리티가 대신 로그인 기능을 수행하므로, 컨트롤러에 로그인 기능을 따로 구현하지 않아도 됨
                        .defaultSuccessUrl("/") // 로그인 성공하면 메인 페이지로 이동
                );

        return http.build();
    }
}

PrincipalDetails >

package com.cos.security1.config.auth;

import com.cos.security1.model.User;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

// 로그인한 사용자의 정보를 담는 그릇
// Security Session => Authentication => UserDetails(PrincipalDetails)
public class PrincipalDetails implements UserDetails {

    private User user;

    public PrincipalDetails(User user) {
        this.user=user;
    }

    // 해당 user의 권한을 리턴하는 곳
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collect;
    }

    @Override
    public @Nullable String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

UserRepository >

package com.cos.security1.repository;

import com.cos.security1.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Integer> {
    public User findByUsername(String username);
    public User findByEmail(String email);
}

PrincipalDetailService >

package com.cos.security1.config.auth;

import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;

//login 요청이 오면 실행됨
@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if(userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}

SecurityConfig >

package com.cos.security1.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true) // 메서드 기준으로 권한 설정
public class SecurityConfig {

    @Bean
    public BCryptPasswordEncoder encodePwd() { // 비밀번호 암호화 도구
        return new BCryptPasswordEncoder();
    }

    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                .requestMatchers("/user").authenticated()
                .requestMatchers("/manager")
                        .hasAnyRole("ADMIN", "MANAGER")
                .requestMatchers("/admin")
                        .hasRole("ADMIN")
                .anyRequest().permitAll() // 위 주소들만 아니면 누구나 어디든 들어갈 수 있음
                )
                .formLogin(form -> form
                        .loginPage("/loginForm")
                        .permitAll()
                        .loginProcessingUrl("/login") // 시큐리티가 대신 로그인 기능을 수행하므로, 컨트롤러에 로그인 기능을 따로 구현하지 않아도 됨
                        .defaultSuccessUrl("/") // 로그인 성공하면 메인 페이지로 이동
                );

        return http.build();
    }
}

IndexController >

package com.cos.security1.controller;

import com.cos.security1.model.User;
import com.cos.security1.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class IndexController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    //localhost:8080/
    //localhost:8080
    @GetMapping({"","/"})
    public String index() {
        return "index";
    }

    @GetMapping("/admin")
    public @ResponseBody String admin() {
        return "admin";
    }

    @GetMapping("/user")
    public @ResponseBody String user() {
        return "user";
    }

    @GetMapping("/manager")
    public @ResponseBody String manager() {
        return "manager";
    }

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

    @GetMapping("/joinForm") // 회원가입 페이지
    public String joinForm() {
        return "joinForm";
    }

    @PostMapping("/join") // 회원가입
    public String join(User user) {
        System.out.println(user);
        user.setRole("ROLE_USER");
        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encPassword);

        userRepository.save(user);
        return "redirect:/loginForm"; // 회원가입 완료 후에는 다시 로그인 페이지로 리다이렉트
    }

    @Secured("ROLE_ADMIN") // 더 최근 방식
    @GetMapping("/info")
    public @ResponseBody String info() {
        return "개인정보";
    }

    @PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") // 예전 방식
    @GetMapping("/data")
    public @ResponseBody String data() {
        return "데이터정보";
    }
}
profile
안녕하세요 :)

0개의 댓글