MapleStory Finger Point

๐ŸŸค JAVA/๐ŸŸค Spring

[Spring Security] ๋กœ๊ทธ์ธ ๊ตฌํ˜„

HYEJU01 2024. 12. 4. 17:58
 

๋ชฉ์ฐจ

 

     

     

    ( ์ œ๊ฐ€ ์ž‘์—…ํ–ˆ๋˜  ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.)

     

     

     

    1. build gradle ์— ์˜์กด์„ฑ ์ถ”๊ฐ€

    	//์‹œํ๋ฆฌํ‹ฐ ๋ฒ„์ „์€ ์Šคํ”„๋ง๋ฒ„์ „์— ๋”ฐ๋ผ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์ด ์™„์ „ํžˆ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.
    	//์‹œํ๋ฆฌํ‹ฐ 5๋ฒ„์ „ => ์Šคํ”„๋ง ๋ถ€ํŠธ 2๋ฒ„์ „ (์ˆ˜์—…)
    	//์‹œํ๋ฆฌํ‹ฐ 6๋ฒ„์ „ => ์Šคํ”„๋ง ๋ถ€ํŠธ 3๋ฒ„์ „ (๋ฌธ๋ฒ•์ด ์™„์ „ ๋ณ€๊ฒฝ๋˜๋‹ˆ ์ฃผ์˜)
    	implementation 'org.springframework.boot:spring-boot-starter-security'
    	//์‹œํ๋ฆฌํ‹ฐ ํƒ€์ž„๋ฆฌํ”„์—์„œ ์‚ฌ์šฉ
    	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    	//์‹œํ๋ฆฌํ‹ฐ ํ…Œ์ŠคํŠธ
    	testImplementation 'org.springframework.security:spring-security-test'

     

    • ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ฒ˜์Œ ์„ค์ • ๋˜๋ฉด ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ์‹œํ๋ฆฌ๊ฐ€ ๊ธฐ๋ณธ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ๋ณด์ด๊ฒŒ ๋จ.
    • ๊ธฐ๋ณธ์•„์ด๋””๋Š” user / ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” console์ฐฝ์— ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐœ๊ธ‰๋จ.
    • ๋กœ๊ทธ์•„์›ƒ์˜ ๊ฒฝ๋กœ๋Š” /logout์ด ๊ธฐ๋ณธ์ด ๋œ๋‹ค.

     

     

     

     

    2. ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ํŒŒ์ผ ๊ตฌํ˜„

     

    security_config.java

     

     

     

    ์ฃผ์š”๋ฉ”์„œ๋“œ

     

     

     

    3. SecurityConfig ์— SecurityFilterChain ๊ตฌํ˜„ 

     

    package com.project.tobe.config;
    
    import com.project.tobe.dto.UserRole;
    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 encoder() {
        return new BCryptPasswordEncoder();  // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ์ธ์ฝ”๋”
      }
    
      @Bean
      public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()//csrfํ† ํฐ ์‚ฌ์šฉx
    
            .authorizeRequests() // ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๊ทœ์น™ ์„ค์ •
            .antMatchers("/login.user").permitAll() // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ํ—ˆ์šฉ
          	.antMatchers("/employee.do").hasAnyRole("S", "A") // "/employee.do" ๊ฒฝ๋กœ๋Š” "A","S" ์—ญํ• ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
            .antMatchers("/*.do").authenticated() // ".do"๋กœ ๋๋‚˜๋Š” ๋ชจ๋“  ์š”์ฒญ์€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
            .anyRequest().permitAll() // ๊ทธ์™ธ ์š”์ฒญ์€ ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅ
    
            .and()
            .formLogin() // ๋กœ๊ทธ์ธ ์„ค์ •
            .loginPage("/login.user") // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์„ค์ •(์‚ฌ์šฉ์ž ์ •์˜)
            .usernameParameter("employeeId") //๋กœ๊ทธ์ธํผ์—์„œ ์•„์ด๋””ํ•„๋“œ name ๊ฐ’์„ employeeId ๋กœ ์„ค์ •
            .passwordParameter("employeePw") //๋กœ๊ทธ์ธํผ์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธํ•„๋“œ name ๊ฐ’์„ employeePw ๋กœ ์„ค์ •
            .loginProcessingUrl("/loginForm") //๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€๋กœ์ฑ„ ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค๋กœ ๋กœ๊ทธ์ธ์„ ์—ฐ๊ฒฐ
            .defaultSuccessUrl("/main.do") //๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์ด๋™ํ•  URL
            .failureUrl("/login.user?err=true") // ๋กœ๊ทธ์ธ ์‹คํŒจ์‹œ ์ด๋™ URL
    //        .failureHandler(new CustomAuthenticationFailureHandler()) // ์ปค์Šคํ…€ ์‹คํŒจ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก
    
            .and()
            .logout() // ๋กœ๊ทธ์•„์›ƒ ์„ค์ •
            .logoutUrl("/logout") // ๋กœ๊ทธ์•„์›ƒ ํŽ˜์ด์ง€ ์„ค์ •
            .logoutSuccessUrl("/login.user") // ๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต ์‹œ ์ด๋™ํ•  URL
            .invalidateHttpSession(true)// ๋กœ๊ทธ์•„์›ƒ ์‹œ ์„ธ์…˜ ๋ฌดํšจํ™”
            .deleteCookies("JSESSIONID") // ๋กœ๊ทธ์•„์›ƒ ์‹œ ์ฟ ํ‚ค ์‚ญ์ œ (JSESSIONID)
    
            .and()
            .exceptionHandling() //์ธ์ฆ(Authentication) ๋˜๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ(Authorization) ๊ด€๋ จ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
            .accessDeniedPage("/accessDenied"); //์ ‘๊ทผ๊ถŒํ•œ์ด ์—†๋Š” ์š”์ฒญ์€ accessDenied ํŽ˜์ด์ง€๋กœ ์ด๋™
    
        return http.build();
      }
    
    }

     

     

     

     

     

    ํ•ด๋‹น ํ˜•ํƒœ๋ฅผ ์‹œํ๋ฆฌํ‹ฐ์—๊ฒŒ ์ „๋‹ฌํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ์ฒด ๊ตฌํ˜„์„ ๋จผ์ €ํ•ด์ค˜์•ผํ•œ๋‹ค.

     

     

     

    4. UserDetailsService ๊ตฌํ˜„ (์ธ์ฆ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ) 

     

     

    package com.project.tobe.serviceimpl;
    
    import com.project.tobe.entity.Employee;
    import com.project.tobe.repository.EmployeeRepository;
    //import com.project.tobe.security.EmployeeDetails;
    import com.project.tobe.security.EmployeeDetails;
    import lombok.RequiredArgsConstructor;
    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;
    
    @Service
    @RequiredArgsConstructor
    //loginProcessingUrl()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด loadUserByUsername์ด ์ž๋™ ํ˜ธ์ถœ๋จ
    public class PrincipalDetailsService implements UserDetailsService {
    
      private final EmployeeRepository employeeRepository;
    
      /**
       * ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””(username)๋ฅผ ํ†ตํ•ด ์ธ์ฆ ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
       * ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋กœ๊ทธ์ธ ์š”์ฒญ ์‹œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
       */
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
          // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ username์„ ๊ธฐ์ค€์œผ๋กœ Employee ๊ฐ์ฒด ์กฐํšŒ
        Employee employee = employeeRepository.findById(username)
            .orElseThrow(() -> {
                // username ์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์œผ๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ
              return new UsernameNotFoundException("ํ•ด๋‹น ์œ ์ €๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
            });
        // ์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋ฅผ EmployeeDetails (์œ ์ € ๊ฐ์ฒด) ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜
        // ์‹œํ๋ฆฌํ‹ฐ์„ธ์…˜( new Authentication(new MyUserDetails()) ) ํ˜•์‹์œผ๋กœ ์ „๋‹ฌ ๋จ
        return new EmployeeDetails(employee);
      }
    }

     

     

     

    5.UserDetails ๊ตฌํ˜„ (์œ ์ € ๊ฐ์ฒด ๋งŒ๋“ค๊ธฐ)

     

    package com.project.tobe.security;
    
    import com.project.tobe.dto.EmployeeDTO;
    import com.project.tobe.entity.Employee;
    import com.project.tobe.dto.UserRole;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class EmployeeDetails implements UserDetails {
    
      private Employee employee;
      private EmployeeDTO employeedto;
    
      public EmployeeDetails(Employee employee) {
        this.employee = employee;
      }
    
      // ๊ถŒํ•œ ๊ด€๋ จ ์ž‘์—…์„ ํ•˜๊ธฐ ์œ„ํ•œ role return
      @Override
      public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
    
        try {
          UserRole role = UserRole.valueOf(employee.getAuthorityGrade());  // authorityGrade๋ฅผ UserRole Enum์œผ๋กœ ๋ณ€ํ™˜
          authorities.add(new SimpleGrantedAuthority("ROLE_" + role.name()));  // ROLE_ ์ ‘๋‘์‚ฌ ๋ถ™์ด๊ธฐ
        } catch (IllegalArgumentException e) {
          // authorityGrade ๊ฐ’์ด UserRole Enum์— ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ์˜ ์ฒ˜๋ฆฌ
          System.err.println("Invalid authorityGrade value: " + employee.getAuthorityGrade());
        }
    
        return authorities;
      }
    
      // get Password ๋ฉ”์„œ๋“œ
      @Override
      public String getPassword() {
        return employee.getEmployeePw();
      }
    
      // get Username ๋ฉ”์„œ๋“œ (์ƒ์„ฑํ•œ User์€ loginId ์‚ฌ์šฉ)
      @Override
      public String getUsername() {
        return employee.getEmployeeId();
      }
    
      public String getNickname() {
        System.out.println(employee.toString());
        System.out.println(employee.getEmployeeName());
        return employee.getEmployeeName();
      }
    
    
      public String getUserAuthorityGrade() {
        return employee.getAuthorityGrade();
      }
    
    //  public String getUserAuthorityName() {
    //    System.out.println(employeedto.getAuthorityName());
    //    return employeedto.getAuthorityName();
    //  }
    
    
      // ๊ณ„์ •์ด ๋งŒ๋ฃŒ ๋˜์—ˆ๋Š”์ง€ (true: ๋งŒ๋ฃŒX)
      @Override
      public boolean isAccountNonExpired() {
        return true;
      }
    
      // ๊ณ„์ •์ด ์ž ๊ฒผ๋Š”์ง€ (true: ์ž ๊ธฐ์ง€ ์•Š์Œ)
      @Override
      public boolean isAccountNonLocked() {
        return true;
      }
    
      // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ (true: ๋งŒ๋ฃŒX)
      @Override
      public boolean isCredentialsNonExpired() {
        return true;
      }
    
      // ๊ณ„์ •์ด ํ™œ์„ฑํ™”(์‚ฌ์šฉ๊ฐ€๋Šฅ)์ธ์ง€ (true: ํ™œ์„ฑํ™”)
      @Override
      public boolean isEnabled() {
        return true;
      }
    }

     

     

    6. ์ธ์ฆ ๊ฐ์ฒด ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

     

     

    ์ปจํŠธ๋กค๋Ÿฌ

      //ํ˜„์žฌ ๋กœ๊ทธ์ธ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
      @GetMapping("/user-info")
      public ResponseEntity<?> employeeUserInfo(Authentication authentication) {
    
        if (authentication == null) { // ์ธ์ฆ ๊ฐ์ฒด๊ฐ€ ์—†๋‹ค๋ฉด
          return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
        }
    
        //์ธ์ฆ๊ฐ์ฒด ์•ˆ์— principal๊ฐ’์„ ์–ป์œผ๋ฉด ์œ ์ €๊ฐ์ฒด๊ฐ€ ๋‚˜์˜ด
        EmployeeDetails user = (EmployeeDetails)authentication.getPrincipal();
        System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (์ด๋ฆ„):" + user.getNickname());
        System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (์•„์ด๋””):" + user.getUsername());
        System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (๋น„๋ฐ€๋ฒˆํ˜ธ) :" + user.getPassword());
        System.out.println("๋กœ๊ทธ์ธ ์ •๋ณด (๊ถŒํ•œ) :" + user.getUserAuthorityGrade());
    
        // ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๊ถŒํ•œ์„ ๋ฐ˜ํ™˜
        Map<String, Object> response = new HashMap<>();
        response.put("userName", user.getNickname());
        response.put("userId", user.getUsername());
        response.put("userPw", user.getPassword());
        response.put("userAuthorityGrade", user.getUserAuthorityGrade());
        
        return ResponseEntity.ok(response);
      }

     

     

     

     

     

    view (ํƒ€์ž„๋ฆฌํ”„)

     

    <html xmlns:th="http://www.thymeleaf.org"  
    		  xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
              
              .
              .
              .
              
    
    <!-- ์ธ์ฆ ๋˜์—ˆ๋‹ค๋ฉด true, ์•„๋‹ˆ๋ฉด false๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. if๋ฌธ์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. -->
    <div sec:authorize="isAuthenticated()"> 
        <span>[[${#authentication.principal.getUsername}]]</span><br/>
        <span>[[${#authentication.principal.getNickname}]]</span><br/>
        <span>[[${#authentication.principal.getUserAuthorityGrade}]]</span><br/>
    </div>

     

     

     

    view (๋ฆฌ์•กํŠธ)

     

        useEffect(() => {
    
            const fetchUserId = async () => {
                try {
                    const response = await axios.get('/employee/user-info');
                    console.log("์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด" + response.data);
                } catch (error) {
                    console.error("Error fetching user ID:", error);
                }
            };
    
            fetchUserId();
    
        }, []);

     

     

     

    7. ์„ธ์…˜ - ์ฟ ํ‚ค ๋ฐฉ์‹