- spring security ajax login -
이 포스팅에서는 jQuery Ajax를 이용한 커스텀 로그인 페이지 구현을 할 것이다.
스프링 시큐리티에 기본 로그인 페이지는 로그인 실패시 페이지를 새로고침하고 쿼리스트링(localhost:8090/login?error)을 주는 방식으로 구현이 되어 있는데 이 방식으로 구현을 하면 페이지를 새로 띄워야 할 뿐만 아니라 입력했던 정보에 대해서 전부 사라지게 된다.
위에나오는 불편함을 해소하기 위해 요즘 나오는 대부분의 사이트를 로그인시 ajax를 이용해서 login을 한다.
1. LoginController.java 생성
스프링 시큐리티의 기본 로그인 url은 /login 이다.
이걸 커스텀하기위해 /login 을 재정의한다.
아래의 코드에서는 로그인 완료 후 원했던 페이지로 이동하기 위한 이전 페이지 정보를 저장한다.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String Login(HttpServletRequest request, HttpServletResponse response) {
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
try {
//여러가지 이유로 이전페이지 정보가 없는 경우가 있음.
//https://stackoverflow.com/questions/6880659/in-what-cases-will-http-referer-be-empty
request.getSession().setAttribute("prevPage", savedRequest.getRedirectUrl());
} catch(NullPointerException e) {
request.getSession().setAttribute("prevPage", "/");
}
return "login";
}
}
2. login.jsp 생성
정말 간단하게 ID 입력 input 과 PW 입력 input이 존재한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<script type="text/javascript" src="/js/login.js"></script>
</head>
<body>
<div>
<div>
<div>
<div>
<div>
<input type="text" id="member-id" autofocus="autofocus" placeholder="ID">
</div>
<div >
<input type="password" id="member-password" placeholder="Password">
</div>
<div>
<input type="hidden" id="token" data-token-name="${_csrf.headerName}" placeholder="Password" value="${_csrf.token}">
</div>
<div >
<div >
<div >
<button id="login-button">로그인</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
3. login.js 생성
로그인을 ajax를 통해서 구현할 것이다.
원래 시큐리티의 기본 ID , PASSWORD 파라미터는 username , password 이다.
하지만 username과 password은 헷갈려서 6번 과정에서 우리 DB에 맞게 memberId, memberPassword 로 설정을 한다.
여기서 중요한건 스프링 시큐리티는 기본적으로 CSRF 공격을 막도록 설정되어 있어서 beforeSend를 통하여 crsf token 값을 세팅하여 넘겨줘야 한다.
$(function(){
$("#login-button").click(function(){
login();
})
})
/**
* 로그인
*/
function login(){
$.ajax({
url:"/login",
type : "POST",
dataType : "json",
data : {
memberId : $("#member-id").val(),
memberPassword : $("#member-password").val()
},
beforeSend : function(xhr)
{
//이거 안하면 403 error
//데이터를 전송하기 전에 헤더에 csrf값을 설정한다
var $token = $("#token");
xhr.setRequestHeader($token.data("token-name"), $token.val());
},
success : function(response){
if(response.code == "200"){
// 정상 처리 된 경우
window.location = response.item.url; //이전페이지로 돌아가기
} else {
alert(response.message);
}
},
error : function(a,b,c){
console.log(a,b,c);
}
})
}
4. json 응답을 위한 ResponseDataDTO.java 생성
이 응답 객체를 사용하면
{
code : string,
status : string,
message : string,
item : object
}
형태로 json을 응답해준다.
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class ResponseDataDTO {
private String code;
private String status;
private String message;
private Object item;
}
public class ResponseDataCode {
public static final String SUCCESS = "200";
public static final String ERROR = "999";
}
public class ResponseDataStatus {
public static final String SUCCESS = "200";
public static final String ERROR = "999";
}
5. 로그인 성공, 실패를 위한 핸들러 생성
만약 로그인이 성공 혹은 실패 후 필요한 비지니스 로직은 다음 핸들러에서 작성한다.
CustomAuthenticationSuccessHandler.java
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.demo.common.config.ResponseDataCode;
import com.project.demo.common.config.ResponseDataStatus;
import com.project.demo.common.dto.ResponseDataDTO;
/**
* 로그인 성공시 핸들러
*
*/
@Component
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
/**
* 로그인이 성공하고나서 로직
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
ObjectMapper mapper = new ObjectMapper(); //JSON 변경용
ResponseDataDTO responseDataDTO = new ResponseDataDTO();
responseDataDTO.setCode(ResponseDataCode.SUCCESS);
responseDataDTO.setStatus(ResponseDataStatus.SUCCESS);
String prevPage = request.getSession().getAttribute("prevPage").toString(); //이전 페이지 가져오기
Map<String, String> items = new HashMap<String,String>();
items.put("url", prevPage); // 이전 페이지 저장
responseDataDTO.setItem(items);
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print(mapper.writeValueAsString(responseDataDTO));
response.getWriter().flush();
}
}
CustomAuthenticationFailureHandler.java
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.demo.common.config.ResponseDataCode;
import com.project.demo.common.config.ResponseDataStatus;
import com.project.demo.common.dto.ResponseDataDTO;
/**
* 로그인 실패시 로직
*
*/
@Component
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper(); //JSON 변경용
ResponseDataDTO responseDataDTO = new ResponseDataDTO();
responseDataDTO.setCode(ResponseDataCode.ERROR);
responseDataDTO.setStatus(ResponseDataStatus.ERROR);
responseDataDTO.setMessage("아이디 혹은 비밀번호가 일치하지 않습니다.");
response.setCharacterEncoding("UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print(mapper.writeValueAsString(responseDataDTO));
response.getWriter().flush();
}
}
6. 이전 포스팅에서 2번 단계에서 작성한 SecurityConfig.java 에 아래 코드를 추가
@Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
/**
* 시큐리티에 관련된 설정
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/member/**").hasAuthority("MEMBER") // /member/ 로 시작하는 URL은 MEMBER라는 권한을 가진 사용자만 접근 가능
// 만약 권한이 없으면 LoginPage로 이동한다.
// 스프링 시큐리티에서는 ROLE_ 접두어로 붙여주기에 ROLE_MEMBER 를 가진 사람이다.
.and()
.formLogin() //로그인 폼 사용
.loginPage("/login") //커스텀 로그인 페이지
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.usernameParameter("memberId") //스프링 시큐리티에서는 username을 기본 아이디 매핑 값으로 사용하는데 이거 쓰면 변경
.passwordParameter("memberPassword") //이건 password를 변경
.permitAll();
}
7. MemberController.java 생성
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.project.demo.member.repository.MemberRepository;
@Controller
@RequestMapping("/member")
public class MemberController {
@Autowired
MemberRepository memberRepository;
@GetMapping("/")
private String test() {
return "member/member";
}
}
8. 로그인 테스트
localhost:8090/ 에서 localhost:8090/member/ 를 주소에 치면 아래와 같은 화면이 나온다.
로그인 페이지에서 admin / test 를 입력하면 alert 이 뜨며 admin / admin 을 입력하면 자동으로 localhost:8090/member/로 이동한다.
여기까지 하면 Ajax를 이용한 커스텀 로그인 페이지까지 완성이 된다.
스프링 시큐리티 커스텀 로그인 - DB 기반 로그인 시켜보기 -
로그아웃 구현하기
'Spring > spring security' 카테고리의 다른 글
Spring Security - LogoutFilter 란 (0) | 2021.09.10 |
---|---|
Spring Security - UsernamePasswordAuthenticationFilter 란 (1) | 2021.09.09 |
Spring Security UserDetails, UserDetailsService 란? - 삽질중인 개발자 (0) | 2021.02.14 |
spring security DB를 이용한 로그인 구현 3 ( 로그아웃 하기 ) - 삽질중인 개발자 (0) | 2020.03.29 |
스프링 시큐리티 DB를 이용한 로그인 구현 1 ( 회원 설정 및 일반 로그인 ) - 삽질중인 개발자 (2) | 2020.02.16 |