Spring/Spring Security

시큐리티 로그인과 권한 처리

Ynghan 2023. 9. 14. 02:42

시큐리티 로그인

1. 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.

@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
                .defaultSuccessUrl("/");
    }
}

formLogin().loginPage().loginProcessingUrl("/login").defaultSuccessUrl()

2. 로그인 진행이 완료되면 session을 만들어 줍니다.

시큐리티 별도의 session 공간이 있음.
  - Security ContextHolder라는 key 값으로 session을 저장함.
  - 그런데 시큐리티 session에 저장 가능한 객체 타입이 정해져 있음. → "Authentication 객체"
  - 우리는 이 Authentication 객체에 User 정보를 담아야 하는데 그러기 위해서는 UserDetails 객체 타입으로 저장해야함.

2-1. UserDetails 객체 만들기

이렇게 UserDetails를 상속받아서 같은 타입 객체로 만들 수 있음.

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;
    }

	...
}

2-2. Authentication 객체 만들기

SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...
        http.authorizeRequests()
                ...
                .loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
                ...
    }
}
PrincipalDetailsService.java
@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;


    // 시큐리티 session(내부 Authentication(내부 UserDetails))
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("username :" + username);
        User userEntity = userRepository.findByUsername (username);
        if(userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}

/login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는 loadByUsername 함수가 실행된다.

이 때, 파라미터로 username 정보를 가지고 온다.

loadByUsername()의 파라미터로 넘어오는 username은 loginForm에서 넘어오는 username이다.

loginForm.html
    <form action = "/login" method="POST">
        <input type="text" name="username" placeholder="Username" /> <br/>
        <input type="password" name="password" placeholder="Password" /> <br/>
        <button>로그인</button>
    </form>

그렇게 username을 받아서 findByUsername()을 통해 해당 username을 가진 객체가 userRepository에 있는지 탐색한 후 있는 경우에 UserDetails 타입 객체를 리턴하는데,
이 때, 시큐리티 세션 내부에 해당 UserDetails 객체를 포함하는 Authentication 타입 객체가 들어가게된다.


시큐리티 권한 처리

1. 비밀번호 인코딩

회원가입에서 기본 비밀번호를 인코딩하여 DB에 저장하여야 한다. 이 때, encodePwd() 함수를 IoC에 등록해준다.

indexController.java
@Controller //View를 리턴하겠다!!
public class indexController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;    
    
    
    @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);// 회원가입 잘됨. 비밀번호 : 1234 => 시큐리티로 로그인을 할 수 없음. 이유는 패스워드가 암호화가 안되어있기때문이다.
        return "redirect:/loginForm";
    }
    
    ...
}
SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해준다.
    @Bean
    public BCryptPasswordEncoder encodePwd() {
        return new BCryptPasswordEncoder();
    }

	...
}

2. api 별 권한 처리 (1)

http.authorizeRequests()

.antMatchers() 함수를 통해 특정 api url 설정

  • .authenticated() → 시큐리티 인증을 완료한 모든 사용자 허용
  • .access() → 인자에 hasRole() 메서드를 사용하여 해당하는 역할의 사용자에게만 권한을 허용

.anyRequest()를 통해 다른 모든 api url 설정

  • .permitAll() → 모든 사용자에게 인증없이 허용
SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
                .defaultSuccessUrl("/");
    }
}

3. api 별 권한 처리 (2)

SecurityConfig.java
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 됩니다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	...
}
indexController.java
@Controller //View를 리턴하겠다!!
public class indexController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

	...
    
    @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 "데이터정보";
    }
}

@EnableGlobalMethodSecurity(securedEnabled = true)
: @Secured() 어노테이션을 통해 하나의 역할에 시큐리티 권한을 허용하는 기능을 사용할 수 있다.

@EnableGlobalMethodSecurity(prePostEnabled = true)
: @PreAuthorize() 어노테이션을 통해 복수개의 역할에 시큐리티 권한을 허용하는 기능을 사용할 수 있다.