Spring Security를 이어서 공부!
@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {
// 아래의 SecurityFilterChain보다 더 우선적으로 걸리는 설정
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
/*
h2-console 사용 및 resources 접근 허용 설정
Security는 모든 요청을 다 인증하기 때문에 CSS나 JS image 파일같은건 일일이 인증하지 않게 하려고
web.ignoring() 쓰면 이러한 경로로 들어오는 것은 인증처리하는걸 무시한다고 설정
아래의 .permitAll로 하나하나 설정하는 방법도 있고 이렇게 한방에 할 수도 있다.
*/
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf().disable();
// .permitAll()로 저기 url들은 인증 안하고 실행 되게 한다.
http.authorizeRequests()
// .antMatchers(HttpMethod.GET, "/api/user").hasAnyRole(ADMIN)// 이렇게 api 지정하거나
// .antMatchers("/h2-console/**").permitAll()
// .antMatchers("/css/**").permitAll()
// .antMatchers("/js/**").permitAll()
// .antMatchers("/images/**").permitAll()
// .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated(); // 이 외의 URL요청들은 다 인증을 하겠다.
// 디폴트 로그인 폼 사용
// http.formLogin();
// Custom 로그인 페이지 사용
http.formLogin().loginPage("/api/user/login-page").permitAll();
return http.build();
}
}
(web) -> web.ignoring().~ : .requestMatchers(경로)로 들어오는 요청들은 인증 처리를 안하고 무시하겠다. 인증이 필요없는 부분은 생략해주는 방법.
Spring Boot 3v 이상부터는 antMatchers() → requestMatchers()로 고쳐써야한다.
.antMatchers("/h2-console/**"). : 여기 경로 요청은 인증안해도 된다.
.antMatchers(HttpMethod.GET, "/api/user"). : http 메서드와 api경로도 지정 가능.
.뒤에 오는 메서드로 여러 인증 권한 설정 가능.
.permitAll()은 전부 허가하겠다 .
.authenticated() 전부 인증하겠다.
.anonymous()익명의 사용자만 허용하겠다.
.hasAnyRole(권한) 해당 권한을 가진 사람만 요청 받겠다.
.anyRequest().authenticated(); : 이 외의 URL요청들은 다 인증처리를 하겠다.
http.formLogin(); : Security에서 제공하는 default Form Login을 사용한다.
기본적으로 서버가 시작할때 제공하는 디폴트 계정이 있다.
username은 user / password는 서버 시작할때 마다 바뀌는데
Spring 콘솔창에 Using generated security password 라고 찍힌다.
이 계정으로 로그인을 하면 쿠키와 세션id가 발급되는 것을 확인할 수 있다.
Spring Securiy는 기본적으로 쿠키-세션 방식으로 인증처리를 한다.
http.formLogin().loginPage("/api/user/login-page").permitAll();
위의 디폴트 폼 코드 뒤에 이렇게 달면 Custom한 로그인 페이지를 사용할 수 있다.
UserDetails를 implement. private final로 User와 username, password를 멤버변수로 선언해서 인증완료된 User의 객체와 아이디 비번을 가져온다.
생성자를 만들고 User와 아이디 비번 게터 생성.
권한 가져오는 부분인 getAuthorities는 아래와 같이 구현
User의 권한을 가져와 String 값으로 만들고 GrantedAuthority로 추상화해서 사용한다.
@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;
}
UserDetailsService를 implement. @Service 달아서 빈으로 등록
UserRepository를 연결해서 User를 DB에서 조회하는 메서드 생성.
이 메서드는 username을 파라미터로 받아 위의 UserDetailesImpl을 return
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("UserDetailsServiceImpl.loadUserByUsername : " + username);
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
// DB조회해서 나온 User와 User명, 비밀번호를 UserDetailsImpl에 담아 반환
return new UserDetailsImpl(user, user.getUsername(), user.getPassword());
}
//위의 메서드로 userDetails 객체를 생성하고(UserDetailsImpl로 반환)
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 생성한 userDetails로 인증 객체(Authentication) 생성(U~P~A~Token으로)
// principle에는 userDetails, credentials에는 userDetails의 .getPassword(), autholities에는 userDetails의 getAuthorities()
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
회원 등록시 비밀번호는 그대로 DB에 저장하면 안되고 암호화해야하는 것이 관련법 상 의무이다.
현재는 적응형 단방향 함수로 암호화하는 것을 추천.
Spring Security에서 적응형 단방향 함수인bCrypt
를 제공한다.
적응형 단방향 함수는 내부적으로 리소스의 낭비가 심해 API 요청마다 검증하면 성능이 떨어진다.
그래서 회원가입, 로그인할때만 사용하고, 그 뒤 검증이 필요한 단계에서는 세션이나 토큰과 같은 인증방식으로 검증하는 것이 속도와 보안측면에서 유리하다.
단방향 : 암호화는 가능하지만 복호화는 불가능하다. (양방향은 암호화 복호화 둘 다 가능)
암호화된 비밀번호를 해커가 훔쳐가도 복호화가 불가능해 원래의 비밀번호를 유추 불가능.
서버가 유저의 비밀번호를 암호화해 저장하고 로그인할때마다 암호화된 비밀번호끼리 비교.
Password Matching : Spring Security에서는 사용자가 입력한 비밀번호를 저장된 비밀번호와 비교해 일치 여부를 확인해주는 함수도 제공한다.
boolean .matches(CharSequence rawPassword, String encodedPassword);
rawPassword : 사용자가 입력한 비밀번호(평문). 암호화 안된 상태.
encodedPassword : DB에 저장되어 있는 암호화 된 비밀번호
// 사용예시. 비밀번호 확인
if(!passwordEncoder.matches("사용자가 입력한 비밀번호", "저장된 비밀번호")) {
throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
}
@Bean // 비밀번호 암호화 기능 등록. 적응형 단방향 함수 encoder를 적용.
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}