텐의 개발 블로그
[Spring Legacy] 스프링 시큐리티 로그인,회원가입 처리하기 본문
이전 포스팅에서 스프링 레거시 + 스프링 시큐리티를 적용하는 방법에 대해서 알아보았습니다.
이번에는 스프링 시큐리티를 이용해서 본격적으로 로그인+회원가입을 어떤식으로 처리하는지에 대해서 알아보도록 하겠습니다.
이전 포스팅은 아래의 링크를 참고해주세요.
[Spring Legacy]스프링 레거시에서 스프링 시큐리티 적용하기
이번 포스팅에서는 Spring 레거시 + mybatis 환경에서 스프링 시큐리티를 셋팅하는 방법에 대해서 얘기를 해보겠습니다. 최근에 스프링 부트 기준으로 스프링 시큐리티를 적용할때는 부트 자체에서
ten99.tistory.com
참고로 해당 포스팅은 아래의 기준으로 작성 되었습니다.
- jdk version : 1.8
- Spring version : 5.0.7.RELEASE
- Spring Security : 5.0.6.RELEASE
- mybatis 방식 사용
1. root-context.xml에서 base-package 위치 체크
2. ACCOUNT 테이블 생성
3. Account.java(entity) 생성
4. SecurityAccount.java 생성
5. SecurityAccountService.java 생성
6. AccountService.java 생성
7. AccountServiceImpl.java 생성
8. AccountMapper.java 생성
9. AccountMapper.xml 생성
10. Security-context.xml 내용 수정(이전 포스팅에서 생성 완료)
11. Login 컨트롤러 생성(로그인 + 회원가입 같이 처리)
12. Board 컨트롤러 생성(로그인 처리 이후 페이지 이동)
13. login.jsp 생성
14. signIn.jsp 생성
스프링 레거시 + 스프링 시큐리티 + mybatis를 활용하여 로그인 및 회원가입을 처리하기 위해서는 위와 같은 과정들이 필요합니다. 흐름만 저렇게 흘러간다고 생각을 하시면 됩니다.
1. root-context.xml에서 base-package 위치 체크
현재 mybatis를 이용하여 진행하고 있기 때문에 Spring Security를 사용하기 위해서 BoardMapper.java 만들어야 합니다.
mapper는 base-package를 기준으로 지정된 패키지의 mapper 어노테이션 찾는 방식으로 작동을 하기에 base-package의 위치를 먼저 확인하고 시작했습니다.

