Vaadin VaadinWebSecurity & SecurityContextHolder.setStrategyName - vaadin

I have a project based on Spring Boot 2.7.5 and Vaadin 23.3.0.
Using #Async methods, I'm losing the authenticated principal on upstream API calls. Researching this the solution here seems to be setting:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
This resolves the issue and allows for authentication data to reach threads created by a #Async function.
My question is, I see VaadinWebSecurity is setting SecurityContextHolder.setStrategyName as follows:
protected void configure(HttpSecurity http) throws Exception {
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
...
I can override this in my SecurityConfig as follows:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
...
Does the above affect Vaadin's security setup at all? Is this the suggested fix for my situation or is there a better approach?

As per Simon's recommendation, creating a new SecurityContextHolderStrategy which uses InheritableThreadLocal instead of the default ThreadLocal as follows:
import com.vaadin.flow.server.VaadinSession;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import java.util.Optional;
import static java.util.Objects.requireNonNull;
public class VaadinAwareSecurityContextHolderStrategyUsingInheritableThreadLocal implements SecurityContextHolderStrategy {
private final InheritableThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal<>();
#Override
public void clearContext() {
contextHolder.remove();
}
#Override
#NonNull
public SecurityContext getContext() {
SecurityContext context = getFromVaadinSession().orElseGet(contextHolder::get);
if (context == null) {
context = createEmptyContext();
contextHolder.set(context);
}
return context;
}
#NonNull
private Optional<SecurityContext> getFromVaadinSession() {
VaadinSession session = VaadinSession.getCurrent();
if (session == null || session.getSession() == null) {
return Optional.empty();
}
Object securityContext = session.getSession().getAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
if (securityContext instanceof SecurityContext) {
return Optional.of((SecurityContext) securityContext);
} else {
return Optional.empty();
}
}
#Override
public void setContext(#NonNull SecurityContext securityContext) {
contextHolder.set(requireNonNull(securityContext));
}
#Override
#NonNull
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
Then applying this in my SecurityConfig after I call super.configure():
public class SecurityConfig extends VaadinWebSecurity {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategyUsingInheritableThreadLocal.class.getName());
...
}
...
Allows #Async methods to inherit authentication detail used in my WebClient for upstream API calls.

Related

Spring Security, How we can create authorization rules antmatchers dynamically from database

Is it a good practice to store authorization rules antmatchers URLs and Role in database and create dynamically on server start?
When a request comes to application does it fetch rules every time from database in Configure method of the WebSecurityConfigurerAdapter if we use create them by fetching from database.
I'm not able to find any example that how we can create new rules dynamically just from database without changing application code.
Here is the sample code that I want to create rules dynamically
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/dashboard/**").hasAuthority("VIEW_DASHBOARD")
.antMatchers("/dashboard/**").hasAuthority("EDIT_DASHBOARD")
.antMatchers("/dashboard/**").hasAuthority("DELETE_DASHBOARD")
.antMatchers("/profiling/**").hasAuthority("VIEW_PROFILING")
.antMatchers("/profiling/**").hasAuthority("EDIT_PROFILING")
.antMatchers("/profiling/**").hasAuthority("DELETE_PROFILING");
}
you can create your AccessDecisionVoter
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// you can inject AccessDecisionVoter in spring container
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
decisionVoters.add(new AccessDecisionVoter<FilterInvocation>() {
#Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
#Override
public boolean supports(Class<?> clazz) {
return true;
}
#Override
public int vote(Authentication authentication, FilterInvocation invocation, Collection<ConfigAttribute> attributes) {
String url = invocation.getRequestUrl();
AntPathMatcher m = new AntPathMatcher();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean matched = false;
// you can retrieve url pattern from database here
if (m.match("/**", url) && CollectionUtils.isEmpty(authorities) == false) {
matched = authorities.stream().anyMatch(a -> {
return a.getAuthority().equals("your_authority");
});
}
if (matched) {
return AccessDecisionVoter.ACCESS_GRANTED;
} else {
return AccessDecisionVoter.ACCESS_DENIED;
}
}
});
AffirmativeBased aa = new AffirmativeBased(decisionVoters);
http.authorizeRequests().accessDecisionManager(aa);
}
}

How to secure Apache Camel rest endpoint with Spring Security and OAuth2

I'm working on Spring Boot application with configured SSO/OAuth2 security.
Authentication works fine for my rest controllers and now I need to secure my Apache Camel route with a rest endpoint.
As I understand there are several ways how to do it:
By adding auth processor to my route
By adding policy (SpringSecurityAuthorizationPolicy) to my route
By handlers option to jetty endpoint
I'm trying to do it by adding new auth processor to my rest endpoint but I stuck on this exception:
org.springframework.security.oauth2.common.exceptions.OAuth2Exception:
No AuthenticationProvider found for
org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
During debugging I see that org.springframework.security.authentication.ProviderManager.getProviders() contains only one provider AnonymousAuthenticationProvider so probably I have to register appropriate provider...
Can someone help me to find the right way to solve this problem please?
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().permitAll();
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${oauth2.token.endpoint}")
private String tokenEndpoint;
#Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("clientId");
tokenServices.setClientSecret("clientSecret");
tokenServices.setCheckTokenEndpointUrl(tokenEndpoint);
return tokenServices;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
}
}
#Configuration
public class EmbeddedServerRoute {
#Bean
public RoutesBuilder embeddedServer() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().component("jetty").port("8081").bindingMode(RestBindingMode.json);
}
};
}
}
#Component
public class RestTestRoute extends RouteBuilder {
#Autowired
private AuthProcessor authProcessor;
#Override
public void configure() throws Exception {
from("rest:get:/test").process(authProcessor).to("mock:end").end();
}
}
#Component
public class AuthProcessor implements Processor {
#Autowired
private AuthenticationManager authenticationManager;
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
#Override
public void process(Exchange exchange) throws Exception {
HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class);
Subject subject = new Subject();
Authentication auth = getAuth(request);
subject.getPrincipals().add(auth);
exchange.getIn().setHeader(Exchange.AUTHENTICATION, subject);
}
private Authentication getAuth(HttpServletRequest request) throws OAuth2Exception {
Authentication authentication = null;
try {
authentication = tokenExtractor.extract(request);
if (authentication != null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
return authenticationManager.authenticate(authentication);
}
} catch (Exception e) {
throw new OAuth2Exception(e.getMessage());
}
throw new OAuth2Exception("Not Authorized to view resource");
}
}
As a final solution I decided to use Spring Boot embedded servlet container instead of Apache Camel rest component. So it could be easily secured by Spring Security. This could be done by creating additional beans:
#Bean
public ServletRegistrationBean servletRegistrationBean() {
SpringServerServlet serverServlet = new SpringServerServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean(serverServlet, "/camel/*");
Map<String, String> params = new HashMap<>();
params.put("org.restlet.component", "restletComponent");
regBean.setInitParameters(params);
return regBean;
}
#Bean
public Component restletComponent() {
return new Component();
}
#Bean
public RestletComponent restletComponentService() {
return new RestletComponent(restletComponent());
}

How to set a custom invalid session strategy in Spring Security

I'm developing a web application, based on Spring-Boot - 1.1.6, Spring -Security -3.2.5 and more.
I'm using Java based configuration:
#Configuration
#EnableWebMvcSecurity
public class SecurityCtxConfig extends WebSecurityConfigurerAdapter {
#Bean
DelegatingAuthenticationEntryPoint delegatingAuthenticationEntryPoint() {
LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = new LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>();
Http403ForbiddenEntryPoint defaultEntryPoint = new Http403ForbiddenEntryPoint();
map.put(AnyRequestMatcher.INSTANCE, defaultEntryPoint);
DelegatingAuthenticationEntryPoint retVal = new DelegatingAuthenticationEntryPoint(map);
retVal.setDefaultEntryPoint(defaultEntryPoint);
return retVal;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http.exceptionHandling();
exceptionHandling.authenticationEntryPoint(delegatingAuthenticationEntryPoint());
http.logout().logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication arg2)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
});
}
}
The requirement is to return Http status 401 in case that the session cookie is invalid or missing(no matter the reason)
I see the InvalidSessionStrategy but I don't find a way to set it on the SessionManagementFilter.
Can some one please instract me how to implement my plan or another one that will fulfill the requirement
Using SpringBoot this works for me:
#Configuration
#EnableWebSecurity
public class UISecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.addFilterAfter(expiredSessionFilter(), SessionManagementFilter.class);
...
}
private Filter expiredSessionFilter() {
SessionManagementFilter smf = new SessionManagementFilter(new HttpSessionSecurityContextRepository());
smf.setInvalidSessionStrategy((request, response) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Session go BOOM!"));
return smf;
}
}
We had the exact same problem and I did this hack to solve it (yes I know, this is a hack, therefore the name...).
I create a BeanPostProcessor and search for the SessionManagementFilter to reconfigure it...
#Bean
public HackyBeanPostProcessor myBeanPostProcessor() {
return new HackyBeanPostProcessor();
}
protected static class HackyBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// FIXME check if a new spring-security version allows this in an
// other way (current: 3.2.5.RELEASE)
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new InvalidSessionStrategy() {
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
});
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
Since I'm using AspectJ (I mean, compile time weaving and not Spring AOP), it was quite easy to hack the SessionManagementFilter creation by setting my custom InvalidSessionStrategy after the SessionManagementFilter is constructed:
#Aspect
public class SessionManagementAspect {
private static final Log logger = LogFactory.getLog();
#AfterReturning("execution( org.springframework.security.web.session.SessionManagementFilter.new(..))&&this(smf)")
public void creation(JoinPoint pjp, SessionManagementFilter smf) throws Throwable {
logger.debug("Adding/Replacing the invalid session detection policy to return 401 in case of an invalid session");
smf.setInvalidSessionStrategy(new InvalidSessionStrategy() {
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
logInvalidSession(request, "invalid cookie");
if (!response.isCommitted())
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
});
}
}
If you are not using AspectJ, try adding #Component and add this Aspect to your context, it might work if the SessionManagementFilter is a bean (Since Spring-AOP applias only on spring beans)

Spring Security with Java Configuration: How to handle BadCredentialsException from a custom provider

I need to authenticate some rest services using a token id in the url (or maybe in the request header - but this is not important for now). I am trying to use java configuration to set this up using as a guide this post. My problem is that I do not know how to handle "BadCredentialsException" that is thrown when the authentication fails from the provider. Here is my Security Config:
public static class SecurityConfigForRS extends
WebSecurityConfigurerAdapter {
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.regexMatcher("^/rest.*")
.addFilterBefore(
new TokenAuthenticationFilter(
authenticationManagerBean()),
AbstractPreAuthenticatedProcessingFilter.class)
.and().csrf().disable();
}
}
For now I skip the other implementations - if it helps I will post them later.
When the token is missing or is invalid, the TokenAuthernticationProvider throws a BadCredentialsException. I need to catch this and send back an 401-Unauthorized. Is it possible to do this?
The first Filter I created was a subclass of GenericFilterBean and it did not have support for authentication failure handler or success handler. However AbstractAuthenticationProcessingFilter supports success and failure handlers. My filter is as simple as that:
public class TokenAuthenticationProcessingFilter extends
AbstractAuthenticationProcessingFilter {
public TokenAuthenticationProcessingFilter(
RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
Authentication auth = new TokenAuthentication("-1");
try {
Map<String, String[]> params = request.getParameterMap();
if (!params.isEmpty() && params.containsKey("auth_token")) {
String token = params.get("auth_token")[0];
if (token != null) {
auth = new TokenAuthentication(token);
}
}
return this.getAuthenticationManager().authenticate(auth);
} catch (AuthenticationException ae) {
unsuccessfulAuthentication(request, response, ae);
}
return auth;
}}
and my http security is:
public static class SecurityConfigForRS extends
WebSecurityConfigurerAdapter {
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(tokenAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
#Bean
protected AbstractAuthenticationProcessingFilter getTokenAuthFilter()
throws Exception {
TokenAuthenticationProcessingFilter tapf = new TokenAuthenticationProcessingFilter(
new RegexRequestMatcher("^/rest.*", null));
tapf.setAuthenticationManager(authenticationManagerBean());
return tapf;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.regexMatcher("^/rest.*")
.addFilterAfter(getTokenAuthFilter(),
BasicAuthenticationFilter.class).csrf().disable();
}
}
The filter chain order does matter! I placed it after BasicAuthenticationFilter and it works fine. Of course there might be a better solution but for now this works!
May be you can try with Global Exception handling with ControllerAdvice
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler // when Invalid Credentials
public ResponseEntity<ErrorMessage> handleInvalidCredentialsException(
BadCredentialsException ex) {
return new ResponseEntity<ErrorMessage>(
new ErrorMessage(ex.getMessage()), HttpStatus.UNAUTHORIZED);
}
#Getter
#Setter
#AllArgsConstructor
class ErrorMessage {
private String error;
}
}

Spring Security: #PreAuthorized("hasAuthority()") Not Being Checked

Per the Spring Security 3.2.0 documentation I've created a Spring Security configuration and reference it in getRootConfigClasses:
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfiguration.class, SpringSecurityConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringWebConfiguration.class};
}
I can prove that this mostly works as Spring Security forces users to login per my configurataion. The problem is with method security. I've annotated SpringSecurityConfig with #EnableGlobalMethodSecurity like so:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
then annotated a method in my contoller with:
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
with the idea of only allowing those with ROLE_ADMIN access to this controller method. However, those logged in with ROLE_USER and ROLE_ADMIN can call this method, not what is expected.
By modifying my web application initializer to doubly include the Spring Security configuration it starts to work, but I'd like to use method authentication on methods in my root context as well the web context, which I can't seem to make happen:
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfiguration.class, SpringSecurityConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringSecurityConfig.class, SpringWebConfiguration.class};
}
Does each context need its own security config? Or should one in the parent context suffice?
Thanks.
I finally managed to have a root context, with a child web context and have #Pre and #Post authorization annotations work for controllers.
The trick was to expose the AuthenticationProvider created in the RootContext, which is not exposed by default.
So, my setup is :
#Order(1)
public class SecurityWebAppInitializer extends AbstractSecurityWebApplicationInitializer {}
#Order(2)
public class ApiDispatcherInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfiguration.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { ApiWebMvcConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/*" };
}
}
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// IMPORTANT: to expose it to the WebContext
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#Configuration
#EnableWebMvc
#EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY, proxyTargetClass = true) // <-- IMPORTANT to make it work for controllers
#ComponentScan(basePackageClasses = { foo.bar.Package.class }, useDefaultFilters = false, includeFilters = { #Filter(Controller.class) })
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}
Hope this might help someone.

Resources