본문 바로가기
스프링부트+gradle+JSP+STS(Eclipse)

스프링부트 스프링시큐리티 비동기(ajax) 로그인

by 크리스턍 2022. 7. 17.

스프링시큐티를 활용하여 ajax 로 비동기 로그인 처리

 

수정 소스

 

 수정소스

 

 - 로그인 페이지 처리

 LoginController.java
 loginForm.jsp
 loginForm.js

 

 - 사용자 조회
 LoginService.java
 LoginMapper.java
 LoginMapper.xml

 

 - 스프링시큐리티 설정
 SecurityConfig.java
 CustomAuthenticationSuccessHandler.java
 CustomAuthenticationFailureHandler.java
 LoginProvider.java

 

 

1. 로그인 페이지 처리

 

 1) LoginController.java : 로그인 페이지 이동 컨트롤러

package com.gradle.gradletemplate.login.controller;

import com.gradle.gradletemplate.login.service.LoginService;
import com.gradle.gradletemplate.login.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class LoginController {

    @Autowired
    private LoginService loginService;

    // 로그인 페이지
    @RequestMapping(value = "/login/loginForm", method = {RequestMethod.GET})
    public ModelAndView login() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("login/loginForm");
        return mv;
    }
}


 2) loginForm.jsp : 로그인 화면

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/bootstrap/css/signin.css"/>
</head>
<div class="container">
    <h2 class="form-signin-heading">Gradle Sign in</h2>
    <form id="frm_login" method="post">
    <label for="userId" class="sr-only">Email address</label>
        <input type="email" id="userId" name="userId" class="form-control" placeholder="Email address">
        <label for="userPw" class="sr-only">Password</label>
        <input type="password" id="userPw" name="userPw" class="form-control" placeholder="Password">
        <div class="checkbox">
            <label>
                <input type="checkbox" value="remember-me"> ID 저장
            </label>
        </div>
        <button class="btn btn-lg btn-primary btn-block" type="button" id="btn_signin">Sign in</button>
    </form>
</div>
</html>


 3) loginForm.js : 로그인 화면 스크립트

var loginForm = {
    init: function() {
        loginForm.bind();
    },
    bind: function() {
        $("#btn_signin").on('click', function(){
           loginForm.signin();
        });
    },
    signin: function() {
        $.ajax({
           url: '/login',
            type: 'POST',
            dataType: 'json',
            data: {
               userId: $("#userId").val(),
                userPw: $("#userPw").val()
            },
            success: function(res){
               if(res.code=='200'){
                   alert("로그인 되었습니다.");
                   common.goPage("/board/boardList");
               } else {
                   alert(res.message);
               }
               console.log(res);
            }
        });
    }
}

$(function(){
   loginForm.init();
});

 

 

2. 사용자 조회

 

 1) LoginService.java : 사용자 조회 매퍼 호출

package com.gradle.gradletemplate.login.service;

import com.gradle.gradletemplate.login.mapper.LoginMapper;
import com.gradle.gradletemplate.login.vo.UserVO;
import com.gradle.gradletemplate.util.CryptUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class LoginService {

    @Autowired
    private LoginMapper loginMapper;

	// 사용자 조회
    public UserVO selectUser(UserVO userVO) {
        return loginMapper.selectUser(userVO);
    }
}


 2) LoginMapper.java : 사용자 조회 쿼리 호출

package com.gradle.gradletemplate.login.mapper;

import com.gradle.gradletemplate.login.vo.UserVO;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoginMapper {

	// 사용자 조회
    UserVO selectUser(UserVO userVO);
}


 3) LoginMapper.xml : 사용자 조회 쿼리

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.gradle.gradletemplate.login.mapper.LoginMapper">

    <select id="selectUser" parameterType="com.gradle.gradletemplate.login.vo.UserVO" resultType="com.gradle.gradletemplate.login.vo.UserVO">
        select USER_ID
             , USER_PW
             , USER_NAME
             , PHONE
             , EMAIL
             , ROLE
             , CREATED_TIME
             , UPDATE_TIME
        from user
        where USER_ID = #{userId}
    </select>
</mapper>

 

 

3. 스프링 시큐리티 설정

 

 1) SecurityConfig.java : 로인과 관련된 세션정보 체크, 로그인페이지, 로그인 처리 프로세스 및 핸들러 등에 대한 설정을 한다.

package com.gradle.gradletemplate.config;

