[Spring Boot] Spring-Security AuthenticationProvider, AuthenticationManager

이맑음·2021년 10월 7일
0

Spring Boot

목록 보기
14/21
post-thumbnail

fastcampus 웹 개발 마스터 초격차 패키지를 수강하며 정리한 내용들입니다.

Authentication(인증)

  • Authentication은 인증 결과만 저장하는 것이 아니고, 인증을 하기 위한 정보와 인증을 받기 위한 정보가 하나의 객체에 동시에 들어 있다. AuthenticationProdiver를 통해 어떤 인증에 대해서 허가를 할 것인지 직접 보고 인증을 해주는 방식을 가지고 있다.
  • Authentication을 구현한 객체들은 Token(=통행권) 이라는 이름의 객체로 구현된다.
  • Authentication 객체는 SecurityContextHolder를 통해 세션의 유무에 상관없이 언제든 접근할 수 있도록 필터체인에서 보장해준다.

AuthenticationProvider(인증제공자)

  • 인증제공자는 Authentication을 받아서 인증을 하고 그 결과를 다시 Authentication 객체로 돌려준다.
  • 이때 인증제공자는 어떤 인증에 대해 통행권을 발급해 줄 것인지 support()라는 메소드를 통해 AuthenticationManager에게 알려줘야한다.

AuthenticationManager(인증관리자)

  • 인증제공자들을 관리하는 인터페이스가 인증관리자이고, 인증 관리자를 구현한 객체가 ProviderManager이다.
  • 개발자가 AuthenticationManager를 제공하지 않으면, AuthenticationManagerFactoryBean에서 DaoAuthenticationProvider를 기본 인증자로 등록한 AuthenticationManager를 만들어 제공한다.

예제 - Authentication Token과 Manager 직접 생성

  • student/Student
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Student {

    private String id;
    private String username;
    private Set<GrantedAuthority> role;
}
  • student/StudentAuthenticationToken
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class StudentAuthenticationToken implements Authentication {
    // student가 받게 될 통행증

    private Student principal; //인증된 결과, 인증 대상
    private String credentials; //인증 받기 위해 필요한 정보
    private String details;
    private boolean authenticated;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return principal == null ? new HashSet<>() : principal.getRole();
    }

    @Override
    public String getName() {
        return principal == null ? "": principal.getUsername();
    }
}
  • student/StudentManager
@Component
public class StudentManager implements AuthenticationProvider, InitializingBean {
    // 통행증을 발급할 provider manager
    // 이러한 provider manager는 config 파일에서 등록해야 사용할 수 있다.

    private HashMap<String, Student> studentDB = new HashMap<>();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // authentication으로 입력된 토큰을 UsernamePasswordAuthenticationToken로 형변환 해준다.
        // 그래야 student manager가 student 통행증을 발급해주기 때문에
        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
        if(studentDB.containsKey(token.getName())){
            Student student = studentDB.get(token.getName());
            return StudentAuthenticationToken.builder()
                    .principal(student)
                    .details(student.getUsername())
                    .authenticated(true)
                    .build();
        }
        return null;
        // 가져올 토큰값이 없다면 리턴 값을 null로 줘야한다. 
        // false로 주면 인증에 대해 임의의 핸들링을 한 것이기 때문이다.
    }

    @Override
    public boolean supports(Class<?> authentication) {
        // UsernamePasswordAuthenticationToken을 받으면 검증을 해주는 provider manager로 동작
        return authentication == UsernamePasswordAuthenticationToken.class;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    // Set.of는 Java8 이후에 나온 기능이라 자바 버전을 변경해야한다.
        Set.of(
                new Student("hong", "홍길동", Set.of(new SimpleGrantedAuthority("ROLE_STUDENT"))),
                new Student("kang", "강아지", Set.of(new SimpleGrantedAuthority("ROLE_STUDENT"))),
                new Student("rang", "호랑이", Set.of(new SimpleGrantedAuthority("ROLE_STUDENT")))
        ).forEach(s->
                studentDB.put(s.getId(), s)
        );
    }
}
  • config/SecurityConfig
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final StudentManager studentManager;

    public SecurityConfig(StudentManager studentManager, TeacherManager teacherManager) {
        this.studentManager = studentManager;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(studentManager);
        auth.authenticationProvider(teacherManager);
        // providermanager로 StudentManager를 사용하도록 설정
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CustomLoginFilter filter = new CustomLoginFilter(authenticationManager());
        http
                .authorizeRequests(request->
                        request.antMatchers("/", "/login").permitAll()
                                .anyRequest().authenticated()
                )
                .formLogin(
                        login->login.loginPage("/login")
                                .permitAll()
                                .defaultSuccessUrl("/", false)
                                .failureUrl("/login-error")
                )
                .logout(logout-> logout.logoutSuccessUrl("/"))
                .exceptionHandling(e->e.accessDeniedPage("/access-denied"))
                ;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                ;
    }
}

예제2 - UsernamePasswordAuthenticationFilter를 직접 custom

  • config/CustomLoginFilter
public class CustomLoginFilter extends UsernamePasswordAuthenticationFilter {
    // UsernamePasswordAuthenticationFilter를 직접 custom

    public CustomLoginFilter(AuthenticationManager authenticationManager){
        super(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = obtainUsername(request);
        username = (username != null) ? username : "";
        username = username.trim();
        String password = obtainPassword(request);
        password = (password != null) ? password : "";
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

-config/SecurityConfig

@Override
    protected void configure(HttpSecurity http) throws Exception {
        CustomLoginFilter filter = new CustomLoginFilter(authenticationManager());
        http
                .authorizeRequests(request->
                        request.antMatchers("/", "/login").permitAll()
                                .anyRequest().authenticated()
                )
                // 기존 코드에서 .formlogin부분을 지우고 작성
                .addFilterAt(filter, UsernamePasswordAuthenticationFilter.class)
                .logout(logout-> logout.logoutSuccessUrl("/"))
                .exceptionHandling(e->e.accessDeniedPage("/access-denied"))
                ;
    }
profile
하삐

0개의 댓글