๋ฉ์๋ ๋ณด์์ ์ํ ํธ๋์ญ์ ์ด์
๋ฉ์๋ ๋ณด์์ ์ ํ์ฉํ๊ณ ์์์ผ๋ ๋ฉ์๋ ๋ณด์์ ์ํด ํธ๋์ญ์ ์ด ์ ์ฉ๋์ง ์๊ฒ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์์คํ ์ ์คํ๋ง ๋ถํธ ๊ธฐ๋ฐ์ ํ๋ก์ ํธ๋ก ์๋์ ๊ฐ์ด ๊ตฌ์ฑ๋์ด ์์์ ๊ณต์ ํ๊ณ ์ ํ๋ค. ์์คํ ์ ์ด์ฉํ๋ ์ฌ์ฉ์๊ฐ ํด๋ผ์ด์ธํธ ํฌ๋ ๋ด์ ์ ๋ฐ๊ธํ๊ณ OAuth ํ ํฐ์ ๋ฐ๊ธํ์ฌ OpenAPI๋ฅผ ์ด์ฉํ ์ ์์ผ๋ฉฐ OpenAPI ์ ๋ํ ๊ถํ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ฉ์๋ ๋ณด์๊ณผ ํจ๊ป PermissionEvaluator๋ฅผ ๊ตฌํํ์ฌ ์ปค์คํ ํํ์์ ์ฌ์ฉํ๊ณ ์์๋ค.
- Spring Boot 2.3.12.RELEASE
- Spring Security OAuth
- EnableGlobalMethodSecurity with PermissionEvaluator
- UserDetailsService + ClientDetailsService
@EnableTransactionManagement
@Configuration
public class JdbcConfiguration implements TransactionManagementConfigurer {
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return null;
}
}
@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;
}
}
@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;
}
}
spring:
aop:
auto: true
proxy-target-class: true
datasource:
hikari:
auto-commit: false
๋ฉ์๋ ๋ณด์๊ณผ ํธ๋์ญ์ ์ฒ๋ฆฌ ์์ ๋ฌธ์
ํธ๋์ญ์ ์ด ๋์ํ์ง ์๋ ๋ฌธ์ ์ ๋ํด์ ํ์ธํด๋ณด๋ฉด ์๋์ ๊ฐ์ด TransactionAspectSupport๊ฐ ์คํํธ๋ ์ด์ค์ ํฌํจ๋์ง ์๋ ๊ฒ์ ํ์ธํ ์๊ฐ ์๋ค. @EnableGlobalMethodSecurity(prePostEnabled = true) ์ GlobalMethodSecurityConfiguration ๊ทธ๋ฆฌ๊ณ @EnableTransactionManagement() ์ธ ์ํ์์ ์ปค์คํ PermissionEvaluator๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํธ๋์ญ์ ์ธํฐ์ ํฐ๊ฐ ๋์ํ์ง ์์ ์ ์๊ฒ ๋๊ณ @Transactional์ ๋ช ์ํ๋๋ผ๋ ํธ๋์ญ์ ์ด ์์ฑ๋์ง ์์ ์ ์๋ ๋ฌธ์ ๋ฅผ ๋ด์ฌํ๊ฒ ๋๋ค.
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>)
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 ์ด ๋์ํ์ง ์์๋ ์ธ์งํ์ง ๋ชปํ๊ณ ๋ฐ์๋์์ ๊ฒ ๊ฐ๋ค์.