참고 강의: 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 "데이터정보";
}
}