[ Spring Security 적용법 → STS : 4.22.0 / spring boot version : 3.3.3 / java : 21 / spring security 6 ]
*** html과 @RestController 사용하여 구현 ***
→ @RestController와 정적 HTML 자원을 함께 사용하는 것은 RESTful 아키텍처를 유지하면서 사용자 인터페이스를 제공하는 효과적인 방법이며, 이러한 접근 방식은 클라이언트와 서버의 독립성을 유지하며, 다양한 클라이언트에서 API를 통해 데이터에 접근할 수 있게 해주기 때문에 @RestController와 HTML을 사용하여 구현하였다.
→ @Controller 사용 시, Security의 permiAll() 메서드가 작동하지 않는 문제 발생
1. Project 생성
File → New → Project → Spring Starter Project
2. Project 설정 및 의존성(Dependency) 설정
Finish 클릭하여 Project 생성
3. com.eugeneprogram 패키지 안에 dao 패키지를 생성하고 dao 패키지 안에 TestMapper 인테페이스 생성
package com.eugeneprogram.dao;
import java.util.List;
import java.util.Map;
public interface TestMapper {
public int comparePw(String pw, String id) throws Exception;
public List<Map<String, Object>> getAllList() throws Exception;
public void insertTest(String name, String id, String pw, String phone) throws Exception;
}
4. com.eugeneprogram 패키지 안에 service 패키지를 생성하고 TestService 클래스 생성
package com.eugeneprogram.service;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.eugeneprogram.dao.TestMapper;
@Service
public class TestService {
@Autowired
TestMapper testMapper;
public int comparePw(String pw, String id) throws Exception {
return testMapper.comparePw(pw, id);
}
public List<Map<String, Object>> getAllList() throws Exception {
return testMapper.getAllList();
}
public void insertTest(String name, String id, String pw, String phone) throws Exception {
tcMapper.insertTest(name, id, pw, phone);
}
}
5. src/main/resources 위치에 mapper 폴더를 추가하고 testMapper 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.eugeneprogram.dao.TestMapper">
<select id="comparePw" resultType="Integer" parameterType="String">
SELECT COUNT(*) AS tc_pw FROM tc WHERE tc_pw = #{pw} AND tc_mail = #{id};
</select>
<select id="getAllList" resultType="java.util.Map">
SELECT * FROM tc;
</select>
<insert id="insertTest" parameterType="String">
insert into tc(tc_name, tc_mail, tc_pw, tc_phone) values(#{name}, #{id}, password(#{pw}), #{phone});
</insert>
</mapper>
6. com.eugeneprogram 패키지 안에 config 패키지 생성 후 DatabaseConfig 클래스 생성
package com.eugeneprogram.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@MapperScan(basePackages = "com.eugeneprogram.dao")
@EnableTransactionManagement
public class DatabaseConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
return sessionFactory.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
final SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
return sqlSessionTemplate;
}
}
7. com.eugeneprogram 패키지 안에 security 패키지 생성 후 CustomAuthenticationProvider 클래스 생성
package com.eugeneprogram.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import com.eugeneprogram.service.TestService;
import java.util.Arrays;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
//TcService에서 즉 tc 테이블에 있는 id, pw들로 로그인을 해야한다.
TestService testService;
@Override
// 만약 인증이 필요하게 된 경우 인증에 필요한 id, pw를 설정해야 하는데, authenticate 메서드를 통해 임의의 id, pw를 통해 인증하게끔 하는 메서드이다.
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
int num = 0;
// username과 password는 각각 인증 시 입력된 값을 받아온다.
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
try {
// 임의로 만든 쿼리를 username과 password 사용하여 tc테이블에 값이 존재하는지 여부를 판별한다.
num = testService.comparePw(password, username);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(num);
// num에 값이 존재한다면, username과 password가 tc 테이블에 존재하므로 인증 허가
// num에 값이 존재하지 않다면, username과 password가 tc 테이블에 존재하지 않으므로 에러 발생
if (num > 0) {
return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
} else {
throw new AuthenticationCredentialsNotFoundException("Error!");
}
}
@Override
public boolean supports(Class<?> authenticationType) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authenticationType);
}
}
8. com.eugeneprogram.config 패키지 안에 SecurityConfig 클래스 생성
package com.eugeneprogram.config;
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.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.eugeneprogram.security.CustomAuthenticationProvider;
import static org.springframework.security.config.Customizer.withDefaults;
import org.springframework.beans.factory.annotation.Autowired;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomAuthenticationProvider authenticationProvider;
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// test.html을 보면 알겠지만 POST 처리를 통해 값을 '/tc/postTest.do'로 값을 넘긴다.
// POST 처리 시 CSRF(Cross-Site Request Forgery)가 사용되어야 하는데,
// csrf.ignoringRequestMatchers의 경로들은 CSRF토큰을 설정하지 않아 SecurityConfig에서 임의로 CSRF 요청을 무시한다는 내용.
.csrf(csrf -> csrf.ignoringRequestMatchers(
new AntPathRequestMatcher("/tc/postTest.do")
))
.authorizeHttpRequests((authz) -> authz
.requestMatchers(
new AntPathRequestMatcher("/"),
new AntPathRequestMatcher("/tc/**") // '/tc'로 시작하는 모든 경로들을 의미
).permitAll() // permitAll() 메서드를 사용하여, requestMatchers안에 있는 경로들은 인증 없이 접속할 수 있다.
.anyRequest().authenticated()) // 그 외의 요청(=경로)은 인증이 필요하게 된다.
.httpBasic(withDefaults());
return http.build();
}
}
9. com.eugeneprogram 패키지 안에 controller 패키지 생성 후 MainController 클래스 생성
package com.eugeneprogram.controller;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.eugeneprogram.service.TestService;
@RestController
public class MainController {
@Autowired
TestService testService;
@GetMapping(value="/tc/selectTC.do")
public List<Map<String, Object>> getTc() throws Exception {
return testService.getAllList();
}
//서버 사이드에서 요청을 포워드
@GetMapping("/tc/testPage.do")
public void getTestPage(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getRequestDispatcher("/tc/test.html").forward(request, response);
}
@PostMapping("/tc/postTest.do")
public void handlePostRequest(String name, String id, String pw, String phone, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.print(name);
testService.insertTest(name, id, pw, phone);
//Post로 처리하는데 getRequestDispatcher는 get요청이므로 오류 발생, 따라서 다른 컨트롤러의 url경로로 sendRedirect처리를 한다.
response.sendRedirect("/tc/tcList.do");
}
@GetMapping("/tc/tcList.do")
public void getTcList(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getRequestDispatcher("/tc/testDbList.html").forward(request, response);
}
}
10. src/main/resources에 위치한 application.properties 수정
spring.application.name=TestWeb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.thymeleaf.prefix=classpath:/static/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/institute
spring.datasource.username=root
spring.datasource.password=1234
#디버그 활용 시 사용할 것
#logging.level.org.springframework.security=DEBUG
#logging.level.org.springframework.web=DEBUG
[ “institute” DB의 tc 테이블 구성 ]
tc_id(pk) |
tc_name |
tc_mail |
tc_pw |
tc_phone |
5 |
kim |
aaa@naver.com |
*89C6B530AA78695E257E55D63C00A6EC9AD3E977 → password(“1111”) |
010-1111-1111 |
6 |
park |
bbb@naver.com |
*D142A988197D6E8B1D3D0945283450811637B73F → password(“2222”) |
010-2222-2222 |
7 |
heo |
ccc@naver.com |
*0C794B34A1890E1AC777079465E38D1376F8FC24 → password(“3333”) |
010-3333-3333 |
11. src/main/webapp 위치에 tc 폴더를 생성한 후 test.html 파일 생성
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Test Form</title>
</head>
<body>
<form action="/tc/postTest.do" method="post">
Name : <input type="text" id="name" name="name"><br>
Phone : <input type="text" name="phone"><br>
ID : <input type="text" name="id"><br>
PW : <input type="password" name="pw"><br>
<button type="submit">Submit</button>
</form>
</body>
</html>
12. src/main/webapp/tc 위치에 testDbList.html 파일 생성
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<script>
// $.ajax 메소드를 사용하여 /tc/selectTC.do 경로에 GET 요청을 보낸다.
// 성공 시 처리: 요청이 성공하면, 응답(response)이 배열인지 확인합니다. 배열이 존재하고 요소가 있을 경우, 각 요소(item)에 대해 반복한다.
// 각 항목의 tc_id, tc_name, tc_phone, tc_pw, tc_mail 값을 <p> 태그로 출력한다.
// 데이터가 없을 경우: 배열이 비어있으면 "No data found"라는 메시지를 표시한다.
// 오류 처리: 요청 중 오류가 발생하면 경고창을 띄우고 오류 정보를 콘솔에 기록합니다.
function codeList() {
$.ajax({
type: "GET",
url: "/tc/selectTC.do",
success: function(response) {
$("#div1").empty();
if (Array.isArray(response) && response.length > 0) {
response.forEach(function(item) {
$("#div1").append(
"<p>ID: " + item.tc_id +
", Name: " + item.tc_name +
", Phone: " + item.tc_phone +
", PW: " + item.tc_pw +
", Mail: " + item.tc_mail +
"</p>"
);
});
} else {
$("#div1").text("No data found");
}
},
error: function(xhr, status, error) {
alert("error:: " + xhr.responseText);
console.error("Error:: ", xhr, status, error);
}
});
}
</script>
<!-- 'tc조회' 버튼 누를 시 codeList 실행 -->
<button onclick="codeList()">tc 조회</button> <br>
<div id="div1"></div>
<br><br>
<button onclick="location.href='/tc/testPage.do'">뒤로가기</button>
</body>
</html>
13. Boot Dashboard에서 프로젝트 우클릭하고 (Re)start 클릭하여 실행하기
→ 프로젝트에 에러가 없다면 위와 같이 console 창에 출력된다.
14. Web(= 크롬)에서 localhost:8080/tc/testPage.do 입력하고 엔터
→ 위에서 만든 /tc/testPage.do 경로에 의해 test.html 출력된다.
→ Name에 son, Phone에 010-9999-9999, ID에 zzz@naver.com, PW에 9999 입력 후 Submit 클릭
→ tc 조회 클릭 시
→ 위와 같이 tc 테이블에 있는 정보를 모두 조회하는 기능을 한다. “뒤로가기”는 아까 입력했던 페이지로 이동.
→ son의 ID값이 8이 아닌 이유는 tc_id가 primary key인데 heo 다음 오류 값이 있어 지우고 다시 son 값을 넣은 것이니 순서가 다를 수 있다. 다른 값도 마찬가지로 tc_id 순서가 다를 수 있다. 오류가 아니니 상관없다.
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
4 |
fetch()와 await fetch()
![]() | 김지훈 | 2025.04.17 | 11 |
» |
Spring Security 적용법
![]() | 김지훈 | 2024.09.20 | 53 |
2 |
@Controller와 @RestController의 차이점
![]() | 김지훈 | 2024.07.19 | 31 |
1 | SpringToolSuite 4를 이용한 Spring Boot Web 프로그래밍 기초 | Eugene | 2023.10.17 | 1849 |