텍스트이 글을 공부를 위해 스파르타코딩에서 가져온 본 글입니다.
스프링 시큐리티 설정을 이용해 일반 사용자가 '관리자용 상품조회 API' 에 접속 시도 시 접속 불가 페이지가 뜨도록 구현프론트
templates > forbidden.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>접근 불가</title>
<style>
html,body{
margin:0;
padding:0;
display:flex;
justify-content:center;
align-items:center;
background-color:salmon;
font-family:"Quicksand", sans-serif;
}
#container_anim{
position:relative;
width:100%;
height:70%;
}
#key{
position:absolute;
top:77%;
left:-33%;
}
#text{
font-size:4rem;
position:absolute;
top:55%;
width:100%;
text-align:center;
}
#credit{
position:absolute;
bottom:0;
width:100%;
text-align:center;
bottom:
}
a{
color: rgb(115,102,102);
}
</style>
<script>
var lock = document.querySelector('#lock');
var key = document.querySelector('#key');
function keyAnimate(){
dynamics.animate(key, {
translateX: 33
}, {
type:dynamics.easeInOut,
duration:500,
complete:lockAnimate
})
}
function lockAnimate(){
dynamics.animate(lock, {
rotateZ:-5,
scale:0.9
}, {
type:dynamics.bounce,
duration:3000,
complete:keyAnimate
})
}
setInterval(keyAnimate, 3000);
</script>
</head>
<body>
<div id="container_anim">
<div id="lock" class="key-container">
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="317.286 -217 248 354" width="248" height="354"><g><path d="M 354.586 -43 L 549.986 -43 C 558.43 -43 565.286 -36.144 565.286 -27.7 L 565.286 121.7 C 565.286 130.144 558.43 137 549.986 137 L 354.586 137 C 346.141 137 339.286 130.144 339.286 121.7 L 339.286 -27.7 C 339.286 -36.144 346.141 -43 354.586 -43 Z" style="stroke:none;fill:#2D5391;stroke-miterlimit:10;"/><g transform="matrix(-1,0,0,-1,543.786,70)"><text transform="matrix(1,0,0,1,0,234)" style="font-family:'Quicksand';font-weight:700;font-size:234px;font-style:normal;fill:#4a4444;stroke:none;">U</text></g><g transform="matrix(-1,0,0,-1,530.786,65)"><text transform="matrix(1,0,0,1,0,234)" style="font-family:'Quicksand';font-weight:700;font-size:234px;font-style:normal;fill:#8e8383;stroke:none;">U</text></g><path d="M 343.586 -52 L 538.986 -52 C 547.43 -52 554.286 -45.144 554.286 -36.7 L 554.286 112.7 C 554.286 121.144 547.43 128 538.986 128 L 343.586 128 C 335.141 128 328.286 121.144 328.286 112.7 L 328.286 -36.7 C 328.286 -45.144 335.141 -52 343.586 -52 Z" style="stroke:none;fill:#4A86E8;stroke-miterlimit:10;"/><g><circle vector-effect="non-scaling-stroke" cx="441.28571428571433" cy="63.46153846153848" r="10.461538461538453" fill="rgb(0,0,0)"/><rect x="436.055" y="66.538" width="10.462" height="34.462" transform="matrix(1,0,0,1,0,0)" fill="rgb(0,0,0)"/></g></g></svg>
</div>
<div id="key">
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="232.612 288.821 169.348 109.179" width="169.348" height="109.179"><g><path d=" M 382.96 349.821 L 368.96 349.821 L 368.96 314.821 L 382.96 307.821 L 382.96 349.821 Z " fill="rgb(55,49,49)"/><path d=" M 292.134 354.827 L 379.96 315.39 L 379.96 305.547 L 292.134 343.094 L 292.134 354.827 Z " fill="rgb(55,49,49)"/><path d=" M 280.96 340.109 L 401.96 288.821 L 401.96 340.109 L 382.96 349.972 L 382.96 308.547 L 265.96 360.821 L 259.96 349.972 L 280.96 340.109 Z " fill="rgb(115,102,102)"/><path d=" M 401.96 288.821 L 382.96 288.821 L 280.96 332.821 L 292.134 340.109 L 401.96 288.821 Z " fill="rgb(115,102,102)"/><g><path d=" M 232.755 354.125 C 230.958 328.501 246.297 306.519 266.988 305.068 C 287.679 303.617 305.937 323.243 307.734 348.867 C 309.531 374.492 294.191 396.473 273.5 397.924 C 252.809 399.375 234.552 379.75 232.755 354.125 Z " fill="rgb(55,49,49)"/><path d=" M 239.241 352.316 C 237.564 328.406 252.144 307.876 271.779 306.499 C 291.414 305.122 308.716 323.416 310.393 347.326 C 312.07 371.236 297.49 391.766 277.855 393.143 C 258.22 394.52 240.917 376.226 239.241 352.316 Z " fill="rgb(115,102,102)"/><path d=" M 260.038 353.084 C 259.196 348.171 261.788 343.621 265.822 342.929 C 269.856 342.238 273.816 345.665 274.658 350.578 C 275.5 355.49 272.909 360.041 268.874 360.732 C 264.84 361.424 260.88 357.997 260.038 353.084 Z " fill="salmon"/></g></g></svg>
</div>
</div>
<p id="text">403 FORBIDDEN</p>
<p id="credit">사용자 접근 불가 페이지입니다.</a></p>
</body>
</html>
UserController.java
package com.sparta.springsecurity.controller;
import com.sparta.springsecurity.dto.SignupRequestDto;
import com.sparta.springsecurity.entity.User;
import com.sparta.springsecurity.entity.UserRoleEnum;
import com.sparta.springsecurity.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
// ADMIN_TOKEN
private static final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";
@GetMapping("/signup")
public ModelAndView signupPage() {
return new ModelAndView("signup");
}
@GetMapping("/login-page")
public ModelAndView loginPage() {
return new ModelAndView("login");
}
@PostMapping("/signup")
public String signup(SignupRequestDto signupRequestDto) {
String username = signupRequestDto.getUsername();
String password = passwordEncoder.encode(signupRequestDto.getPassword());
// 회원 중복 확인
Optional<User> found = userRepository.findByUsername(username);
if (found.isPresent()) {
throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
}
// 사용자 ROLE 확인
UserRoleEnum role = UserRoleEnum.USER;
if (signupRequestDto.isAdmin()) {
if (!signupRequestDto.getAdminToken().equals(ADMIN_TOKEN)) {
throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
}
role = UserRoleEnum.ADMIN;
}
User user = new User(username, password, role);
userRepository.save(user);
return "redirect:/api/user/login-page";
}
@PostMapping("/login")
public String login(@AuthenticationPrincipal UserDetails userDetails) {
System.out.println("*********************************************************");
System.out.println("UserController.login");
System.out.println("userDetails.getUsername() = " + userDetails.getUsername());
System.out.println("*********************************************************");
return "redirect:/api/user/login-page";
}
@PostMapping("/forbidden")
public ModelAndView forbidden() {
return new ModelAndView("forbidden");
}
}
"@EnableGlobalMethodSecurity(securedEnabled = true)" 추가
"접근 불가" 페이지 URL 설정
→ /api/user/forbidden
WebSecurityConfig
package com.sparta.springsecurity.config;
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.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 어노테이션 활성화
public class WebSecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// h2-console 사용 및 resources 접근 허용 설정
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf().disable();
http.authorizeRequests().antMatchers("/api/user/**").permitAll()
.anyRequest().authenticated();
// Custom 로그인 페이지 사용
http.formLogin().loginPage("/api/user/login-page").permitAll();
// Custom Filter 등록하기
http.addFilterBefore(new CustomSecurityFilter(userDetailsService, passwordEncoder()), UsernamePasswordAuthenticationFilter.class);
// 접근 제한 페이지 이동 설정
http.exceptionHandling().accessDeniedPage("/api/user/forbidden");
return http.build();
}
}
# 3. Test API 에 @Secured 어노테이션으로 권한 설정
TestController.java
package com.sparta.springsecurity.controller;
import com.sparta.springsecurity.entity.UserRoleEnum;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/api")
public class TestController {
@Secured(value = UserRoleEnum.Authority.ADMIN)
@PostMapping("/test-secured")
public String securedTest(@AuthenticationPrincipal UserDetails userDetails) {
System.out.println("*********************************************************");
System.out.println("UserController.securedTest");
System.out.println("userDetails.getUsername() = " + userDetails.getUsername());
System.out.println("*********************************************************");
return "redirect:/api/user/login-page";
}
}
![](https://velog.velcdn.com/images/wonizizi99/post/1910be1f-91df-4b14-bd9e-00f28092861b/image.png)