스프링시큐리티 전역메서드 보안 17장 / 사전필터링과 사후필터링 / 스프링시큐리티 oauth2 어플리케이션 18장
메서드 호출은 허용하면서도 메서드로 보내는 매개 변수가 몇가지 규칙을 따르는지 확인
사전 필터링 : 프레임워크가 메서드를 호출하기 전에 매개 변수의 값을 필터링
사후 필터링 : 프레임워크가 메서드를 호출한 후 반환된 값을 필터링
사전 권한 부여 : 매개변수가 주어진 권한 부여 규칙을 준수하지 않으면 프레임워크가 아예 메서드를 호출하지 않음.(16장 내용)
하지만, 사전필터링 : 메서드가 호출되긴 하지만, 규칙을 준수하는 값만 메서드에 매개변수로 전달(17장)
사용자정의
package com.laurentiuspilca.ssia.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ProjectConfig {
@Bean
public UserDetailsService userDetailsService() {
var uds = new InMemoryUserDetailsManager();
var u1 = User.withUsername("nikolai")
.password("12345")
.authorities("read")
.build();
var u2 = User.withUsername("julien")
.password("12345")
.authorities("write")
.build();
uds.createUser(u1);
uds.createUser(u2);
return uds;
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
@PreFilter어노테이션
@Service
public class ProductService {
@PreFilter("filterObject.owner == authentication.name")
public List<Product> sellProducts(List<Product> products) {
// sell products and return the sold products list
return products;
}
}
그냥 sell을 호출할 경우 거부
C:\Users\user>curl http://localhost:8080/sell
{"timestamp":"2023-10-16T07:35:59.508+00:00","status":401,"error":"Unauthorized","message":"","path":"/sell"}
로그인 사용자를 주면
C:\Users\user>curl -u julien:12345 http://localhost:8080/sell
[{"name":"chocolate","owner":"julien"}]
C:\Users\user>curl -u nikolai:12345 http://localhost:8080/sell
[{"name":"beer","owner":"nikolai"},{"name":"candy","owner":"nikolai"}]
위와 같이 적절한 프로덕트를 가져오는 것을 확인.
여기서 재밌는 사실은 /sell메서드를 호출할 때
적절한 값을 필터링 한다는 것이다.
우리가 봤을때 유저에 해당하는 프로덕트를 가져온다고 볼 수 있지만
사실은 맞지 않는 프로덕트를 빼준다고 책에서 나온다.
그래서 arraylist가 아닌 list.of와 같은 변경이 불가능한 인스턴스는 에러가 발생한다.
그다음 사후필터링 예제
컨트롤러
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/find")
public List<Product> findProducts() {
return productService.findProducts();
}
}
서비스이다.
@Service
public class ProductService {
@PostFilter("filterObject.owner == authentication.principal.username")
public List<Product> findProducts() {
List<Product> products = new ArrayList<>();
products.add(new Product("beer", "nikolai"));
products.add(new Product("candy", "nikolai"));
products.add(new Product("chocolate", "julien"));
return products;
}
}
사실 사전필터링과 사후필터링을 자세하게 비교해보면 거의 차이가 없는것 같다.
차이라고는 post인지 pre인지 어노테이션 정도이다.
하지만 둘을 면밀하게 실행하면서 비교해보면 아주 큰 차이가 있다.
사후필터링은 서비스단에서 4번이 실행될때까지 해당 프로덕트 3개를 모두 가지고 있다.
마지막 리턴 product를 하면서 필터링 된 후에 해당 프로덕트에 해당하는 값을 돌려준다.
하지만 사전필터링은 3번 부분에서 이미 필터링된 값을 가지고 있다.
이 이외의 기능들은 모두 동일하다.
그래서 결론은 필터링은 메서드 호출 자체를 제한하지는 않는다.
메서드 호출 전후의 데이터들을 제한할 수 있는 것이 필터링이다.
이어서 18장.
여기까지 올 줄 몰랐다.
유종의미를 거둬보자.
시작이 가장 중요하지만
시작을 했다면 시작보다 마무리가 훨씬 중요하다.
18장에서는 키클락을 이용해서 신규 프로젝트를 구성하는 예제이다.
2.2 왜 키클락인가?
키클락은 오픈소스이고, 모던 앱과 서비스를 타겟팅한 접근 관리 솔루션이다. 키클락은 SSO를 지원하고, Social Login을 지원하고, User의 그룹핑을 지원하고, 클라이언트, 관리자 페이지, 계정 관리 콘솔을 제공합니다
4. [SSO] Keycloak & Springboot 적용기
1. 서론 앞서 SSO 구축을 위한 오픈소스(Keycloak)을 찾았고, 전반적인 개념과 getting started에 대해 알아보았다. 이제 스프링 부트에 더 잘 적용시키기 위해 www.baeldung.com/spring-boot-keycloak A Quick Guide to Us
dkyou.tistory.com
라고 한다.
키클락 공식홈페이지
Keycloak
Single-Sign On Users authenticate with Keycloak rather than individual applications. This means that your applications don't have to deal with login forms, authenticating users, and storing users. Once logged-in to Keycloak, users don't have to login again
www.keycloak.org
키클락은 솔직히 처음들어본다.
회사에서 sso를 이미 자체구축해서 사용하고 있기 때문에
sso서버를 키클락같은 플랫폼을 이용해서 할 필요가 없었다.
키클락을 도커로 올리는 방법도 있어서
https://www.keycloak.org/getting-started/getting-started-docker
Docker - Keycloak
A realm in Keycloak is equivalent to a tenant. Each realm allows an administrator to create isolated groups of applications and users. Initially, Keycloak includes a single realm, called master. Use this realm only for managing Keycloak and not for managin
www.keycloak.org
문서를 가져와봤다.
키클락을 따로 구성해보지는 않았다.
다만, 여기 예제에서는 권한부여서버를 키클락으로 사용했고
리소스서버만 따로 구성해서 어플을 완성했다.
리소스서버는 이미 기존에 공부했던 내용이라 크게 다른 부분은 없다.
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${claim.aud}")
private String claimAud;
@Value("${jwkSetUri}")
private String urlJwk;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore());
resources.resourceId(claimAud);
// resources.expressionHandler(handler());
}
@Bean
public TokenStore tokenStore() {
return new JwkTokenStore(urlJwk);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers(HttpMethod.DELETE, "/**").hasAuthority("fitnessadmin")
.anyRequest().authenticated();
}
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
// @Bean
// public SecurityExpressionHandler<FilterInvocation> handler() {
// return new OAuth2WebSecurityExpressionHandler();
// }
}
18장은 키클락을 사용해보는 예제와 다름없다.
끝.