
SpringSecurity를 이용하여 인증을 처리할 때, customUserDetailsService를 구현하여 직접 사용자를 DB에 저장하고 DB로부터 사용자를 직접 조회하여 그 조회한 사용자를 통해서 인증처리가 이루어질 수 있도록 할 수 있다.
implementation 'org.springframework.boot:spring-boot-starter-security'
SecurityConfig.java
이전에는 스프링 시큐리티의 인증을 담당하는 AuthenticationManager에 authenticationManagerBuilder를 이용해서 userDetailsService와 passwordEncode를 직접 설정해주어야 했지만, 현재는 AuthenticationManager 빈 생성 시 자동으로 UserSecurityService와 PasswordEncoder가 설정된다.
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig {
@Autowired UserDetailsService userDetailsService;
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() throws Exception {
return (web) -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/","/users").permitAll()
.antMatchers("/mypage").hasRole("USER")
.antMatchers("/messages").hasRole("MANAGER")
.antMatchers("/config").hasRole("ADMIN")
.anyRequest().authenticated();
http.formLogin()
.loginPage("/login").permitAll();
http.logout()
.logoutSuccessUrl("/");
return http.build();
}
}
@Getter
public class UserAccount extends User {
private final Account account;
public UserAccount(Account account, Collection<? extends GrantedAuthority> authorities) {
super(account.getUsername(), account.getPassword(), authorities);
this.account = account;
}
// public UserAccount(Account account) {
// super(account.getUsername(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
// this.account = account;
// }
}
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = userRepository.findByUsername(username);
if (account == null) {
throw new UsernameNotFoundException("UsernameNotFoundException");
}
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority(account.getRole()));
return new UserAccount(account, roles);
}
}
@RequiredArgsConstructor
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserAccount userAccount = (UserAccount) userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, accountContext.getAccount().getPassword())) {
throw new BadCredentialsException("아이디 또는 비밀번호가 일치하지 않습니다.");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userAccount.getAccount(), null, userAccount.getAuthorities());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@GetMapping("/sign-up")
public String signUpForm(Model model) {
model.addAttribute("signUpForm", new SignUpForm());
return "account/sign-up";
}
@PostMapping("/sign-up")
public String signUpSubmit(@Valid @ModelAttribute SignUpForm signUpForm, Errors errors, Model model) {
if (errors.hasErrors()) {
return "account/sign-up";
}
Account account = accountService.processNewAccount(signUpForm);
accountService.login(account);
return "redirect:/";
}
public void login(Account account) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
new UserAccount(account), // UserAccount 객체가 Principal로 간주됨.
account.getPassword(),
List.of(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(token);
}
@Getter
public class UserAccount extends User {
private Account account;
public UserAccount(Account account) {
super(account.getNickname(), account.getPassword(), List.of(new SimpleGrantedAuthority("ROLE_USER")));
this.account = account;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
// 인증된 객체가 아니면 스프링 시큐리티가 UserAccount(principal)에 anonymousUser 라는 값을 넣음.
// UserAccount 객체가 anonymousUser면 null, 그게 아니라면 UserAccount 객체안에 account 프로퍼티를 가져옴.
public @interface CurrentUser {
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(){
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("user"))
.roles("USER")
.build();
UserDetails sys = User.builder()
.username("sys")
.password(passwordEncoder().encode("sys"))
.roles("SYS", "USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN", "SYS", "USER")
.build();
return new InMemoryUserDetailsManager(user, sys, admin);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user").hasRole("USER")
.antMatchers("/admin/pay").hasRole("ADMIN")
.antMatchers("/admin/**").access("hasRole('ADMIN') or hasRole('SYS')")
.anyRequest().authenticated();
http
.formLogin();
return http.build();
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/user").hasRole("USER")
.antMatchers("/admin/pay").hasRole("ADMIN")
.antMatchers("/admin/**").access("hasRole('ADMIN') or hasRole('SYS')")
.anyRequest().authenticated();
http
.formLogin()
.successHandler(new AuthenticationSuccessHandler() { // 인증 성공 시 처리
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
String redirectUrl = savedRequest.getRedirectUrl();
response.sendRedirect(redirectUrl);
}
});
http
.exceptionHandling() // 예외 처리 기능 작동
.authenticationEntryPoint(new AuthenticationEntryPoint() { // 인증 실패 시 처리
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendRedirect("/login");
}
})
.accessDeniedHandler(new AccessDeniedHandler() { // 인가 실패 시 처리
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.sendRedirect("/denied");
}
});
return http.build();
}