
๐ฑ Spring Security: ๋์ URL ์ ๊ทผ ๊ถํ ์ ์ด ๊ตฌํ
๊ฐ์
Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ ๋ฐฉ์์ URL ์ ๊ทผ ์ ์ด๋ฅผ ์ ๊ณตํ๋ค. ํ์ง๋ง ์ค๋ฌด์์๋ ๊ถํ ์ ์ฑ ์ด ์์ฃผ ๋ณ๊ฒฝ๋๊ฑฐ๋, URL ์ ๊ทผ ๊ถํ์ ๊ด๋ฆฌ ํ๋ฉด์์ ์ค์๊ฐ์ผ๋ก ์ ์ดํด์ผ ํ๋ ์๊ตฌ๊ฐ ๋ง๋ค.
"GUEST ์ฌ์ฉ์์ ๋ํ ์ ๊ทผ ๊ฐ๋ฅ URL"์ DB์์ ๋์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ , ์ด๋ฅผ Spring Security์ ์ค์๊ฐ ๋ฐ์ ๋ ์ ์๋๋ก ๊ตฌํํ ๋ด์ฉ์ ์ ๋ฆฌํ ์์ ์ด๋ค.
ํต์ฌ ๋ชฉํ
- GUEST(๋น๋ก๊ทธ์ธ ์ฌ์ฉ์)์ ์ ๊ทผ URL์ DB์์ ๊ด๋ฆฌ
- Spring Security์ ์ธ์ฆ ํํฐ ์ด์ ์ ๋์ ์ผ๋ก URL ์ ๊ทผ ์ฌ๋ถ ํ๋จ
- ๊ถํ ์ ๋ณด๋ ์บ์ฑํ์ฌ ์ฑ๋ฅ ์ต์ ํ
- ์ฌ๊ธฐ๋ ์์ด ์ ์ฑ ๋ณ๊ฒฝ ๊ฐ๋ฅ
๊ธฐ์ ๊ตฌ์ฑ
- Spring boot 3.x
- Spring Security 5.5 ์ด์
- Java 17
- @Scheduled (์บ์ ๊ฐฑ์ )
์์คํ ๊ตฌ์กฐ ์์ฝ
DB ํ ์ด๋ธ ๊ตฌ์ฑ
- roles: ๊ถํ ์ด๋ฆ, ์ญ์ ์ฌ๋ถ ๋ฑ
- menus: URL, ๋ฉ๋ด ์ด๋ฆ, ํ์ฑ์ฌ๋ถ ๋ฑ
- menu_role: ๋ฉ๋ด์ ๊ถํ์ ๋งคํ
์ ์ฒด ํ๋ฆ
1. ์ฌ์ฉ์ ์์ฒญ
2. Spring Security์์ AuthorizationManager๋ก ์์
3. AuthorizationManager์์ ์บ์๋ guest URL ๋ชฉ๋ก๊ณผ ๋น๊ต
4. ํ์ฉ ์ฌ๋ถ ๊ฒฐ์
๊ตฌํ ์์ธ
1. Spring boot ํ๋ก์ ํธ ์์ฑ
[ VScode ] vscode์์ java spring boot ํ๋ก์ ํธ ์์ฑ
๐ VScode์์ JAVA spring boot project ์์ฑ JAVA ํ์ฅํฉ(Extensions) ์ค์น- Extension Pack for Java- Spring Boot Extension Pack https://euntry.tistory.com/47 [ VScode ] JAVA ๊ฐ๋ฐ์ ์ํ Extensions ์ถ์ฒ (OS Window)๐ VScode JAVA ํ๋ฌ
euntry.tistory.com
2. build.gradle (Spring boot 3.x ๊ธฐ์ค)
dependencies {
// Spring Boot Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// Spring Security์์ AuthorizationManager ์ฌ์ฉ ๊ฐ๋ฅ
// (Security 5.5 ์ด์๋ถํฐ ์ฌ์ฉ ๊ฐ๋ฅ)
// ์ค์ผ์ค๋ง (@Scheduled)
implementation 'org.springframework.boot:spring-boot-starter'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
3. ์ค์ผ์ค๋ง ํ์ฑํ
@EnableScheduling ์ด๋ ธํ ์ด์ ์ถ๊ฐ
@SpringBootApplication
@EnableScheduling
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
"@EnableScheduling"์ Spring์๊ฒ ์ค์ผ์ค๋ง ๊ธฐ๋ฅ์ ํ์ฑํํ๋ผ๊ณ ์ง์ํ๋ ์ด๋ ธํ ์ด์ ์ด๋ค.
Spring boot ํ๋ก์ ํธ์์ ์ด ์ค๋ ธํ ์ด์ ์ ๋ช ์์ ์ผ๋ก ์ถ๊ฐํด์ผ ํ๋ฉฐ, ์๋ ํ์ฑํ๋์ง ์๋๋ค.
4. GUEST ์ ๊ทผ ๊ฐ๋ฅ URL ์บ์ ์ค์
"@Cacheable"์ ์ฌ์ฉํด GUEST๊ฐ ์ ๊ทผ ๊ฐ๋ฅํ URL์ ์บ์ฑํ๋ค. DB๋ฅผ ๋งค๋ฒ ์กฐํํ์ง ์์ ์ฑ๋ฅ์ ์ผ๋ก ์ ๋ฆฌ
@Cacheable(value = CACHE_KEY.SECURITY, key = "'guestUrls'", unless = "#result == null or #result.isEmpty()")
public List<String> getGuestAccessibleUrls() {
log.info(">>> DB์์ GUEST_ROLE ์ ๊ทผ ๊ฐ๋ฅ URL ์กฐํ");
return menuRoleRepository.findByRole_RoleSeqAndMenu_ActiveYnAndDelYn(ROLE_KEY.GUEST, ActiveYn.Y, DelYn.N)
.stream()
.map(menuRole -> converToWildcardpattern(menuRole.getMenu().getMenuUrl()))
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
@CacheEvict(value = CACHE_KEY.SECURITY, key = "'guestUrls'")
public void refreshGuestAccessibleUrls() {
// ๋ณ๋ ๋ก์ง ๋ถํ์, ์บ์๋ง ์ ๊ฑฐ๋จ → ๋ค์ ํธ์ถ ์ ์๋ ์ฌ๋ก๋ฉ
}
private String converToWildcardpattern(String url) {
// RESTful ๊ฒฝ๋ก ๋ณ์({...})๋ฅผ *๋ก ๋ณํํด URL ํจํด ์ ๊ทํ
return StringUtils.hasText(url) ? url.replaceAll("\\{[^}]+\\}", "*") : url;
}
5. ์บ์ ๊ฐฑ์ ์ค์ผ์ค๋ฌ
@Component
@RequiredArgsConstructor
@Slf4j
public class PermitAllCacheRefresher {
private final MenuRoleService menuRoleService;
@Scheduled(fixedRate = 600000) // 10๋ถ ๊ฐ๊ฒฉ ์คํ
public void refreshPermitAllUrls() {
//log.info("GUEST ROLE ์ ๊ทผ ๊ฐ๋ฅ URL ์บ์ ๊ฐฑ์ ");
menuRoleService.refreshGuestAccessibleUrls();
}
}
6. GuestUrlAuthorizationManager ๊ตฌํ
@Component
@RequiredArgsConstructor
public class GuestUrlAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final MenuRoleService menuRoleService;
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
HttpServletRequest request = context.getRequest();
String requestURI = request.getRequestURI();
List<String> guestUrls = menuRoleService.getGuestAccessibleUrls();
boolean isGuestUrl = guestUrls.stream()
.filter(StringUtils::hasText)
.anyMatch(pattern -> requestURI.matches(convertToRegex(pattern)));
Authentication auth = authentication.get();
// ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๋ง guest URL ์ ๊ทผ ํ์ฉ
boolean isGuest = !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken;
if (isGuest && isGuestUrl) {
return new AuthorizationDecision(true);
}
// ์ธ์ฆ๋ ์ฌ์ฉ์๋ ํต๊ณผ (์ธ์ฆ ์ฌ๋ถ๋ง ํ์ธ)
if (auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
return new AuthorizationDecision(true); // ๋ก๊ทธ์ธ ์ฌ์ฉ์ → ํต๊ณผ
}
return new AuthorizationDecision(false); // ๋๋จธ์ง๋ ์ฐจ๋จ
}
private String convertToRegex(String pattern) {
// Spring URL ํจํด์ ์ ๊ท์์ผ๋ก ๋ณํ
String regex = pattern
.replace(".", "\\.")
.replace("**", ".*")
.replace("*", "[^/]*");
return "^" + regex + "$";
}
}
7. Spring Security ์ค์
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, GuestUrlAuthorizationManager guestAuthManager) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/images/**", "/js/**", "/error/**").permitAll()
.anyRequest().access(guestAuthManager)
)
.formLogin(...)
.logout(...)
.sessionManagement(...);
return http.build();
}
์ ๋ฆฌ ๋ฐ ํจ๊ณผ
- DB์์ ์ง์ URL ์ ๊ทผ ์ ์ฑ ์ ๊ด๋ฆฌ → ์ด์์๊ฐ ์ง์ ์ ์ด ๊ฐ๋ฅ
- ์๋ฒ ์ฌ๊ธฐ๋ ์์ด ์ค์๊ฐ ๋ฐ์ ๊ฐ๋ฅ (@CacheEvict + @Scheduled) → ์ด์ฉ ํจ์จ ↑
- ๋ณด์ ์ ์ฑ ์ด ์ฝ๋์ ๋ถ๋ฆฌ๋์ด ์ ์ง๋ณด์ ๊ฐํธ
- AuthorizationManager๋ฅผ ํตํด ์ญํ ๊ธฐ๋ฐ ์ ์ด ํ์ฅ ๊ฐ๋ฅ
'Back-end > JAVA & Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [ Spring Boot + Tomcat ] MultipartException ์ค๋ฅ (0) | 2026.04.25 |
|---|---|
| [ Spring Boot ] Jasypt: Spring Boot์์ ๊ฐ๋จํ๊ณ ์์ ํ ์ํธํ (4) | 2025.01.16 |
| [ Spring Boot ] Spring Boot์์ Datasource ์ํธํํ๊ธฐ (1) | 2025.01.16 |
| [ Spring Boot ] Banner ๋ณ๊ฒฝ (1) | 2024.12.24 |
| [ Spring ] application.properties ๊ฐ๋ฐ์ ์ ์ ๊ฒฝ๊ณ ํด๊ฒฐ (1) | 2024.11.25 |