
Spring Securityλ₯Ό μ΄μ©ν΄μ μΈμ
 κΈ°λ° λ‘κ·ΈμΈ λ° κΆν κ²μ¦μ ꡬνν©λλ€.
λν
μΌν μ€μ λ³΄λ€λ μ μ²΄μ μΈ νλ¦μ 보기 μν νλ‘μ νΈμ
λλ€.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "MEMBER")
@Getter
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String username;
    private String password;
    private boolean enabled;
    @Enumerated(EnumType.STRING)
    private Role role;
    @Builder
    public Member(String username, String password, boolean enabled, Role role) {
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.role = role;
    }
}spring securityκ° μ κ³΅νλ ROLE λ€μ΄λ° μ μ±
μ΄ ROLE_κΆν μ΄λ―λ‘ λ§μΆ°μ μμ±ν΄μ€λλ€.
@Getter
public enum Role {
    ROLE_ADMIN("κ΄λ¦¬μ"), ROLE_MANAGER("λ§€λμ "), ROLE_MEMBER("μΌλ°μ¬μ©μ");
    private String description;
    Role(String description) {
        this.description = description;
    }
}Spring securityμ User ν΄λμ€λ₯Ό 보면 "ROLE_"λ‘ μμνλ κΆνμ μ°Ύλ κ²μ νμΈν  μ μμ΅λλ€.