저는 kr.bit.mapper라는 패키지를 기준으로 base-package의 경로를 지정하였습니다.
즉, kr.bit.mapper라는 패키지에 mapper.java를 만들어주면 됩니다.
2. ACCOUNT 테이블 생성
CREATE TABLE ACCOUNT (
username VARCHAR(100) PRIMARY KEY,
name VARCHAR(100),
password VARCHAR(256),
role VARCHAR(20)
);
ACCOUNT라는 테이블을 mysql의 데이터베이스에 생성해줍니다.
- username : 계정 이름(기본키)
- name : 유저 이름
- password : 계정 비밀번호
- role : 권한
3. Account 클래스(entity) 생성
package kr.bit.entity;
public class Account {
private String username;
private String name;
private String password;
private String role;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
저는 entity 전용 패키지를 따로 만들었고 그안에 Account.java라는 파일명으로 엔티티를 작성해줬습니다.
4. SecurityAccount 클래스 생성
package kr.bit.security;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import kr.bit.entity.Account;
public class SecurityAccount extends User {
public SecurityAccount(Account account) {
super(
account.getUsername(),
account.getPassword(),
AuthorityUtils.createAuthorityList(account.getRole())
);
}
}
kr.bit.security라는 시큐리티 관련 패키지를 만들어주었고 해당 패키지 안에 SecurityAccount 클래스를 생성하였습니다.
SecurityAccount는 스프링 시큐리티에서 제공하는 User를 extends(상속) 받았습니다.
SecurityAccount 클래스 내부에 Account 엔티티를 매개변수로 하여 각각 Username,Password,Role을 get하는 생성자를 만들었습니다.
5. SecurityAccountService.java 생성
package kr.bit.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import kr.bit.mapper.AccountMapper;
public class SecurityAccountService implements UserDetailsService {
@Autowired
AccountMapper accountMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SecurityAccount account = new SecurityAccount(accountMapper.getAccountByUserName(username);
return account;
}
}
SecurityAccountService.java는 UserDetailService를 상속받아서 생성해주었습니다.
위에서 생성한 SecurityAccount를 SecurityAccountService에서 인스턴스로 생성합니다.
accountMapper.getAccountById(id)로 db의 account 테이블에 있는 데이터(username,password,role)를 username을 기본키로 하여 가져오고 해당 데이터를 다시 스프링 내부로 리턴 합니다. 스프링 내부에서는 리턴받은 데이터를 기준으로 하여 로그인 허가에 대한 인증을 판단합니다.

보충 설명을 하자면 login page에서 POST 방식으로 username이랑 password를 Spring Seucurity의 @PostMapping("/login") 으로 넘어가고 SecurityAccountService의 UserDetails로 넘어옵니다.
UserDetails에서 이제 getAccountByUserName(username)가 데이터베이스의 account에 접근하여 username을 기본키로 데이터(username,password,role)를 가져와서 다시 return 해줍니다. 물론 AccountMapper.xml에서 쿼리를 생성해줘야 합니다. 이 과정은 뒤에서 언급하겠습니다

브레이킹 포인트를 찍고 로그인 페이지에서 id,password를 입력하고 post로 전송후에 디버깅한 결과입니다.
username, password, role이 account에 들어있는 상태죠. 이제 저 데이터들이 스프링 내부로 return 되는겁니다.
6. AccountService.java 생성
package kr.bit.service;
import kr.bit.entity.Account;
public interface AccountService {
void signIn(Account account);
}
AccountService.java를 인터페이스로 생성해줍니다.
AccountService ~ AccountServiceImpl 모두 signIn 즉, 회원가입 로직만 처리합니다.
7. AccountServiceImpl.java 생성
package kr.bit.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import kr.bit.entity.Account;
import kr.bit.mapper.AccountMapper;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountMapper accountMapper;
@Override
public void signIn(Account account) {
accountMapper.insertAccount(account);
}
}
8. AccountMapper.java 생성
package kr.bit.mapper;
import org.apache.ibatis.annotations.Mapper;
import kr.bit.entity.Account;
@Mapper
public interface AccountMapper {
Account getAccountByUserName(String username);
void insertAccount(Account account);
}
AccountMapper.xml에 접근할 AccountMapper를 작성해줍니다. 결론적으로 AccountMapper에서는 2가지의 로직을 처리합니다.
1) getAccountByUserName
-> username을 매개변수로 데이터베이스의 Account 테이블에 접근하여 계정과 관련된 데이터를 조회합니다.
2) insertAccount
-> account를 매개변수로 하여 계정을 insert합니다. 즉, 회원가입을 구현합니다.
9. AccountMapper.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="kr.bit.mapper.AccountMapper">
<!-- 로그인 -->
<select id="getAccountByUserName" parameterType="string" resultType="kr.bit.entity.Account">
SELECT
username
, name
, password
, role
FROM account
WHERE username = #{username};
</select>
<!-- 회원가입 -->
<insert id="insertAccount" parameterType="kr.bit.entity.Account">
INSERT INTO account (
username
, name
, password
, role
) VALUES (
#{username}
, #{name}
, #{password}
, #{role}
)
</insert>
</mapper>
10. Security-context.xml 내용 수정(이전 포스팅에서 생성 완료)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:sec="http://www.springframework.org/schema/security"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<sec:http pattern="/resources/**" security="none" />
<sec:http use-expressions="true">
<sec:intercept-url pattern="/login" access="permitAll()" />
<sec:intercept-url pattern="/signIn" access="permitAll()" />
<sec:intercept-url pattern="/board/**" access="isAuthenticated()" />
<sec:form-login login-page="/login"
login-processing-url="/login"
authentication-failure-url="/login"
username-parameter="username"
password-parameter="password"
default-target-url="/board/list"
always-use-default-target="true"
/>
<sec:logout logout-url="/logout" logout-success-url="/login" />
</sec:http>
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<bean id="securityAccountService" class="kr.bit.security.SecurityAccountService"/>
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="securityAccountService">
<sec:password-encoder ref="bCryptPasswordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
<sec:intercept-url pattern="/login" access="permitAll()" />
<sec:intercept-url pattern="/signIn" access="permitAll()" />
- /login 및 /signIn 주소에 대해서 permitAll() 즉, 인증절차 없이 접근가능하게 access 권한을 설정합니다.
- 로그인과 회원가입은 권한이 없더라도 접근이 가능해야하기에 해당 주소에 대해서는 permitAll() 부여
<sec:intercept-url pattern="/board/**" access="isAuthenticated()" />
- /board/** 주소에 대해서는 isAuthenticated() 즉, 권한에 관계없이 로그인만 성공했다면 접근하도록 처리했습니다.
- 현재 필자는 /board/list가 메인페이지라 /board/** 형식의 주소에 대해서는 로그인만 했다면 권한에 관계없이 접근 가능하도록 처리했습니다.
<sec:form-login login-page="/login"
login-processing-url="/login"
authentication-failure-url="/login"
username-parameter="username"
password-parameter="password"
default-target-url="/board/list"
always-use-default-target="true" />
- form-login은 스프링 시큐리티에서 제공하는 기본 로그인 페이지가 아닌 사용자가 원하는 로그인 페이지를 따로 개발하여 적용하고 싶은 경우에 사용합니다.
| login-page | 로그인 페이지 주소 지정 |
| login-processing-url | 로그인 페이지의 form의 action 주소 |
| authentication-failure-url | 로그인 실패했을때 호출할 주소 |
| username-parameter | 로그인 페이지의 form의 username를 이름 지정 (default: username) |
| password-parameter | 로그인 페이지의 form의 password를 이름 지정 (default: password) |
| default-target-url | 로그인 성공했을때 호출할 주소 |
| always-use-default-target | 로그인 성공시 default-target-url을 무조건 호출할지에 대한 true or false(true로 설정시 무조건 호출) |
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<bean id="securityAccountService" class="kr.bit.security.SecurityAccountService"/>
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="securityAccountService">
<sec:password-encoder ref="bCryptPasswordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
ㄴ 스프링 시큐리티 5.x 버전부터 bCryptPasswordEncoder가 기본값으로 들어가야 합니다. 5.X 이전 버전에서는 noop(평문)이 기본전략이었는데 버전업 이후로는 bCrypt가 패스워드 기본전략이라고 하네요.
ㄴ bCryptPasswordEncoder는 스프링 시큐리티에서 제공하는 클래스로 비밀번호에 대해 암호화 작업을 하는데 사용합니다.
11. Login 컨트롤러 생성(로그인 + 회원가입 같이 처리)
package kr.bit.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import kr.bit.entity.Account;
import kr.bit.security.SecurityAccount;
import kr.bit.service.AccountServiceImpl;
@Controller
public class LoginController {
@Autowired
AccountServiceImpl accountService;
@GetMapping("/login")
public String loginPage() {
return "login";
}
@GetMapping("/signIn")
public String signInPage() {
return "signIn";
}
@PostMapping("/signIn")
public String signIn(Account account) {
account.setPassword(new BCryptPasswordEncoder().encode(account.getPassword()));
accountService.signIn(account);
return "login";
}
}
회원가입 같은 경우는 회원가입 페이지에서 password가 들어오면
account.setPassword(new BCryptPasswordEncoder().encode(account.getPassword()));로 들어가서 암호화된 password를 account에 set처리 해줍니다.
그뒤에 이제 accountService.signIn(account);를 통해 회원가입 로직을 실행시켜 username 및 password를 포함한 데이터를 가지고 데이터베이스의 ACCOUNT 테이블에 계정을 생성합니다.
12. Board 컨트롤러 생성(로그인 처리 이후 페이지 이동)
@Controller
@RequestMapping("/board/*")
public class BoardController {
@Autowired
BoardService boardService;
@RequestMapping("/list")
public String getList() {
return "board/list"; //view
}
로그인 성공시 board/list로 이동하도록 Security-context.xml에서 설정하였기에 간단하게 컨트롤러를 구성하였습니다.
13. login.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri ="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<c:set var="cpath" value="${pageContext.request.contextPath }"/>
<!DOCTYPE html>
<html lang="en">
<head>
<title>로그인</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="${cpath}/resources/css/style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
<div class="container mt-3">
<div class="card">
<div class="card-body">
<form action="/login" method="post">
<sec:csrfInput/>
<div class2="form-group">
<label for="memID">아이디:</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
<label for="mempwd">비밀번호:</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-primary form-control">로그인</button>
<button type="button" class="btn btn-primary form-control" onclick="location.href='${cpath}/signIn'" style="margin-top:5px;">회원가입</button>
</form>
</div>
</div>
</div>
</html>

부트스트랩을 이용해서 간단하게 username 및 password를 form 전송할수 있게 jsp를 작성하였습니다.
submit을 누르면 /login으로 Post 전송이 가능하게 해야합니다.
14. signIn.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri ="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<c:set var="cpath" value="${pageContext.request.contextPath }"/>
<!DOCTYPE html>
<html lang="en">
<head>
<title>회원가입</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="${cpath}/resources/css/style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
<div class="container mt-3">
<div class="card">
<div class="card-body">
<form action="/signIn" method="post">
<sec:csrfInput/>
<div class="form-group">
<input class="form-control" type="text" id="username" name="username" placeholder="계정명" />
</div>
<div class="form-group">
<input class="form-control" type="text" name="name" placeholder="사용자명" />
</div>
<div class="form-group">
<input class="form-control" type="password" name="password" placeholder="비밀번호" />
</div>
<div class="form-group">
<select class="custom-select custom-select-sm" name="role" size="1">
<option value="">권한을 선택해주세요.</option>
<option value="ROLE_ADMIN">관리자</option>
<option value="ROLE_USER">유저</option>
</select>
</div>
<button class="btn btn-primary" type="submit">회원가입</button>
</form>
</div>
</div>
</div>
</html>

username(계정명), name(사용자명), password(비밀번호), Role(권한)을 받아서 /signIn으로 Post전송하여 계정등록 할수있게 JSP를 작성했습니다.
'Spring Legacy' 카테고리의 다른 글
| [Spring Legacy] 카카오 책 검색 api 및 Ajax를 이용하여 책 검색하기 (0) | 2023.06.10 |
|---|---|
| [Spring Legacy] 스프링 시큐리티, 현재 로그인한 유저 정보 가져오기 (0) | 2023.06.08 |
| [Spring Legacy] 스프링시큐리티 Post 전송시, 403 금지됨 에러 해결방법 (0) | 2023.06.07 |
| [Spring Legacy] 스프링 레거시에서 스프링 시큐리티 적용하기 (0) | 2023.06.02 |