import com.gradle.gradletemplate.config.security.CustomAuthenticationFailureHandler;
import com.gradle.gradletemplate.config.security.CustomAuthenticationSuccessHandler;
import com.gradle.gradletemplate.config.security.CustomUserDetailsService;
import com.gradle.gradletemplate.config.security.LoginProvider;
import lombok.RequiredArgsConstructor;
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.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;

@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Autowired
    private LoginProvider loginProvider;

    // 인증 예외처리
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/bootstrap/**", "/common/**", "/jquery/**", "/js/**");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()
                .antMatchers("/notice/**").authenticated() // 해당 URL 패턴에 대해 로그인 했을 경우에만 접근 허용
                .antMatchers("/board/**").hasAuthority("MEMBER") // 해당 URL 패턴에 대해 MEMBER 권한이 있을 경우에만 접근 허용
                .anyRequest().permitAll() // 위에 설정된 URL 패턴을 제외한 모든 페이지 접근 허용

                .and()
                .formLogin()
                .loginPage("/login/loginForm") // 접근 허용이 되지 않은 페이지에 접근 했을 경우 해당 URL 로 이동
                .loginProcessingUrl("/login") // 해당 URL 로 접근 시 로그인 프로세스 실행
                .usernameParameter("userId") // 사용자아이디 파라미터 name을 정의
                .passwordParameter("userPw") // 비밀번호 파라미터 name을 정의
                .successHandler(customAuthenticationSuccessHandler) // 로그인 성공시 해당 핸들러 실행
                .failureHandler(customAuthenticationFailureHandler) // 로그인 실패시 해당 핸들러 실행
                .permitAll()

                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true).deleteCookies("JSESSIONID")

                // ajax 통신 가능하도록
                .and()
                .csrf()
                .disable()
                .authenticationProvider(loginProvider)
        ;
    }
}

 

 2)  LoginProvider.java : 로인 처리 프로세스

package com.gradle.gradletemplate.config.security;

import com.gradle.gradletemplate.login.service.LoginService;
import com.gradle.gradletemplate.login.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
public class LoginProvider implements AuthenticationProvider {

    @Autowired
    private LoginService loginService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Object details = authentication.getDetails();

        String userId = authentication.getName();
        String userPw = (String) authentication.getCredentials();

        String resultUserPw = "";
        Object resultObj = null;

        UserVO param = new UserVO();
        param.setUserId(userId);
        param.setUserPw(userPw);

        UserVO userInfo = loginService.selectUser(param);

        // 사용자 존재여부
        if(userInfo==null) {
            throw new BadCredentialsException("아이디가 존재하지 않습니다.");
        } else {
            resultUserPw = userInfo.getUserPw();
            userInfo.setUserPw(null);
            resultObj = userInfo;
        }

        // 비밀번호 체크
        if(!userPw.equals(resultUserPw)){
            throw new BadCredentialsException("비밀번호가 일치하지 않습니다.");
        }

        // 권한 리스트
        List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
        roles.add(new SimpleGrantedAuthority("MEMBER"));        // 별도 권한 관리는 만들지 않아 임의로 입력

        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userId, userPw, roles);
        authToken.setDetails(resultObj);

        return authToken;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

 

 3)  CustomAuthenticationSuccessHandler.java : 로그인 성공시 처리 로직

package com.gradle.gradletemplate.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gradle.gradletemplate.common.constant.ResponseDataCode;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        ObjectMapper mapper = new ObjectMapper();

        System.out.println(authentication.getName());
        System.out.println(authentication.getCredentials());

        ResponseDataDTO responseDataDTO = new ResponseDataDTO();
        responseDataDTO.setCode(ResponseDataCode.SUCCESS);
        responseDataDTO.setStatus(ResponseDataCode.SUCCESS);

        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().print(mapper.writeValueAsString(responseDataDTO));
        response.getWriter().flush();
    }
}


 4)  CustomAuthenticationFailureHandler.java : 로그인 실패시 처리 로직

package com.gradle.gradletemplate.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gradle.gradletemplate.common.constant.ResponseDataCode;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        ObjectMapper mapper = new ObjectMapper();

        ResponseDataDTO responseDataDTO = new ResponseDataDTO();
        responseDataDTO.setCode(ResponseDataCode.ERROR);
        responseDataDTO.setStatus(ResponseDataCode.ERROR);
        responseDataDTO.setMessage(exception.getMessage());

        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println(mapper.writeValueAsString(responseDataDTO));
        response.getWriter().flush();
    }
}

 

 

 

댓글