UsernamePasswordAuthenticationFilter를 통한 Authentication 메커니즘 살펴보기
- AuthenticationProvider가 처리 가능한 Authentication에 대해 알려주는 support 메소드를 지원하고, authenticate() 에서 인증 로직을 처리해줌
코드 구현
- 통행증을 위한 Authentication 클래스(
StudentAuthenticationToken
) 정의, principal(Student
)에 들어갈 class 정의
@Data @AllArgsConstructor @NoArgsConstructor @Builder public class StudentAuthenticationToken implements Authentication { 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(); } } @Data @AllArgsConstructor @NoArgsConstructor @Builder public class Student { private String id; private String userName; private Set<GrantedAuthority> role; }
- 그것을 다룰 AuthenticationProvider 클래스 정의
@Component public class StudentManager implements AuthenticationProvider, InitializingBean { private HashMap<String, Student> studentDB = new HashMap<>(); @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { 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; // authentication token을 false로 해서 넘기게 되면 // authentication을 핸들링 했다는 것이 되기 때문에 문제가됨. /// 처리할 수 없는 authentication은 null로 넘겨야 함 // 그래야 그다음 filter에서 문제없이 처리가 가능함 } @Override public boolean supports(Class<?> authentication) { return authentication == UsernamePasswordAuthenticationToken.class; } @Override public void afterPropertiesSet() throws Exception { Set.of( new Student("hong", "홍길동", Set.of(new SimpleGrantedAuthority("ROLE_USER"))), new Student("kang", "강아지", Set.of(new SimpleGrantedAuthority("ROLE_USER"))), new Student("rang", "호랑이", Set.of(new SimpleGrantedAuthority("ROLE_USER"))) ).forEach(s-> studentDB.put(s.getId(), s)); } }
WebSecurityConfigurerAdapter
를 상속받은 클래스에서 authenticationProvider로 등록해주기
@EnableWebSecurity(debug = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { private final StudentManager studentManager; public SecurityConfig(StudentManager studentManager) { this.studentManager = studentManager; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(studentManager); } }
진행 순서
- http.formLogin()을 통해서 로그인 시,
UsernamePasswordAuthenticationFilter
의 attemptAuthentication()으로 넘어오게 됨 - this.getAuthenticationManager() → authRequest의 타입인 UsernamePasswordAuthenticationToken을 처리할 수 있는 AuthenticationProvider를 반환하게됨
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } 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); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
- UsernamePasswordAuthenticationToken을 처리할 수 있는(supports메소드에서 정의) AuthenticationProvider → StudentManager
- StudentManager의 authenticate() 에서 UsernamePasswordAuthenticationToken을 StudentAuthenticationToken으로 변환하여 반환