2.스프링 시큐리티 [로그인 만들기]

dasd412·2022년 1월 31일
0

포트폴리오

목록 보기
13/41

로그인을 하려면 상태를 관리할 수 있는 세션이 필요하다.


스프링 시큐리티의 경우 ‘Secruity Session’을 제공한다.
이 시큐리티 세션은 Security Context Holder 내에서 관리된다. 관계는 다음과 같다.


1.설정 추가하기

가장 먼저, configure()메서드에 loginProcessingUrl(”/login”)과 defaultSuccessUrl(”/”)을 추가한다.

loginProcessingUrl()은 “/login” url이 호출되면 스프링 시큐리티가 인터셉트해서 대신 로그인을 진행해준다.
해당 로그인이 완료되면, Security Context Holder 내에 시큐리티 세션을 만들어준다.

defaultSuccessUrl()은 로그인 후 리다이렉트되는 주소를 지정해준다.
단, 사용자가 진입하고자 했던 url이 있으면 해당 url로 보내준다.
예를 들어, 사용자가 /api/diary/a 라는 url을 접근하고자 했으면, 로그인 진행 후에 해당 url로 리다이렉트 해준다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeRequests()
                .antMatchers("/api/diary/**").authenticated()
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login")// 로그인 주소가 호출이 되면 시큐리티가 인터셉트해서 대신 로그인 진행해줌. 이렇게 하면 컨트롤러에서 /login 매핑할 필요가 없음.
                .defaultSuccessUrl("/");//로그인 하게 되면 "/" url 로 이동됨., 진입하고자 했던 url이 있었으면 해당 url로 이동함.

    }
}

2.UserDetails 구현하기

Security Context Holder 내에 들어갈 수 있는 타입은 Authentication 타입 뿐이다.

그런데, 이 Authentication 타입 객체 내에는 유저의 정보가 필요하다.

이 유저 정보를 어떻게 Authentication 객체에 넣을 수 있을까?

방법은 UserDetails 인터페이스를 구현한 객체를 넣으면 된다.아래의 PrincipalDetails는 해당 인터페이스를 구현한 객체이다.
하지만 PrincipalDetails 에는 “유저(또는 작성자)의 정보”가 존재하지 않는다. 그래서 Composition(합성)을 이용해 Writer 엔티티를 포함시킨다.

//Authentication 객체에 넣기 위한 래퍼 객체
public class PrincipalDetails implements UserDetails {

    //실제 엔티티를 참조함.
    private final Writer writer;

    public PrincipalDetails(Writer writer) {
        this.writer = writer;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return writer.getRole().name();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return writer.getPassword();
    }

    @Override
    public String getUsername() {
        return writer.getName();
    }

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

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

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

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

3.UserDetailsService 구현하기

해결해야 할 문제

  1. 회원 로그인 시, 정보가 일치하지 않아 로그인이 안될 때의 예외 캐치 필요

  2. 회원 가입 시, 유니크 조건을 깨뜨릴 경우의 예외 캐치 필요→회원 가입할 때 이미 있는 엔티티인지, 또는 프로퍼티인지 확인하는 로직 추가 필요.


4.인가 처리하기 (예시)

먼저 Security 설정용 클래스에 EnableGlobalMethodSecurity 어노테이션을 부착한다.

securedEnabled = true를 적용하면 @secured 어노테이션을, prePostEnabled =true를 적용하면 @preauthorize 어노테이션을 어플리케이션 전체에서 사용할 수있게 된다.

@Controller
public class IndexController {
		@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 "데이터 정보";
    }
}

그 다음, 해당 어노테이션들을 부착한다.
두 어노테이션 모두, 메소드를 실행 하기 전에 권한이 있는지를 먼저 체크한다.
차이점이 있다면, PreAuthorize는 표현식을 사용할 수 있다는 것이다. (PreAuthorize가 Secured보다 진보된 어노테이션이라 한다.)

권한이 없는 사용자가 해당 메소드의 url에 접근하려 한다면 403 에러를 뱉는다.

@Controller
public class IndexController {
		@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 "데이터 정보";
    }
}
profile
시드 레벨 스타트업의 2호 직원으로서 백엔드 시스템의 70%를 설계 및 개발하였고, TIPS 5억 투자 유치에 기여한 서버 개발자입니다. (Go/Python/MSA/Spring)

0개의 댓글