๋ฉ”์†Œ๋“œ ๋ณด์•ˆ์„ ์ž˜ ํ™œ์šฉํ•˜๊ณ  ์žˆ์—ˆ์œผ๋‚˜ ๋ฉ”์†Œ๋“œ ๋ณด์•ˆ์— ์˜ํ•ด ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋˜์ง€ ์•Š๊ฒŒ ๋œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ์‹œ์Šคํ…œ์€ ์Šคํ”„๋ง ๋ถ€ํŠธ ๊ธฐ๋ฐ˜์˜ ํ”„๋กœ์ ํŠธ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Œ์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•œ๋‹ค. ์‹œ์Šคํ…œ์„ ์ด์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ผ์ด์–ธํŠธ ํฌ๋ ˆ๋ด์…œ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  OAuth ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜์—ฌ OpenAPI๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ OpenAPI ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ๋ฉ”์†Œ๋“œ ๋ณด์•ˆ๊ณผ ํ•จ๊ป˜ PermissionEvaluator๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ปค์Šคํ…€ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

  • Spring Boot 2.3.12.RELEASE
  • Spring Security OAuth
  • EnableGlobalMethodSecurity with PermissionEvaluator
  • UserDetailsService + ClientDetailsService
JdbcConfiguration
@EnableTransactionManagement @Configuration public class JdbcConfiguration implements TransactionManagementConfigurer { @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return null; } }
MethodSecurityConfiguration
@AllArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true) @Configuration public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { private final ApplicationContext applicationContext; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { OAuth2MethodSecurityExpressionHandler expressionHandler = new OAuth2MethodSecurityExpressionHandler(); expressionHandler.setApplicationContext(applicationContext); return expressionHandler; } }
AuthPermissionEvaluator
@AllArgsConstructor @Component("auth") public class AuthPermissionEvaluator implements PermissionEvaluator { private final UserService userService; public boolean isAll() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); ClientDetails clientDetails = userService.loadClientByClientId(authentication.getName()); if (clientDetails instanceof UserClient) { UserClient userClient = (UserClient) clientDetails; return userClient.getScope().contains("all"); } return false; } @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { return true; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return true; } }
application.yml
spring: aop: auto: true proxy-target-class: true datasource: hikari: auto-commit: false

๋ฉ”์†Œ๋“œ ๋ณด์•ˆ๊ณผ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์ˆœ์„œ ๋ฌธ์ œ

ํŠธ๋žœ์žญ์…˜์ด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ์— ๋Œ€ํ•ด์„œ ํ™•์ธํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด TransactionAspectSupport๊ฐ€ ์ŠคํƒํŠธ๋ ˆ์ด์Šค์— ํฌํ•จ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜๊ฐ€ ์žˆ๋‹ค. @EnableGlobalMethodSecurity(prePostEnabled = true) ์™€ GlobalMethodSecurityConfiguration ๊ทธ๋ฆฌ๊ณ  @EnableTransactionManagement() ์ธ ์ƒํƒœ์—์„œ ์ปค์Šคํ…€ PermissionEvaluator๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๊ฒŒ ๋˜๊ณ  @Transactional์„ ๋ช…์‹œํ•˜๋”๋ผ๋„ ํŠธ๋žœ์žญ์…˜์ด ์ƒ์„ฑ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ๋‚ด์žฌํ•˜๊ฒŒ ๋œ๋‹ค.