usernameμ μ΄μ©ν΄ λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό ν  κ²μ΄κΈ° λλ¬Έμ JPA λ€μ΄λ° 쿼리λ₯Ό μ΄μ©ν΄ findByUsername λ©μλ νλλ₯Ό μ μν©λλ€. 
κ°λ¨ν μ€μ΅μ μν΄ μλΉμ€ κ³μΈ΅μ λμ§ μμμ΅λλ€.
public interface MemberRepository extends CrudRepository<Member, Long> {
    Optional<Member> findByUsername(String username);
}spring securityκ° μ κ³΅νλ User ν΄λμ€λ₯Ό μ°λ¦¬κ° μ μν Memberλ‘ μ¬μ©νκΈ° μν΄ μ»€μ€ν
ν©λλ€.
μ΄ν SecurityUserλ₯Ό ν΅ν΄ Memberμ μ κ·Όν  κ²μ΄λ―λ‘ Memberλ₯Ό νλλ‘ κ°κ² νκ³  μμ±μλ₯Ό ν΅ν΄ κ°μ μ μ§μμΌ μ€λλ€.
super ν€μλλ₯Ό μ΄μ©ν΄ λΆλͺ¨ ν΄λμ€(User)μ μμ±μλ‘ username, password, role μ λ겨μ€λλ€.
μμ²λλ λ°μ΄ν°λ₯Ό νμΈνκΈ° μν΄ λ‘κ·Έλ μ°μ΄λ³΄μμ΅λλ€.
@Slf4j
@Getter @Setter
public class SecurityUser extends User {
    private Member member;
    public SecurityUser(Member member) {
        super(member.getUsername(), member.getPassword(), AuthorityUtils.createAuthorityList(member.getRole().toString()));
        log.info("SecurityUser member.username = {}", member.getUsername());
        log.info("SecurityUser member.password = {}", member.getPassword());
        log.info("SecurityUser member.role = {}", member.getRole().toString());
        this.member = member;
    }
}λ‘κ·ΈμΈμ μν username (νΉμ id, email) μ΄ DBμ μλμ§ νμΈνλ λ©μλ loadUserByUsername λ©μλλ₯Ό μμ±ν©λλ€.
μ΄ λ©μλμμ μ¬μ©νκΈ° μν΄ repositoryμ findByUsername λ©μλλ₯Ό μ μν κ² μ
λλ€.
memberRepositoryλ₯Ό μ£Όμ
λ°κ³  findByUsername λ©μλλ₯Ό μ΄μ©ν΄ μ
λ ₯λ usernameμ΄ μ ν¨νμ§ νμΈν©λλ€.
usernameμ΄ μ ν¨νμ§ μλ€λ©΄ μμΈλ₯Ό λ°μμν€κ³  μ ν¨νλ€λ©΄ usernameμΌλ‘ μ°Ύμμ¨ Memberλ₯Ό μ΄μ©ν΄ Custom UserμΈ SecurityUserλ₯Ό μμ±νμ¬ λ°νν©λλ€.
@Slf4j
@Component
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
    private final MemberRepository memberRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Member> findMember = memberRepository.findByUsername(username);
        if (!findMember.isPresent()) throw new UsernameNotFoundException("μ‘΄μ¬νμ§ μλ username μ
λλ€.");
        log.info("loadUserByUsername member.username = {}", username);
        return new SecurityUser(findMember.get());
    }
}μ΄μ  μΈμ¦ λ° κΆν μ²λ¦¬λ₯Ό μν SecutiryConfigν΄λμ€λ₯Ό μμ±ν©λλ€.
WebSecurityConfigurerAdapterλ₯Ό μμλ°κ³  configure λ©μλλ₯Ό μ€λ²λΌμ΄λ© ν©λλ€.
κ°μ₯ λ¨Όμ  authorizaRequests λ₯Ό μ΄μ©ν΄ κΆν κ²μ¦μ μνν©λλ€.
antMatchersμλ 곡ν΅λ κΆνμ κ°μ§λ endpointλ₯Ό λ¬Άμ΄μ€λλ€. 그리고 λ€μμ λ©μλλ₯Ό μ΄μ©ν΄μ λ¬Άμ΄μ§ endpointμ λν κΆν λΆκΈ°λ₯Ό μνν©λλ€.
permitAll : μΈμ¦ μμ΄ μ κ·Ό κ°λ₯authentication : μΈμ¦λ μ¬μ©μλΌλ©΄ νΉλ³ν κΆν μμ΄ μ κ·Ό κ°λ₯hasAnyRole : λͺ
μν κΆνμ κ°μ§ μΈμ¦λ μ¬μ©μλ§ μ κ·Ό κ°λ₯hasRole : hasAnyRoleκ³Ό λμΌν κΈ°λ₯μ΄μ§λ§ νλμ κΆνκ³Ό λ§€νλ€μμ formLoginμ μ΄μ©ν΄ λ‘κ·ΈμΈ κ΄λ ¨ κΈ°λ₯μ μ μν©λλ€.
loginPage: μΈμ¦λμ§ μμ μ¬μ©μκ° μΈμ¦μ νμλ‘ νλ endpointμ μ κ·Όμ μ€μ ν κ²½λ‘λ‘ μ΄λμμΌμ€λ€. (λ³΄ν΅ λ‘κ·ΈμΈ νμ΄μ§λ‘ μ΄λ)loginProcessingUrl : μ§μ ν endpointλ‘ POST μμ²μ΄ μ¬ λ μμ²λ μ λ³΄λ₯Ό κΈ°λ°μΌλ‘ λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό μνexceptionHandling().accessDiniedPage : μΈμ¦λ μ¬μ©μμ΄μ§λ§ κΆν λ°μ endpointμ μ κ·Όμ μ΄λμν¬ uri λͺ
μlogout().logoutUrl : ν΄λΉ URLλ‘ μμ²μ΄ μ€λ κ²½μ° λ‘κ·Έμμ μνhttp.userDetailsService(userDetailsService);μ€μ  λ‘κ·ΈμΈ μ²λ¦¬λ₯Ό μννλ UserDetailsServiceλ₯Ό μ§μ 
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    private final UserDetailsServiceImpl userDetailsService;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/","/registry","/login", "/css/**").permitAll()
                .antMatchers("/member/**").authenticated() // μΌλ°μ¬μ©μ μ κ·Ό κ°λ₯
                .antMatchers("/manager/**").hasAnyRole("MANAGER", "ADMIN") // λ§€λμ , κ΄λ¦¬μ μ κ·Ό κ°λ₯
                .antMatchers("/admin/**").hasRole("ADMIN"); // κ΄λ¦¬μλ§ μ κ·Ό κ°λ₯
        // μΈμ¦ νμμ λ‘κ·ΈμΈ νμ΄μ§μ λ‘κ·ΈμΈ μ±κ³΅μ 리λ€μ΄λν
 κ²½λ‘ μ§μ 
        http.formLogin().loginPage("/login").defaultSuccessUrl("/", true);
        // λ‘κ·ΈμΈμ΄ μνλ  uri λ§€ν (post μμ²μ΄ κΈ°λ³Έ)
        http.formLogin().loginProcessingUrl("/login").defaultSuccessUrl("/", true);
        // μΈμ¦λ μ¬μ©μμ΄μ§λ§ μΈκ°λμ§ μμ κ²½λ‘μ μ κ·Όμ 리λ€μ΄λν
 μν¬ uri μ§μ 
        http.exceptionHandling().accessDeniedPage("/forbidden");
        // logout
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/");
        http.userDetailsService(userDetailsService);
    }
}μΈμ¦λ μ¬μ©μμκ² viewλ₯Ό λ¬λ¦¬νκΈ° μν΄ thymeleaf extras Springsecurity5 λ₯Ό μ¬μ©νμ΅λλ€. (μΈμ¦λ μ¬μ©μμκ²λ§ λ‘κ·Έμμ λ²νΌ νμ, λ‘κ·ΈμΈ λ²νΌ λ―Ένμ λ± ..)
Maven Repository λ§ν¬
μμ‘΄μ± μΆκ° ν μλμ κ°μ΄ λ€μμ€νμ΄μ€λ₯Ό μΆκ°ν΄μ€λλ€.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
  <div class="my-4">
    <h2>ν</h2>
      <div sec:authorize="isAuthenticated()">
          <p th:text="|${principal.username}λ νμν©λλ€.|"></p>
          <p th:text="|νμ¬ ${principal.username} λμ κΆν : ${role}|"></p>
          <ul>
              <li th:text="|session.getId = ${#session.getId()}|"></li>
              <li th:text="|session.getAttributeNames = ${#session.getAttributeNames()}|"></li>
              <li th:text="|session.getCreationTime = ${#session.getCreationTime()}|"></li>
          </ul>
      </div>
      <div class="btn">
        <a th:href="@{/registry}">
            <button class="btn btn-lg btn-secondary">νμκ°μ
</button>
        </a>
        <a th:href="@{/login}">
            <button sec:authorize="!isAuthenticated()" class="btn btn-lg btn-dark">λ‘κ·ΈμΈ</button>
        </a>
          <a th:href="@{/logout}">
              <button sec:authorize="isAuthenticated()" class="btn btn-danger btn-lg">λ‘κ·Έμμ</button>
          </a>
        <hr class="my-4">
          <a th:href="@{/admin/home}"><button class="btn btn-lg btn-dark">ADMIN page μ κ·Ό </button></a>
          <a th:href="@{/manager/home}"><button class="btn btn-lg btn-dark">MANAGER page μ κ·Ό</button></a>
      </div>
  </div>
</div>
</body>
</html>[ νμκ°μ
 νΌ ]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>νμκ°μ
</title>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
    <div class="my-4">
        <h2>νμκ°μ
</h2>
    </div>
    <form th:action method="post" th:object="${member}">
        <div class="mb-3">
            <label for="username" class="form-label">username</label>
            <input type="text" class="form-control" id="username" th:field="*{username}">
        </div>
        <div class="mb-3">
            <label for="password" class="form-label">Password</label>
            <input type="password" class="form-control" id="password" th:field="*{password}">
        </div>
        <hr class="my-3">
        <div class="mb-3">
            <div th:each="r : ${roles}" class="form-check form-check-inline">
                <input type="radio" th:field="*{role}" th:value="${r.value}" class="form-check-input">
                <label th:for="${#ids.prev('role')}" th:text="${r.key}" class="form-check-label"></label>
            </div>
        </div>
        <button type="submit" class="btn btn-primary">νμκ°μ
</button>
    </form>
</div>
</body>
</html>[ λ‘κ·ΈμΈ νΌ ]
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container">
  <div class="my-4">
    <h2>λ‘κ·ΈμΈ</h2>
  </div>
  <form th:action method="post" th:object="${member}">
    <div class="mb-3">
      <label for="username" class="form-label">username</label>
      <input type="text" class="form-control" id="username" th:field="*{username}">
    </div>
    <div class="mb-3">
      <label for="password" class="form-label">Password</label>
      <input type="password" class="form-control" id="password" th:field="*{password}">
    </div>
    <button type="submit" class="btn btn-primary">λ‘κ·ΈμΈ</button>
  </form>
</div>
</body>
</html>μλΉμ€ κ³μΈ΅μ λ°λ‘ λμ§ μμ 컨νΈλ‘€λ¬μμ λ°λ‘ λ ν¬μ§ν 리λ₯Ό μ£Όμ λ°μ μ¬μ©νμ΅λλ€.
password μ μ₯μ BCryptPasswordEncoderλ₯Ό μ΄μ©ν΄ λ¨λ°©ν₯ ν΄μλ‘ μΈμ½λ©νμ΅λλ€.
@Slf4j
@RequiredArgsConstructor
@Controller
public class RegistryController {
    private final MemberRepository memberRepository;
    private final BCryptPasswordEncoder passwordEncoder;
    @GetMapping("/registry")
    public String registryForm(Model model) {
        model.addAttribute("member", new RegistryRequest());
        return "registration";
    }
    @PostMapping("/registry")
    public String registry(@ModelAttribute RegistryRequest registryRequest) {
        Member member = Member.builder()
                .username(registryRequest.getUsername())
                .password(passwordEncoder.encode(registryRequest.getPassword()))
                .role(registryRequest.getRole())
                .build();
        memberRepository.save(member);
        return "redirect:/login";
    }
    @ModelAttribute("roles")
    public Map<String, Role> roles() {
        Map<String, Role> map = new LinkedHashMap<>();
        map.put("κ΄λ¦¬μ", Role.ROLE_ADMIN);
        map.put("λ§€λμ ", Role.ROLE_MANAGER);
        map.put("μΌλ° μ¬μ©μ", Role.ROLE_MEMBER);
        return map;
    }
}BCryptPasswordEncoderλ κ°λ¨νκ² main ν΄λμ€μμ λΉμΌλ‘ λ±λ‘ν΄μ£Όμμ΅λλ€.

viewμ controller κ° λ°μ΄ν° μΈν°νμ΄μ±μ μν΄ μ¬μ©ν μμ²κ°μ²΄μ λλ€.
@Getter @Setter
public class RegistryRequest {
    private String username;
    private String password;
    private Role role = Role.ROLE_MEMBER;
}μ λΆμ λλ€..
λ‘κ·ΈμΈ μͺ½ 컨νΈλ‘€λ¬λ λ‘κ·ΈμΈ νΌ λλλ§λ§μ λ΄λΉν©λλ€.
λ‘κ·ΈμΈ μ²λ¦¬λ SecurityConfigμ  loginProcessingUrlλ‘ λ³΄λ΄μ£ΌκΈ°λ§ νλ©΄ λ©λλ€.
@Controller
public class LoginController {
    @GetMapping("/login")
    public String loginForm(Model model) {
        model.addAttribute("member", new LoginRequest());
        return "login";
    }
}



μ΄μμΌλ‘ Spring Securityλ₯Ό μ΄μ©ν κ°λ¨ν μΈμ
λ°©μ λ‘κ·ΈμΈμ μμ보μμ΅λλ€.
μ¬μ€ μ΄λ° λ°©μμ μ΄μ λ μ μ°μ΄μ§ μλλ€κ³  ν©λλ€. μ£Όλ‘ JWT ν ν°λ°©μμ μ¬μ©νμ£ . 보μμ μΈ λ¬Έμ κ° κ°μ₯ ν¬κ² μ§λ§ μ΄λ° μΈμ
λ°©μμ μ¬μ©νλ€λ©΄ μ΄ν Oauth2 μ¬μ©μ λ¬Έμ κ° λ  μ μμ΅λλ€.
κ·Έλλ κΈ°λ³Έμ μκ³  μμ΄μΌνλ€κ³  μκ°νκΈ°μ ν λ² κ°λ¨νκ² κ΅¬νν΄λ΄€μ΅λλ€. γ
λ€μμλ JWTλ₯Ό μ΄μ©ν μΈμ¦μ ꡬνν΄λ³΄κ² μ΅λλ€ !
μ 체 μμ€μ½λλ κΉνλΈλ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ κ°μ¬ν©λλ€ γ  π
μλ νμΈμ.
OAuth2 μ¬μ©μ μ λ¬Έμ κ° μλ€κ³ μκ°νμλμ?