Spring boot/Spring Security
02. SpringSecurity - AuthenticationFailureHandler 로그인 실패시 실패 메시지 띄우기
akatjd
2021. 6. 20. 22:43
01) SecurityConfiguration 설정
- configure(HttpSecurity http) 메서드에 http.formLogin().failureHandler(loginFailHandler) 설정해줘야 함.
package com.kjc.workplus.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.kjc.workplus.login.handler.LoginFailHandler;
import com.kjc.workplus.login.service.MemberService;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MemberService memberService;
@Autowired
private LoginFailHandler loginFailHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/workplus/member/**").hasRole("MEMBER")
.antMatchers("/workplus/admin/**").hasRole("ADMIN")
.antMatchers("/workplus/**").permitAll();
http.formLogin()
.loginPage("/workplus/login.do")
.defaultSuccessUrl("/workplus/main.do")
.failureHandler(loginFailHandler)
.permitAll();
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/workplus/main.do")
.invalidateHttpSession(true);
http.exceptionHandling()
.accessDeniedPage("/workplus/denied.do");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
}
}
- LoginFailHandler를 작성해줘야 함. (Service에 작성)
package com.kjc.workplus.login.handler;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Service;
@Service("loginFailHandler")
public class LoginFailHandler implements AuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
Logger log = LoggerFactory.getLogger(this.getClass());
// TODO Auto-generated method stub
if(exception instanceof AuthenticationServiceException) {
request.setAttribute("LoginFailMessage", "죄송합니다. 시스템에 오류가 발생했습니다.");
}
else if(exception instanceof BadCredentialsException) {
request.setAttribute("LoginFailMessage", "아이디 또는 비밀번호가 일치하지 않습니다.");
}
else if(exception instanceof DisabledException) {
request.setAttribute("LoginFailMessage", "현재 사용할 수 없는 계정입니다.");
}
else if(exception instanceof LockedException) {
request.setAttribute("LoginFailMessage", "현재 잠긴 계정입니다.");
}
else if(exception instanceof AccountExpiredException) {
request.setAttribute("LoginFailMessage", "이미 만료된 계정입니다.");
}
else if(exception instanceof CredentialsExpiredException) {
request.setAttribute("LoginFailMessage", "비밀번호가 만료된 계정입니다.");
}
else request.setAttribute("LoginFailMessage", "계정을 찾을 수 없습니다.");
RequestDispatcher dispatcher = request.getRequestDispatcher("/workplus/login.do");
System.out.println(request.getAttribute("LoginFailMessage"));
dispatcher.forward(request, response);
}
}
Fail Handler가 받는 예외 | 디폴트 Provider가 리턴하는 결과 (커스터마이징 가능) |
AuthenticationServiceException | - null 값을 리턴 |
BadCredentailException | - UsernameNotFoundException 예외를 throw - UserDetails 객체를 리턴했으나, 아이디 또는 비밀번호가 틀림 |
LockedException | - UserDetails 객체의 isAccountNonLocked() 메서드의 리턴값이 false |
DisabledException | - UserDetails 객체의 isEnabled() 메서드의 리턴값이 false |
AccountExpiedException | - UserDetails 객체의 isAccountNonExpired() 메서드의 리턴값이 false |
CredentailExpiredException | - UserDetails 객체의 isCredentialsNonExpired() 메서드의 리턴값이 false |
02) View 처리
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.w3.org/1999/xhtml"
xmlns="http://www.w3.org/1999/xhtml"
layout:decorator="board/layout/basic"
layout:decorate="~{layout/default_layout}">
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
<script src="/webjars/bootstrap/3.3.4/dist/js/bootstrap.min.js"></script>
<th:block layout:fragment="head">
<!--/* css */-->
<link th:href="@{/css/common.css}" rel="stylesheet" />
</th:block>
</head>
<body>
<h1>로그인</h1>
<div th:if="${LoginFailMessage!=null}">
<p th:text="${LoginFailMessage}" class="alert alert-danger"></p>
</div>
<hr>
<form th:action="@{/workplus/login.do}" method="post">
<input th:type="hidden" th:value="secret" name="secret_key">
<input type="text" name="username" placeholder="account를 입력해주세요.">
<input type="password" name="password" placeholder="password를 입력해주세요.">
<button type="submit">로그인</button>
</form>
</body>
</html>
<div th:if="${LoginFailMessage!=null}">
<p th:text="${LoginFailMessage}" class="alert alert-danger"></p>
</div>
로그인시 에러 발생하면 LoginFailMessage 애트리뷰트가 포워딩 되며 해당 오류 알러트를 띄어줌.