TransactionAspectSupport ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
com.example.demo.user.UserRepository.update(UserRepository.java:42) com.example.demo.user.UserRepository$$FastClassBySpringCGLIB$$c53b685e.invoke(<generated>) org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:792) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762) org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762) org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:162) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762) org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:707) com.example.demo.user.UserRepository$$EnhancerBySpringCGLIB$$7a11f77b.update(<generated>) com.example.demo.user.UserService.update(UserService.java:70) com.example.demo.user.UserService.update(UserService.java:66) com.example.demo.user.UserApi.updateUser(UserApi.java:28) com.example.demo.user.UserApi$$FastClassBySpringCGLIB$$df90bb86.invoke(<generated>)
TransactionAspectSupport ๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ
com.example.demo.user.UserRepository.update(UserRepository.java:42) com.example.demo.user.UserRepository$$FastClassBySpringCGLIB$$c53b685e.invoke(<generated>) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762) org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762) org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:707) com.example.demo.user.UserRepository$$EnhancerBySpringCGLIB$$964218d2.update(<generated>) com.example.demo.user.UserService.update(UserService.java:70) com.example.demo.user.UserService.update(UserService.java:66) com.example.demo.user.UserApi.updateUser(UserApi.java:28) com.example.demo.user.UserApi$$FastClassBySpringCGLIB$$df90bb86.invoke(<generated>)

Migrating from @EnableGlobalMethodSecurity

์ด์™€ ๊ฐ™์ด ํŠธ๋žœ์žญ์…˜ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๋กœ ์ธํ•˜์—ฌ @EnableGlobalMethodSecurity ๋ฅผ @EnableMethodSecurity๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด @EnableTransactionManagement ์˜ ์ˆœ์„œ๋ฅผ 0์œผ๋กœ ์ง€์ •ํ•˜๋ฉด ํ•ด๊ฒฐ๋œ๋‹ค.

@EnableTransactionManagement(order = 0)
@Configuration
public class JdbcConfiguration implements TransactionManagementConfigurer {

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return null;
    }
}

@EnableMethodSecurity
@Configuration
public class MethodSecurityConfiguration {
}
com.example.demo.user.UserRepository.update(UserRepository.java:43)
com.example.demo.user.UserRepository$$FastClassBySpringCGLIB$$c53b685e.invoke(<generated>)
org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:792)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:707)
com.example.demo.user.UserRepository$$EnhancerBySpringCGLIB$$38621c9f.update(<generated>)
com.example.demo.user.UserService.update(UserService.java:70)
com.example.demo.user.UserService.update(UserService.java:66)
com.example.demo.user.UserApi.updateUser(UserApi.java:28)
com.example.demo.user.UserApi$$FastClassBySpringCGLIB$$df90bb86.invoke(<generated>)
org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:792)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)
org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:162)

UserApi์˜ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜์— ๋ถ€์—ฌ๋œ @PreAuthorize ๋กœ ์ธํ•˜์—ฌ AuthorizationManagerBeforeMethodInterceptor๊ฐ€ ์ŠคํƒํŠธ๋ ˆ์ด์Šค์— ํฌํ•จ๋˜๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ณ  TransactionInterceptor ์— ์˜ํ•ด @Transactional ์ด ์ ์šฉ๋œ ๋ฆฌํŒŒ์ง€ํ† ๋ฆฌ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ด์ „์— ํŠธ๋žœ์žญ์…˜ ์ƒ์„ฑ์„ ์‹œ๋„ํ•˜๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์žฌํ˜„ํ•œ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ์™€ ๋‹ค๋ฅด๊ฒŒ ์‹ค์ œ๋กœ๋Š” @EnableGlobalMethodSecurity๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  PermissionEvaluator ๊ตฌํ˜„์ฒด ๋‚ด์—์„œ UserService๋ฅผ ๋‚˜์ค‘์— ์ฐธ์กฐํ•˜๋„๋ก @Lazy ๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ์ด ๋˜์—ˆ๋Š”๋ฐ ๊ทธ ์ด์œ ์— ๋Œ€ํ•ด์„œ๋Š” ์กฐ๊ธˆ ๋” ์ฐพ์•„๋ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค.

์ฐธ๊ณ ๋กœ, hikari.auto-commit ์˜ต์…˜์ด ์ ์šฉ๋˜์–ด ์žˆ์—ˆ๋‹ค๋ฉด @Transactional ์ด ๋™์ž‘ํ•˜์ง€ ์•Š์•„๋„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋ฐ˜์˜๋˜์—ˆ์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

์ฐธ๊ณ