Spring Security) Form Login 적용하기

Dokuny·2022년 1월 13일
0

Spring Security

목록 보기
7/7

뭔가 깔끔하게 정리된 곳이 없는 것 같아서 개인적인 용도로 정리해보고자 한다.

우선 스프링 시큐리티의 설정을 담당할 Config 파일을 만든다.
자세한 설명은 코드 주석으로 적어놓았다.

부가적으로 이전에 올려둔 Spring Security 기초를 참고하면 좋을 것 같다.

기본적으로 회원 관련(ex.Member,User 등 직접 만든 이용자 엔티티) 엔티티가 작성 되어 있는 것을 가정하고 작성한다.

1. Security Config 작성

@EnableWebSecurity 적용 및 WebSecurityConfigurerAdapter를 상속한 Config 파일을 만든다.

밑의 설정들은 테스트하고 그러느라 임의로 설정해둔 것이니 참고만 할 것.

WebSecurityConfig.class

@RequiredArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // UserDetailsService를 구현한 객체를 DI
    private final PrincipalDetailsService principalDetailsService;

    // 비밀번호를 암호화할 PasswordEncoder를 빈 등록, 여기서는 BCryptPasswordEncoder 사용
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // AuthenticationManager 세팅
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 커스텀한 AuthenticationProvider,UserDetailsService를 AuthenticationManager에 세팅할 수 있다.
        // 부가적으로 비밀번호 검증에 사용할 PasswordEncoder도 여기서 등록해준다.
        auth.userDetailsService(principalDetailsService).passwordEncoder(passwordEncoder());

    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        // 정적 자원에 대해서는 Security 적용 X -> 정적 자원들은 굳이 시큐리티 필터를 탈 필요가 없기 때문에 자원 낭비를 줄이기 위해서 설정
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());

        // 이거 설정할 때 아래와 같은 식으로 설정도 가능하다.
//      web.ignoring()
//              .antMatchers("/resources/**")
//              .antMatchers("/css/**");
    }

    // 시큐리티 설정
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // 일단 csrf는 당장 사용을 안하니 꺼둔다.
                .cors().disable() // cors 도 꺼두자. 나중에 api로 프론트 서버랑 백엔드 서버 왓다갔다할때 설정 해두는 방법이 따로 있다.
                .authorizeRequests() // 보호된 리소스 URI 설정
                    .antMatchers("/auth/**").permitAll() // permitAll은 권한이 하나도 없어도 접근 가능능
                   .antMatchers("/admin/**").hasRole(Role.ADMIN.name()) // 권한별 접근 가능한 uri 지정
                    .antMatchers("/member/**").hasRole(Role.MEMBER.name())
                .antMatchers("/").authenticated() // authenticated는 아무 권한이나 있어야 접근가능
                .and()
                .formLogin()  // 폼 로그인 사용 설정
                    .loginPage("/auth/login") // 로그인 페이지 경로, 앞으로 로그인은 이 경로에서 수행된다.
                    .loginProcessingUrl("/auth/login")  // 로그인 처리 경로, 로그인 form의 action과 일치시켜주어야한다.
                    .usernameParameter("email")  // form태그에서 <input name = username>이 시큐리티의 id 기본 값인데 이를 바꾸고 싶으면 이렇게 쓰면 된다.
                    .passwordParameter("pw")     // 위와 마찬가지로 password의 name을 바꾸고 싶을 때 사용
                    .defaultSuccessUrl("/",false) // 로그인 완료 후 이동할 기본 url 설정, alwaysUse를 false로 해두면 로그인을 요청하기 이전 페이지로 이동한다.
                    .failureUrl("/auth/login") // 로그인 실패 시 이동할 url
                    .permitAll()// 혹시라도 로그인이 권한에 막히거나 그럴 수 있으니 permitAll을 걸어둔다.
                .and()
                .logout() // 로그아웃 설정
                .logoutRequestMatcher(new AntPathRequestMatcher("/auth/logout")) // 로그아웃 url 설정
                    .logoutSuccessUrl("/auth/login") // 로그아웃 성공시 돌아갈 url
                    .invalidateHttpSession(true);  // 저장된 세션 삭제
    }
}

2. UserDetails를 구현한 클래스 작성

이거는 구현안하고 User 라는 객체를 사용해도 된다.

public class PrincipalDetails implements UserDetails {

    private Member member;  // 콤포지션

    // 생성자를 통해서 유저 정보(Member)를 넣어준다.
    public PrincipalDetails(Member member) {
        this.member = member;
    }

    // 해당 User의 권한을 리턴하는 곳.
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collect = new ArrayList<>();

        // 권한을 넣어준다. 권한이 여러개라면 여러개 넣어줄 수도 있다.
        collect.add(new SimpleGrantedAuthority(member.getRole().name()));

        return collect;
    }
    
    @Override
    public String getPassword() {
        return member.getPw();
    }

    @Override
    public String getUsername() {
        return member.getEmail();
    }

    // 아래의 녀석들은 기본 값이 다 false로 되어있는데 true로 바꿔두자. 지금 당장 쓸일이 없기 때문, 만약 쓸일이 생기면 로직넣어서 true,false 골라주면 된다.
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 사용가능한지 물어보는데 이거를 사용해서 로그인 한지 얼마 지나면 계정이 잠기도록 하는 로직을 짤 수도 있다. false로 바꾸면된다.
    @Override
    public boolean isEnabled() {
        return true;
    }
}

3. UserDetailsService를 구현한 클래스 작성

// 시큐리티에 설정에서 loginProcessingUrl 을 통해 요청이 오면
// 자동으로 UserDetailsService 타입으로 IoC에 등록되어 있는 빈을 찾은 후 loadUserByUsername 메소드가 실행된다.
// Authentication에 UserDetails를 집어넣어주는 작업을 하는 Service
@RequiredArgsConstructor
@Service
public class PrincipalDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

        // 넘어온 username(email)으로 레파지토리에 유저가 있는지 확인.
        Member member = memberRepository.findMemberByEmail(email);

        // 있으면 만들어둔 UserDetails 구현 객체에 넣어준다.
        if (member != null) {
            // 반환된 구현 객체는 Security Session 내부의 Authentication 객체 내부의 UserDetails로 들어간다.
            return new PrincipalDetails(member);
        }

        return null;
    }
}
profile
모든 것은 직접 경험해보고 테스트하자

0개의 댓글