๋ฉ์๋ ๋ณด์์ ์ํ ํธ๋์ญ์ ์ด์
๋ฉ์๋ ๋ณด์์ ์ ํ์ฉํ๊ณ ์์์ผ๋ ๋ฉ์๋ ๋ณด์์ ์ํด ํธ๋์ญ์ ์ด ์ ์ฉ๋์ง ์๊ฒ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์์คํ ์ ์คํ๋ง ๋ถํธ ๊ธฐ๋ฐ์ ํ๋ก์ ํธ๋ก ์๋์ ๊ฐ์ด ๊ตฌ์ฑ๋์ด ์์์ ๊ณต์ ํ๊ณ ์ ํ๋ค. ์์คํ ์ ์ด์ฉํ๋ ์ฌ์ฉ์๊ฐ ํด๋ผ์ด์ธํธ ํฌ๋ ๋ด์ ์ ๋ฐ๊ธํ๊ณ 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.ymlspring: 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 ์ด ๋์ํ์ง ์์๋ ์ธ์งํ์ง ๋ชปํ๊ณ ๋ฐ์๋์์ ๊ฒ ๊ฐ๋ค์.