Spring Security
는 Spring 서버에서 필요한 인증 및 인가를 처리하기 위한 많은 기능을 제공해주는 프레임워크이다.
- Spring Security는 기본 로그인 기능을 가지고있다.
- Username : user
- Password : Spring 로그에서 확인해야함. (서버 시작시마다 달라짐)
- Spring Security는
FilterChainProxy
를 통해 인증 및 인가를 처리하기위한 로직을 구현한다.
- Form Login 기반 인증방식은 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환하는 형태이다.
UsernamePasswordAuthenticationFilter
는 Spring Security의 필터인AbstractAuthenticationProcessingFilter
를 상속한 Filter이다.- 인증 과정
- 사용자가 username과 password를 제출하면
UsernamePasswordAuthenticationToken
을 만들어AuthenticationManager
에게 넘겨 인증을 시도한다.- 실패하면
SecurityContextHolder
를 비웁니다.- 성공하면
SecurityContextHolder
에Authentication
를 세팅한다.
SecurityContext
는 인증이 완료된사용자의 상세 정보(Authentication)
를 저장한다.SecurityContext
는SecurityContextHolder
로 접근할 수 있다.SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities); context.setAuthentication(authentication); // SecurityContext 에 Authentication 를 저장 SecurityContextHolder.setContext(context);
- 현재 인증된 사용자를 나타내며 SecurityContext에서 가져올 수 있다.
- principal : 사용자를 식별한다.
- Username/Password 방식으로 인증할 때 일반적으로
UserDetails
인스턴스이다.- credentials : 주로 비밀번호이다. (대부분 사용자 인증에 사용한 후 비운다)
- authorities : 사용자에게 부여한 권한을
GrantedAuthority
로 추상화하여 사용한다.<UserDetails> @Override public Collection<? extends GrantedAuthority> getAuthorities() { UserRoleEnum role = user.getRole(); String authority = role.getAuthority(); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority); Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(simpleGrantedAuthority); return authorities; } Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
- Spring Security에서 사용자의 정보를 담는 인터페이스이다.
- Spring Security에서 사용자의 정보를 불러오기 위해서 구현해야 하는 인터페이스로 기본 오버라이드 메서드들은 아래와 같다.
- Spring Security에서 유저의 정보를 가져오는 인터페이스이다.
- Spring Security에서 유저의 정보를 불러오기 위해서 구현해야하는 인터페이스로 기본 오버라이드 메서드는 아래와 같다.
Spring Security
사용 전
Spring Security
사용 후
- 로그인 시도
- 로그인 시도할 username, password 정보를
HTTP body
로 전달 (POST 요청)
- 로그인 시도 URL 은
WebSecurityConfig
클래스에서 변경 가능@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // CSRF 설정 http.csrf((csrf) -> csrf.disable()); http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정 .anyRequest().authenticated() // 그 외 모든 요청 인증처리 ); // 로그인 사용 http.formLogin((formLogin) -> formLogin // 로그인 처리할 URL 설정 (POST /api/user/login) .loginProcessingUrl("/api/user/login").permitAll() ); return http.build(); }
UserDetailsService
에게username
을 전달하고 회원상세 정보를 요청
- 회원 DB 에서 회원 조회. 회원 정보가 존재하지 않을 시 → Error 발생
User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
- 조회된 회원 정보(user) 를
UserDetails
로 변환UserDetails userDetails = new UserDetailsImpl(user)
UserDetails
를 "인증 관리자"에게 전달
Client
가 로그인 시도한 username, password 와
UserDetailsService
가 전달해준UserDetails
의 username, password 일치여부 확인
Client
가 보낸 password 는 평문이고,UserDetails
의 password 는 암호문이므로
Client
가 보낸 password를 암호화해서 비교한다
- 인증
성공
→ 세션에 로그인 정보 저장
인증실패
→ Error 발생
Authentication
의Principal
에 저장된UserDetailsImpl
을 가져올 수 있다.UserDetailsImpl
에 저장된 인증된 사용자인 User 객체를 사용할 수 있다.@Controller @RequestMapping("/api") public class ProductController { @GetMapping("/products") public String getProducts(@AuthenticationPrincipal UserDetailsImpl userDetails) { // Authentication 의 Principal 에 저장된 UserDetailsImpl 을 가져온다. User user = userDetails.getUser(); System.out.println("user.getUsername() = " + user.getUsername()); return "redirect:/"; } }
일반 사용자
는 관리자 전용 페이지에 접속이 인가되지 않아야 한다.
- 일반 사용자가 관리자 전용 맵에 인가된 오류
- 회원 상세정보
UserDetailsImpl
를 통해권한(Authority)
을 설정한다.
- "권한 이름" 규칙 : "ROLE_" 로 시작해야 함
ADMIN
권한 부여 →ROLE_ADMIN
USER
권한 부여 →ROLE_USER
public enum UserRoleEnum { USER(Authority.USER), // 사용자 권한 ADMIN(Authority.ADMIN); // 관리자 권한 private final String authority; UserRoleEnum(String authority) { this.authority = authority; } public String getAuthority() { return this.authority; } public static class Authority { public static final String USER = "ROLE_USER"; public static final String ADMIN = "ROLE_ADMIN"; } }
public class UserDetailsImpl implements UserDetails { // ... @Override public Collection<? extends GrantedAuthority> getAuthorities() { SimpleGrantedAuthority adminAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(adminAuthority); return authorities; } }
new SimpleGrantedAuthority("ROLE_ADMIN");
- 예시 코드는
ROLE_ADMIN
으로 고정되어 있지만
실제 코드에서는 사용자에 저장되어있는 role의 authority 값을 사용하여 동적으로 저장한다.UserRoleEnum role = user.getRole(); String authority = role.getAuthority(); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
UserDetailsImpl
저장된authorities
값을 사용하여 간편하게 권한을 제어할 수 있다.
Controller
에@Secured
애너테이션으로 권한 설정이 가능하다.
- @Secured("권한 이름")
@Secured(UserRoleEnum.Authority.ADMIN) // 관리자용 @GetMapping("/products/secured") public String getProductsByAdmin(@AuthenticationPrincipal UserDetailsImpl userDetails) { System.out.println("userDetails.getUsername() = " + userDetails.getUsername()); for (GrantedAuthority authority : userDetails.getAuthorities()) { System.out.println("authority.getAuthority() = " + authority.getAuthority()); } return "redirect:/"; }
@Secured
애너테이션 활성화 방법 :@EnableGlobalMethodSecurity(securedEnabled = true)
@Configuration @EnableWebSecurity // 스프링 Security 지원을 가능하게 함 @EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 애너테이션 활성화 public class WebSecurityConfig {
하루하루 강의를 들으면서 쿠키,세션방식 로그인에서 JWT로, 오늘은 Spring Security까지 배우면서
로그인을 구현하는 방식이 매일매일 복잡해지고 있는 기분이다.
그래도 점점 편리해지고 범용성도 좋아지니 재밌다. 아직은 머릿속에서 완전히 그려지지는 않는다.
얼른 코드 짜보면서 하고싶은데 남은 강의에 아직 배우지 않은 더 좋은것들이 있을테니 일단 강의부터 털어야겠다. (Spring 입문때 호되게 당했다)