본문 바로가기
Web Programming/Java

[React + SpringSecurity + DB] 로그인 구현하기 (Backend)

by jaey0ng 2024. 1. 18.

 

저의 예시는 스프링 2.5.5 버전입니다.

버전에 따라 코드가 다를 수 있으므로 버전에 맞게 코딩해주시면 되겠습니다.

 

스프링 시큐리티부터 만들어볼까 합니다.

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-security'

 

위 문구를 적고 재빌드를 해줍니다.

 

SecurityConfig.java

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable() //csrf토큰 비활성화(테스트시 걸어두는게 좋음)
            .authorizeRequests()
                .antMatchers("/com/login","/com/join", "/**").permitAll()
                .anyRequest().authenticated()
                .and()
//            .formLogin()
//                .loginPage("/com/login")
//                .defaultSuccessUrl("/")
//                .permitAll()
//                .and()
            .logout()
                .permitAll()
                .and();
//            .csrf(csrf -> csrf
//                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
//            );

        http.sessionManagement() //중복로그인 제어
            .maximumSessions(1) //세션 최대 허용 수
            .maxSessionsPreventsLogin(false); // false: 중복 로그인하면 이전 로그인이 풀림
    }
}

 

저는 이렇게 만들었습니다.

.formLogin() 부분을 주석처리한 이유는 로그인페이지를 지정해주지 않아도 프론트에서 처리할 수 있기 때문에 굳이 사용하지 않았습니다.

 

또한 아래 부분의 .csrf(csrf -> ... 부분은 맨 위에 있는 .csrf().disable()과 서로 다른 동작을 하므로

테스트 할 때는 .csrf().disable() 사용하여 csrf 토큰을 비활성화 하는 것을 추천드리고

실제 운영을 하거나 배포를 하실 때는 비활성화 부분을 주석처리 하시고 밑에 부분을 사용하시면 되겠습니다.

 

CSRF 토큰은 보안 토큰의 일종으로, 토큰 키를 들고 있지 않은 페이지가 데이터를 요청하면 동작하지 않도록 합니다. 따라서 토큰이 필요한 경우 프론트에도 설정이 필요합니다.
CSRF에 대해 더 알아보실 분들은 따로 검색해서 찾아보시면 되겠습니다.

 

 

UserController.java

@ResponseBody
@RestController
@RequestMapping("/com")
public class UserController {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    UserService userService;

    @PostMapping(value = "/login")
    public ResponseEntity<List<Map<String, Object>>> login(
    @RequestBody Map<String, String> request, HttpServletRequest httpRequest
    ) {
        String userId = request.get("userId");
        String enteredPassword = request.get("password");

        // 암호화된 패스워드
        String storedPasswordHash = userService.getUserPassword(userId);

        // 입력된 비밀번호, 암호화된 비밀번호 비교
        boolean passwordMatches = passwordEncoder.matches(enteredPassword, storedPasswordHash);

        if (passwordMatches) {
            List<Map<String, Object>> loginList 
                                  = userService.login(userId, storedPasswordHash);

            HttpSession session = httpRequest.getSession();
            session.setAttribute("userId", userId);

            return ResponseEntity.ok(loginList);
        } else {
            return ResponseEntity.status(
                       HttpStatus.UNAUTHORIZED).body(Collections.emptyList()
                   );
        }
    }
}

 

passwordEncoder를 통해 비밀번호 암호화를 시켜서 디비에 저장한 상태일 것이기에

.matches를 이용해서 입력된 비밀번호와 암호화된 상태로 디비에 저장된 비밀번호가 서로 맞는지 확인합니다.

비밀번호가 동일하면 로그인되고, 아니라면 빈값이 넘어가게 됩니다.

 

UserService.java

package org.backend.web.common.service;

import org.backend.web.common.dao.UserDao;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    public List<Map<String, Object>> login(String userId, String hashedPassword) {
        return userDao.login(userId, hashedPassword);
    }

    public String getUserPassword(String userId) {
        return userDao.getUserPassword(userId);
    }
}

 

서비스단은 간단하게 dao와 연결만 해줍니다.

 

 

UserDao.java

package org.backend.web.common.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class UserDao {
    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    public List<Map<String, Object>> login(String userId, String hashedPassword) {
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("password", hashedPassword);

        List<Map<String, Object>> loginInfo = new ArrayList<>();

        loginInfo = sqlSessionTemplate.selectList("UserMapper.login", params);

        return loginInfo;
    }

    public String getUserPassword(String userId) {
        try {
            return sqlSessionTemplate.selectOne("UserMapper.encodePw", userId);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }
}

 

params에 아이디, 비밀번호를 담아서 실질적으로 로그인을 진행해 줍니다.

 

 

UserMapper.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="UserMapper">
    <select id="login" resultType="Map" parameterType="Map">
        SELECT USER_NM
          FROM USER_MST
         WHERE USER_ID = #{userId}
           AND USER_PASSWORD = #{password}
    </select>

    <select id="encodePw" resultType="String" parameterType="String">
        SELECT USER_PASSWORD
          FROM USER_MST
         WHERE USER_ID = #{userId}
    </select>
</mapper>

 

이렇게 간단한 백엔드 로직이 완성되었습니다.

 

 

프론트앤드 부분이 궁금하시다면 아래 링크로 들어가주세요.

2024.01.18 - [Web Programming/JavaScript] - [React + SpringSecurity + DB] 로그인 구현하기 (frontend)

 

[React + SpringSecurity + DB] 로그인 구현하기 (frontend)

리엑트와 스프링을 연결할 때 리엑트 설정을 하셨을 겁니다. axiosInstance.jsx를 만들었다던가, App.jsx를 수정했다던가 저는 둘다 수정했기 때문에 전부 있는 상황에서 구현하겠습니다. App.jsx function

jaey0ng.tistory.com