초기 인가 작업
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/loginProcess", "/join", "/joinProcess").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
http
.formLogin((auth) -> auth.loginPage("/login")
.loginProcessingUrl("/loginProcess")
.permitAll()
);
http
.csrf((auth) -> auth.disable());
return http.build();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
@EnableWebSecurity
https://born2bedeveloper.tistory.com/75
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/login", "/loginProcess", "/join", "/joinProcess").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/my/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
);
경로들에 대한 인가 작업
.anyRequest().authenticated()
그 외 나머지 경로들().로그인 된 사용자만()
http
.formLogin((auth) -> auth.loginPage("/login")
.loginProcessingUrl("/loginProcess")
.permitAll()
);
loginPage("경로") 권한이 없는 경로들에 대해 자동으로 로그인 페이지로 이동시켜주는 설정
loginProcessingUrl() 로그인 페이지에서 보낸 정보를 처리하는 경로
http
.csrf((auth) -> auth.disable());
CSRF 비활성화
CSRF란?
csrf 활성 화 시 로그아웃 방법 (추가 예정)
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
스프링 시큐리티는 사용자 인증(로그인)시 비밀번호에 대해 단방향 해시 암호화를 진행하여 저장되어 있는 비밀번호와 대조한다. 따라서 회원가입시 비밀번호 항목에 대해서 암호화를 진행해야 한다.
스프링 시큐리티는 암호화를 위해 BCrypt Password Encoder를 제공하고 권장
UserDetailsService
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userData = userRepository.findByUsername(username);
if (userData != null) {
return new CustomUserDetails(userData);
}
return null;
}
}
UserDetailsService는 loadUserByUsername 메서드를 구현하도록 강제한다. 해당 메서드는 사용자명으로 비밀번호를 조회하여 찾은 사용자 객체를 UserDetails로 반환한다.
UserDetails
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserDetails는 사용자의 정보를 담는 인터페이스이다. 스프링 시큐리티에서 사용자의 정보를 불러오기 위해서 구현해야 하는 인터페이스로 기본 오버라이드 메서드는 아래와 같다.
메소드 | 리턴 타입 | 설명 | 기본값 |
getAuthorities() | Collection<? extends GrantedAuthorities> |
계정의 권한 목록을 반환 | |
getPassword() | String | 계정의 비밀번호를 반환 | |
getUsername() | String | 계정의 고유한 값을 반환 (Ex. DB PK값, 중복이 없는 이메일 값) |
|
isAccountNonExpired() | boolean | 계정의 만료 여부 반환 | true(만료 안됨) |
isAccountNonLocked() | boolean | 계정의 잠김 여부 반환 | true(잠기지 않음) |
isCredentialsNonExpired() | boolean | 비밀번호 만료 여부 반환 | true(만료 안됨 |
isEnabled() | boolean | 계정의 활성화 여부 반환 | true(활성화 됨) |
로그인 정보
사용자가 로그인을 진행한 뒤 사용자 정보는 SecurityContextHolder에 의해서 서버 세션에 관리된다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
# Username
String username = SecurityContextHolder.getContext().getAuthentication().getName();
# Role
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iter = authorities.iterator();
GrantedAuthority auth = iter.next();
String role = auth.getAuthority();
해당 코드를 통해서 UserDetails의 정보에 접근 가능
application.properties
//초 기반
server.servlet.session.timeout=1800
//분 기반
server.servlet.session.timeout=90m
세션 타임아웃 설정을 통해 로그인 이후 세션이 유지되고 소멸하는 시간을 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
...
http
.sessionManagement((auth) -> auth
.maximumSessions(1)
.maxSessionsPreventsLogin(true));
...
return http.build();
}
SecurityConfig의 filterChain 내부
sessionManagement() 메소드를 통한 설정을 진행한다.
maximumSession(정수) : 하나의 아이디에 대한 다중 로그인 허용 개수
maxSessionPreventsLogin(불린) : 다중 로그인 개수를 초과하였을 경우 처리 방법
true : 초과시 새로운 로그인 차단
false : 초과시 기존 세션 하나 삭제
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
...
http
.sessionManagement((auth) -> auth
.sessionFixation().changeSessionId());
...
return http.build();
}
세션 고정 공격
https://velog.io/@inmo/%EC%84%B8%EC%85%98-%EA%B3%A0%EC%A0%95-%EC%B7%A8%EC%95%BD%EC%A0%90
을 보호하기 위한 로그인 성공시 세션 설정 방법은 sessionManagement() 메소드의 sessionFixation() 메소드를 통해서 설정할 수 있다.
sessionManagement().sessionFixation().none() : 로그인 시 세션 정보 변경 안함
sessionManagement().sessionFixation().newSession() : 로그인 시 세션 새로 생성
sessionManagement().sessionFixation().changeSessionId() : 로그인 시 동일한 세션에 대한 id 변경
'Web > Spring Security' 카테고리의 다른 글
JWT 방식 OAuth2.0 클라이언트 동작 원리 (0) | 2025.02.26 |
---|---|
Spring Security 6.x.x JWT 보안 요소 추가 (0) | 2025.02.20 |
Spring Security 6.x.x JWT 기반 인증 인가 (0) | 2025.02.15 |
Spring Security JWT 인증 인가 순서 (0) | 2025.02.14 |
Thymeleaf + Spring Security 사용 시 principal 변수 접근 (0) | 2023.04.17 |