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

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);
}
}

Related

Vaadin VaadinWebSecurity & SecurityContextHolder.setStrategyName

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.

Spring Security custom ldapAuthenticationProvider + custom ldapAuthoritiesPopulator

Is it possible to have a custom ldap authentication provider along with custom ldap authorities populator?
I don't want to restart my application each time ldap server is unreachable for a short moment (So i need the custom provider, to create a new context and override authenticate method on each login).
On the other side, i need to create custom roles for each membership of ldap user (need to override the getGrantedAuthorities)
For implementing custom ldap authentication provider you need to create class that extends from AbstractLdapAuthenticator
public class BindPasswordAuthentificator extends AbstractLdapAuthenticator {
public BindPasswordAuthentificator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
#Override
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user;
String username = authentication.getName();
String password = (String)authentication.getCredentials();
user = authenticateByLdap(username, password); // authenticate user here
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
}
And for implementing ldap authorities populator you need to create class that extends from LdapAuthoritiesPopulator
public class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Collection<GrantedAuthority> gauth = new HashSet<>();
//you need to place logic for populating user authorities here
return gauth;
}
}
After that you need to configure these two classes in your configuration
#Configuration
#PropertySource("classpath:application.properties")
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${life.ldap.server}")
private String ldapServer;
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
return new LdapAuthenticationProvider(authentificator(), authPopulator());
}
#Bean
public BindPasswordAuthentificator authentificator() {
return new BindPasswordAuthentificator(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(ldapServer);
}
#Bean
public CustomLdapAuthoritiesPopulator authPopulator() {
CustomLdapAuthoritiesPopulator result = new CustomLdapAuthoritiesPopulator();
return result;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/tokens/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().csrf().disable();
}
}

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;
}
}

Resources