Shop ํ๋ก์ ํธ Security ์ ์ฉ
gradle์ security ์ฌ์ฉ์ ์ํด ์๋ ์ฝ๋ ์ถ๊ฐ
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.security:spring-security-test'
Security์์ ์๋ ๋ก๊ทธ์ธ ํด์ฃผ๊ธฐ ์ํด memberController์ ์์ฑํ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ์ฝ๋ ์ญ์
Security๋ก ๋ก๊ทธ์ธ์ ๋ง์ถฐ์ ์ฟผ๋ฆฌ ์์ .
<select id="login" resultMap="member">
SELECT MEM_ID
, MEM_PW
, MEM_ROLE
FROM SHOP_MEMBER
WHERE MEM_ID = #{memId}
AND MEM_STATUS != 3 <!-- ํํด ๊ณ์ ์ด๋ฉด ์ ๋จ! -->
</select>
SecurityConfig ํด๋์ค ์์ฑ
css, img, js์ security ์ถฉ๋ ๋ง์์ฃผ๋ ์ฝ๋ ์์ฑ.
//css,js,img security์ ์ถฉ๋ ๋ฐฉ์งํ๋ ์ฝ๋
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/js/**", "/css/**", "/upload/**");
}
์ธ์ฆ ์ธ๊ฐ ์ค์ ์ฝ๋ ์์ฑ
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception{
security.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/"
, "/item/itemList"
, "/item/itemDetail"
, "/member/isDuplicateMemId" //id ์ค๋ณต ์ฒดํฌ ํ์ด์ง
, "/member/join"
, "/member/loginForm").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN") //ํด๋น ๊ถํ๋ง ๋ค์ด์ฌ ์ ์๊ฒ ์ธ๊ฐ ์ค์ .
// /admin/* ์ฝ๋ ์์ฑ ์
// /admin/manage (O)
// /admin/item/manage(x)
//.requestMatchers("/admin/**").hasAnyRole("ADMIN", "MANAGER")) //๋ ์ค ํ๋๋ง ๊ถํ ์์ผ๋ฉด ๋ค์ด์ฌ ์ ์์
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/member/loginForm")
.usernameParameter("memId")
.passwordParameter("memPw")
.loginProcessingUrl("/member/login") //๋ก๊ทธ์ธ ajax์ ๋์ ์คํ๋จ. ๋์ผํ๊ฒ ajax์์ ์ด๋ ๊ฒฝ๋ก ๋ง์ถฐ์ฃผ๋ฉด ๋จ.
.successHandler(getSucessHandler()) //๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์คํ๋๋ ํด๋์ค
.failureHandler(getFailureHandler())
.permitAll();
return security.build();
}
//css,js,img security์ ์ถฉ๋ ๋ฐฉ์งํ๋ ์ฝ๋
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/js/**", "/css/**", "/upload/**");
}
//๋น๋ฐ๋ฒํธ ์ํธํ ๊ฐ์ฒด ์์ฑ
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//๋ก๊ทธ์ธ ์คํจ ์ ์คํ๋๋ ํด๋์ค ๊ฐ์ฒด ์์ฑ
@Bean
public FailureHandler getFailureHandler() {
return new FailureHandler();
}
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์คํ๋๋ ํด๋์ค ๊ฐ์ฒด ์์ฑ
@Bean
public SucessHandler getSucessHandler() {
return new SucessHandler();
}
}
shop ํ๋ก์ ํธ์์๋ ๋ก๊ทธ์ธ์ ajax๋ฅผ ํตํด์ ํ๊ณ ์์
> ๋ก๊ทธ์ธ ์ฑ๊ณต ๋ฐ ์คํจ ์ ์คํ๋๋ ํด๋์ค๋ฅผ ์์ฑ ํด์ security ๋ก๊ทธ์ธ ์ค์
- ๋ก๊ทธ์ธ ์ฑ๊ณต
SucessHandler ์์ฑ
SimpleUrlAuthenticationSuccessHandler ์ธํฐํ์ด์ค๋ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์คํ๋๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์.
์ด ์ธํฐํ์ด์ค๋ฅผ ์์ ๋ฐ์์ ๋ฉ์๋ ์ค๋ฒ๋ผ์ด๋ฉ.
๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์คํ๋๋ SucessHandler ํด๋์ค๋ ๋ก๊ทธ์ธ ajax์ ๋์์ ์คํ๋จ.
ajax๋ฅผ ํตํด ๋ก๊ทธ์ธํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ajax์ success ๊ตฌ๋ฌธ์ผ๋ก ๋์๊ฐ์ ํ์ด์ง ์ด๋.
public class SucessHandler extends SimpleUrlAuthenticationSuccessHandler{
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์คํ๋๋ ํด๋์ค
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
System.out.println("success handler ์คํ~");
//์๋ฐ์์ html ๊ธ์ ๊ทธ๋ฆฌ๋ ์ฝ๋
PrintWriter p = response.getWriter();
p.write("success"); //๋ก๊ทธ์ธ ์ฑ๊ณตํ๋ฉด ์ด ํด๋์ค์ header.js์ ajax(๋ก๊ทธ์ธํ๋) ๋์์ ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ์ธ ํ success ๊ตฌ๋ฌธ์ผ๋ก ๋์๊ฐ.
//success๋ผ๋ ๊ธ์๋ฅผ ajax์ success ๊ตฌ๋ฌธ result๋ก ๋ฐ์๊ฐ๋ ๊ฒ.
p.flush();
//super.onAuthenticationSuccess(request, response, authentication);
}
}
-๋ก๊ทธ์ธ ์คํจ
FailureHandler ์์ฑ
SimpleUrlAuthenticationFailureHandler ์ธํฐํ์ด์ค๋ ๋ก๊ทธ์ธ ์คํจ ์ ์คํ๋๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์.
์ด ์ธํฐํ์ด์ค๋ฅผ ์์ ๋ฐ์์ ๋ฉ์๋ ์ค๋ฒ๋ผ์ด๋ฉ.
๋ก๊ทธ์ธ ์คํจ ์ ์คํ๋๋ FailureHandler ํด๋์ค๋ ๋ก๊ทธ์ธ ajax์ ๋์์ ์คํ๋จ.
๋ก๊ทธ์ธ ์คํจ ์ ์๋ฌ ๋ฉ์ธ์ง๊ฐ ๋จ๊ฒ ์์ธ์ฒ๋ฆฌ ์ค์ .
๋ค์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๊ฐ๋ ์ด์ ์ ์ ๋ ฅํ id๊ฐ ๋จ์ ์๊ฒ memId ๋ฐ์ดํฐ ๊ฐ์ ธ๊ฐ์ผํจ.
ajax๋ฅผ ํตํด ๋ก๊ทธ์ธํ๊ณ ์๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ์ธ ์คํจ ํ header.js์ ๋ก๊ทธ์ธ ajax success ๊ตฌ๋ฌธ์ผ๋ก ๋์๊ฐ์ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํ๋ฉด์ ๋ฟ๋ ค์ค๋ค.
//๋ก๊ทธ์ธ ์คํจ ์ ์๋์ผ๋ก ์คํ๋๋ ํด๋์ค
public class FailureHandler extends SimpleUrlAuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//์๋ฌ ๋ฉ์ธ์ง
String eMsg = "";
if(exception instanceof BadCredentialsException) {
eMsg = "์์ด๋ ํน์ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํ์ญ์์ค.";
}
else if(exception instanceof UsernameNotFoundException) {
eMsg = "๊ณ์ ์ด ์กด์ฌํ์ง ์์ต๋๋ค.";
}
else {
eMsg = "์ ์ ์๋ ์ด์ ๋ก ๋ก๊ทธ์ธ์ ์คํจํ์์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํ์ญ์์ค.";
}
//๋ก๊ทธ์ธ ์ ์
๋ ฅํ id๊ฐ
String memId = request.getParameter("memId");
//java์์ html๋ก ํ
์คํธ ๋ณด๋(?
PrintWriter p = response.getWriter();
p.write("fail");
p.flush();
}
}
header.js ์์
//๋ก๊ทธ์ธ ํจ์
function login(){
//joinModal์๋ memId ์์ ์์ญ ์ ํํ๊ฒ ์ง์ . ๋ณดํต ๊ฒน์น๊ฒ ์์ฑx
const memId = document.querySelector('#loginModal #memId').value;
const memPw = document.querySelector('#loginModal #memPw').value;
//ajax start
$.ajax({
url: '/member/login', //์์ฒญ๊ฒฝ๋ก (security, ajax ์ฝ๋ ๋์ ์คํ๋จ.)
type: 'post',
data: { 'memId' : memId, 'memPw' : memPw }, //ํ์ํ ๋ฐ์ดํฐ
//success๋ ํจ์๋ฅผ ์ ์ํ ๊ฒ.
success: function(result) { //์ํ๋ฆฌํฐ ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ์คํ๋๋ ํด๋์ค์์ ๋ณด๋ธ success ๊ธ์ result์ ๋ฐ์์ด.
//๋ก๊ทธ์ธ ์ฑ๊ณต ์ true
if(result == 'success') {
location.href = '/'; //์ธ๋ฑ์ค ์ปจํธ๋กค๋ฌ ํ์ด์ง๋ก ์ด๋ > ์ํ ๋ชฉ๋ก ํ๋ฉด ์ด๋
}
//๋ก๊ทธ์ธ ์คํจ (์์ด๋ ๋ฐ ๋นจ๊ฐ ๊ธ์)
else{
//alert('๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ํ์ธํ์ญ์์ค.');
//๋ก๊ทธ์ธ ์ค๋ฅ ๋ฉ์์ง
//๋ก๊ทธ์ธ ์คํจ ์ ์ถ๊ฐ๋๋ div ํ๊ทธ ์ญ์ (๋ฌธ๊ตฌ ์คํจํ ์๋งํผ ๋จ๋ ๊ฒ ๋ฐฉ์ง)
const error_div = document.querySelector('#errorDiv');
if (error_div != null) {
error_div.remove();
}
//๋ก๊ทธ์ธ ์คํจ ์ ์ค๋ฅ ๋ฌธ๊ตฌ
let str = '';
str += '<div id="errorDiv" style="color: red; font-size: 0.9rem;">';
str += '๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ํ์ธํ์ญ์์ค.';
str += '</div>';
const login_error_div = document.querySelector('#loginErrorDiv');
login_error_div.insertAdjacentHTML('beforeend', str);
//id, pw input ํ๊ทธ ์ด๊ธฐํ (๋ก๊ทธ์ธ ๋ฒํผ ๋นผ๊ณ ๋ค ๋ค๊ณ ์์ผ ๋ผ์ All)
//์๋ก์ด for๋ฌธ //ํ์
์ด ๋ฒํผ์ธ ๊ฒ ์ ์ธํ๊ณ input ํ๊ทธ ๋ค ์ ํ.
document.querySelectorAll('#loginModal input:not([type="button"])').forEach(function(t, index){
t.value; //input ํ๊ทธ ์์ฒด๊ฐ t์.
});
/*์์ฃผ ์ฐ๋ for๋ฌธ
const tags = document.querySelectorAll('#loginModal input:not([type="button"])');
for(const t of tags) {
t.value = '';
} */
}
},
error: function() {
alert('์คํจ');
}
});
//ajax end
}
๋ก๊ทธ์ธ ์คํจ ์ ์๋์ฒ๋ผ ์์ด๋๊ฐ ๋จ์ ์๊ณ , ๋ก๊ทธ์ธ ์ ๋ณด ํ์ธํ๋ผ๋ ์ค๋ฅ ๋ฉ์์ง ๋ธ
header ๋ถ๋ถ ๋ฒํผ ์ธ์ฆ ์ธ๊ฐ ์ปจํธ๋กค
header.html์ ์ํ๋ฆฌํฐ ๋ฌธ๋ฒ ์ฌ์ฉ์ ์ํด ์๋ ์ฝ๋ ์ถ๊ฐ
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
000๋ ๋ฐ๊ฐ์ต๋๋ค. ๋ฌธ๊ตฌ ๋ฐ MYPAGE, LOGOUT ๋ฒํผ ์ธ์ฆ ๋์ ๋๋ง ๋ณด์ด๊ฒ ๋ณ๊ฒฝ.
<span sec:authorize="isAuthenticated()">
[[${#authentication.name}]]๋ ๋ฐ๊ฐ์ต๋๋ค. <!-- ๋ก๊ทธ์ธํ ์ฌ๋ id -->
<a th:href="@{/cart/cartList}">MY PAGE</a>
<a th:href="@{/member/logout}">LOGOUT</a>
</span>
LOGIN, JOIN ๋ฒํผ ์ธ์ฆ ์ ๋์ ๋ ๋ณด์ด๊ฒ
<span sec:authorize="isAnonymous()">
<span data-bs-toggle="modal" data-bs-target="#loginModal" style="cursor: pointer;">LOGIN</span>
<span data-bs-toggle="modal" data-bs-target="#joinModal" style="cursor: pointer;">JOIN</span>
</span>
Security ๋ก๊ทธ์์ ๊ธฐ๋ฅ
์ฐ์ MemberController ๋ก๊ทธ์์ ์ฝ๋ ์ญ์
SecurityConfig์ ๋ก๊ทธ์์ ์ ์ค์ ์ฝ๋ ์ถ๊ฐ
@Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception{
security.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/"
, "/item/itemList"
, "/item/itemDetail"
, "/member/isDuplicateMemId"
, "/member/join"
, "/member/loginForm").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/member/loginForm")
.usernameParameter("memId")
.passwordParameter("memPw")
.loginProcessingUrl("/member/login")
.successHandler(getSucessHandler())
.failureHandler(getFailureHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/member/logout")
.invalidateHttpSession(true)
.logoutSuccessUrl("/"); //
return security.build();
}
๊ฐ๊ฐcontroller ์์
session ์ ๋ณด ์ฌ์ฉํ๋ ๊ฒ๋ค ์์ ํด์ผ ํจ.
admincontroller ์นดํ ๊ณ ๋ฆฌ ๊ด๋ฆฌ ํ์ด์ง ์ธ์ ๋ฉ์๋ ์ง์ฐ๊ธฐ
id ๋ฝ๋ ์ํ๋ฆฌํฐ ์ฝ๋ ์ฌ์ฉ.
//์นดํ
๊ณ ๋ฆฌ ๊ด๋ฆฌ ํ์ด์ง
@GetMapping("/cateManage")
public String cateManage(Model model, AdminSubMenuVO adminSubMenuVO) {
//๊ด๋ฆฌ์๋ก ๋ก๊ทธ์ธ ํ ๊ฒฝ์ฐ ์ฒซ ํ์ด์ง ์นดํ
๊ณ ๋ฆฌ ํ์ด์ง๋ก ์ด๋.(๊ฐ์ ธ๊ฐ๋ ๋ฐ์ดํฐ X)
//์ธ๋ฑ์ค ์ปจํธ๋กค๋ฌ์์ ์ฌ ๋๋ ๋ฐ์ดํฐ ์๊ธฐ ๋๋ฌธ์ ์๋ธ ๋ฉ๋ด ์ฝ๋ ๋ฐ์ดํฐ ๋ฃ๊ธฐ.
adminSubMenuVO.setMenuInfo(ConstVariable.DEFAULT_MENU_CODE, ConstVariable.DEFAULT_SUB_MENU_CODE_1);
//์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก ์กฐํ
model.addAttribute("cateList", adminService.getCateListForAdmin());
//model.addAttribute("menuCode", "MENU_001"); //์ธํฐ์
ํฐ์์ ๋ฐ์ดํฐ ๋ฐ์ ๋ ๋๊ธฐ๋ ๋ฐ์ดํฐ ์ด๋ฆ์ด key๊ฐ ๋จ
//model.addAttribute("MemberVO", new MemberVO());
return "content/admin/cate_manage";
}
buycontroller ๋ฐ๋ก ๊ตฌ๋งค ๊ธฐ๋ฅ
memId ๊ตฌํ๋ ์ฝ๋ ์์
๋งค๊ฐ๋ณ์ authentication ์ถ๊ฐ ํ ์ํ๋ฆฌํฐ ๋ฌธ๋ฒ ์ฌ์ฉ
๋ฐ๋ก๊ตฌ๋งค
์ ํ๊ตฌ๋งค
๊ตฌ๋งค๋ด์ญ ํ์ด์ง
cartController
์ฅ๋ฐ๊ตฌ๋ ์ํ ๋ฑ๋ก
์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง
config ์ธ์ฆ ์ธ๊ฐ ์์
์ผ๋ฐ ํ์์ด url ์ฃผ์๋ฅผ ์๊ณ ์์ผ๋ฉด admin ๊ด๋ จ ํ์ด์ง ์ด๋ ๊ฐ๋ฅ
์ค์ ํด์ค์ผ ํจ.
์๋ ์ฝ๋ ์ถ๊ฐ
.requestMatchers("/admin/**").hasRole("ADMIN") //ํด๋น ๊ถํ๋ง ๋ค์ด์ฌ ์ ์๊ฒ ์ธ๊ฐ ์ค์ .
user๋ก ๋ก๊ทธ์ธ ํด๋ ์ด์ ๊ถํ ์์ด์ ์ค๋ฅ์ฒ๋ผ ํ์ด์ง ๋ธ
์ธ๊ฐ๊ฐ ์ ๋์ ํ๊ฒผ์ ๊ฒฝ์ฐ
๊ถํ ์๋ค๊ณ ์๋ ค์ฃผ๋ ํ์ด์ง ์ค์ ๊ฐ๋ฅ.
indexController์ ์ปจํธ๋กค๋ฌ ์ฝ๋ ์ถ๊ฐ
//๋ฏธ์ธ๊ฐ ์ ์ด๋ํ ํ์ด์ง
@GetMapping("/accessDeny")
public String accessDeny() {
return "content/access_deny";
}
content์ html ์์ฑ
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>์ ๊ทผ์ด ๊ฑฐ๋ถ๋์์ต๋๋ค.</h3>
<h5><a th:href="@{/}">ํ์ผ๋ก ๊ฐ๊ธฐ</a></h5>
</body>
</html>
์ํ๋ฆฌํฐ ์ ์ฉ ํ ๊ธฐ๋ฅ ์ค๋ฅ ์ก๊ธฐ
- ๊ด๋ฆฌ์ ๋ก๊ทธ์ธ ์ ๊ด๋ฆฌ์ ์๋จ ๋ฉ๋ด ์ค๋ฅ
header.html ์์
๋ก๊ทธ์ธ ์ฌ๋ถ์ ๋ฐ๋ผ ๋ฉ๋ด ๋์ค๋ ์ฝ๋ ์์ ํ์ session ์ ์ฐ๊ธฐ ๋๋ฌธ์ ์กฐ๊ฑด๋ฌธ ์ง์์ฃผ๊ณ
์ํ๋ฆฌํฐ ๋ฌธ๋ฒ ์ฌ์ฉํ์ฌ ์๋์ฒ๋ผ ์ฝ๋ ์์
<ul class="navbar-nav">
<!-- ๋ก๊ทธ์ธ X -->
<th:block sec:authorize="isAnonymous()">
<th:block th:each="category : ${categoryList}">
<li class="nav-item">
<a class="nav-link">[[${category.cateName}]]</a>
</li>
</th:block>
</th:block>
<th:block sec:authorize="isAuthenticated()">
<!-- ๋ก๊ทธ์ธ O, ์ผ๋ฐํ์ -->
<th:block sec:authorize="hasRole('ROLE_USER')">
<th:block th:each="category : ${categoryList}">
<li class="nav-item">
<a class="nav-link">[[${category.cateName}]]</a>
</li>
</th:block>
</th:block>
<!-- ๋ก๊ทธ์ธ O, ๊ด๋ฆฌ์ -->
<th:block sec:authorize="hasRole('ROLE_ADMIN')">
<th:block th:each="adminMenu : ${adminMenuList}">
<li class="nav-item">
<a class="nav-link" th:href="@{'/admin' + ${adminMenu.menuUrl}(menuCode=${adminMenu.menuCode})}">[[${adminMenu.menuName}]]</a>
</li>
</th:block>
</th:block>
</th:block>
</ul>
- ์ํ ์์ธ ํ์ด์ง ์ฅ๋ฐ๊ตฌ๋ ํด๋ฆญ ์ ๋ก๊ทธ์ธ ์ธ์ ๋ชป ํ๋ ์ค๋ฅ
item_detail.html
์ฅ๋ฐ๊ตฌ๋ ๋ฒํผ ํด๋ฆญ ์ ์ธ์ ์ผ๋ก memId ๊ฐ์ ธ๊ฐ๊ฒ ๋์ด ์์ > ์์
<input type="button" class="btn btn-outline-success" value="์ฅ ๋ฐ ๊ตฌ ๋"
th:onclick="regCart([[${#authentication.name}]], [[${item.itemCode}]]);">
security๋ ๋ก๊ทธ์ธ ํ์ง ์์ ์ํ์๋ ์ต๋ช ์ผ๋ก ์์ ์์ด๋๋ฅผ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ์ ์!
์ฐ๊ฒฐ๋ item_detail.js์ regCart ํจ์ ์์
์ต๋ช ์ผ๋ก ์์ ์์ด๋์ธ anonymousUser ๊ฒฝ์ฐ์ ๋ก๊ทธ์ธ ํ๊ฒ ์ค์ .