정수원님의 강의 스프링 시큐리티 완전 정복 [6.x 개정판] 보면서 공부한 내용입니다.
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// "/" 경로 인증 없이 접근 허용
.requestMatchers("/").permitAll()
// 나머지는 인증 받도록 설정
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
;
return http.build(); // securityFilterChain 빈 생성
}
// 사용자 계정 생성
@Bean
public UserDetailsService userDetailsService(){
UserDetails user = User.withUsername("user").password("{noop}1111").roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
}
UserDetailsService
InMemoryUserDetailsManager
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// csrf 기능 활성화
.authorizeHttpRequests(auth -> auth
// 정적 자원 모두 인증 없이 접근 허용 설정
.requestMatchers("/css/**","/images/**","/js/**","/favicon.*","/*/icon-*").permitAll()
// "/" root 인증 없이 접근 허용 설정
.requestMatchers("/").permitAll()
// 나머지는 인증 받도록 설정
.anyRequest().authenticated()
)
// 로그인 페이지 설정
// 로그인 페이지를 커스텀하면 로그아웃 페이지도 커스텀 해야됨!
.formLogin(form -> form.loginPage("/login").permitAll())
;
return http.build();
}
<form th:action="@{/login}" method="post">
...
</form>
PasswordEncoder
구현 객체를 생성해주는 컴포넌트로써 DelegatingPasswordEncoder를 통해 애플리케이션에서 사용할 PasswordEncoder를 결정하고, 결정된 PasswordEncoder로 사용자가 입력한 패스워드를 단방향으로 암호화 해준다.{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
💡 결정된 PasswordEncoder 확인 방법
→ {id} 형식의 접두사로 어떤 방식으로 인코딩 되었는지 확인할 있다
{bcrypt}
로 적용된다 @PostMapping(value="/signup")
public String signup(AccountDto accountDto) {
// AccountDto에 입력된 정보를 다른 객체에 복사시켜줌
// 이를 통해 각 속성 간 매핑이 됨
ModelMapper mapper = new ModelMapper();
// Account account = mapper.map(원본객체, 원본의 값을 복사해서 받을 클래스 타입);
Account account = mapper.map(accountDto, Account.class);
// account를 통해 db에 데이터 저장
account.setPassword(passwordEncoder.encode(accountDto.getPassword()));
// db에 저장하기 위해 service => respository로 전달
userService.createUser(account);
// 회원 가입 진행 후 root 페이지로 이동
return "redirect:/";
}
회원가입 실행
입력된 비밀번호 encoding
회원가입 성공
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = userRepository.findByUsername(username);
if(account == null){
// account가 인증받지 못한 경우, 예외 발생 시켜서 접근 차단
throw new UsernameNotFoundException("No user found with username" + username);
}
// 권한 정보 설정
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(account.getRoles()));
// AccountDto에 매핑시키기
ModelMapper mapper = new ModelMapper();
AccountDto accountDto = mapper.map(account, AccountDto.class);
return new AccountContext(accountDto,authorities);
}
로그인 요청 유저 정보 확인
유저 권한 확인
UserDetail 정보 확인
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String loginId = authentication.getName();
String password = (String) authentication.getCredentials();
AccountContext accountContext = (AccountContext) userDetailsService.loadUserByUsername(loginId);
if(!passwordEncoder.matches(password, accountContext.getPassword())){
// 입력한 비밀번호와 저장된(인코딩된) 비밀번호 일치 여부 확인
throw new BadCredentialsException("유효하지 않은 비밀번호");
}
// 새로운 인증 객체 만들어서 반환
return new UsernamePasswordAuthenticationToken(accountContext.getAccountDto(),null,accountContext.getAuthorities());
}
@GetMapping(value="/logout")
public String logout(HttpServletRequest request, HttpServletResponse response){
Authentication authentication = SecurityContextHolder.getContextHolderStrategy().getContext().getAuthentication();
if(authentication != null){
new SecurityContextLogoutHandler().logout(request,response,authentication);
}
return "redirect:/login";
}
WebAuthenticationDetails
클래스)를 제공하는 소스 역할을 한다 public FormAuthenticationDetails(HttpServletRequest request) {
super(request);
// request 값을 전달받아 클라이언트에 넘긴 정보를 시큐리티에 저장시킴
this.secretKey = request.getParameter("secret_key");
}