티스토리 뷰

반응형

Spring Security

로그인하여 세션에 정보를 저장하고, 유저 권한에 따른 URI 접근까지 Spring Security를 이용하면 간편하게 구현 할 수있다.. 라고 배웠지만, 공부하기가 너무 어렵다. 이런저런 예제를 따라해보기도 했고, Spring Boot와 Spring의 시큐리티 설정이 얼마나 다른지도 몰라 예제를 찾아보는 것도 힘들었다. 2일간 삽질하여 얼추 원하는 방향으로 구현 할 수 있었다. (막상 완성된 예제를 보니 아주 어렵지는 않았지만 삽질의 주된 원인은 어떤 곳이 문제인지 알 수가 없었기 때문이었다.) 시큐리티는 개발하면서 지속적으로 공부를 해봐야 정교하게 구현 할 수 있을 것 같다. 현재까지 구현한 시큐리티는 아래와 같다.

WebSecurityConfigureAdapter

WebSecurityConfigureAdapter을 상속받은 시큐리티 설정 클래스를 선언한다. 이 클래스에서는 유저 권한에 따른 URI 접근을 설정한다. 예를들어 /admin 으로 시작하는 URI는 "ADMIN" 권한이 있는 유저만 접근 가능하고, 인덱스 페이지나 회원 가입 페이지는 모든 사용자가 접근 가능하다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resoures/**", "/static/**", "/css/**", "js/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/","/users").permitAll() //모든 사용자가 접근가능
                .antMatchers("/books").hasRole("USER")
                .antMatchers("/admin").hasRole("ADMIN")//ADMIN 권한이 있는경우만 접근 가능
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/session")
                .loginProcessingUrl("/session")
                .permitAll()
                .and()
            .logout()
                .logoutSuccessUrl("/")
                .permitAll();
    }
}

Security 코어의 userdetails

스프링 코어 (org.springframework.security.core.userdetails.User)의 User 클래스를 상속받은 UserDetails 설정을 한다. 이 클래스는 필수적이진 않지만 시큐리티 코어의 User와 실제 서비스 하고 있는 User 도메인과의 차이점을 이 클래스로 상쇄시켜줄 수 있다.

public class CustomUserDetails extends User {
    private String nickname; // 사람이름
    private Long id;

    public CustomUserDetails(String email, String password, Collection authorities) {
        super(email, password, true, true, true, true, authorities);
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail(){
        return super.getUsername();
    }
}

UserDetailsService

UserDetailsService를 구현한 클래스를 선언한다. 이 클래스는 실제 서비스 되고 있는 회원들의 정보를 가져와서, 시큐리티가 알 수 있는 UserDetails로 변환하여 반환하는 작업을 한다.

@Component
@RequiredArgsConstructor
public class UserDetailServiceImpl implements UserDetailsService {
    private final UserService userService;

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userService.getUser(email);//실제 저장된 유저 정보 가져오기
        if (user == null) {
            throw new UsernameNotFoundException(email + " is not found");
        }

        Collection<grantedauthority> authorities = new ArrayList<>();
        for(Role role : user.getRoles()){
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        }
        //userDetails로 변환
        CustomUserDetails userDetails = new CustomUserDetails(email, user.getPassword(), authorities);
        userDetails.setNickname(user.getNickname());
        userDetails.setId(user.getId());
        return userDetails;
    }
}

BCryptPasswordEncoder

BCryptPasswordEncoder클래스를 빈으로 생성한다. 이 클래스는 비밀번호를 DB상의 그대로 저장하지 않고 암호화 시켜서 저장 시켜주는 역할을 한다.

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
}


반응형
댓글