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.
Related
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.
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);
}
}
I am using UserDetails implementation for authentication which has methods
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
If I set isAccountNonLocked() to return false I am unable to log in anymore.
How can I implement own UserDetailsChecker to override exceptions thrown on checks and where I put my custom UserDetailsChecker, because I am not sure how this preauthentication checks work.
If you have a custom UserDetailsChecker, you would wire that up when configuring the AuthenticationManager:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dao());
}
AuthenticationProvider dao() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPreAuthenticationChecks(new MyUserDetailsChecker());
return provider;
}
#Bean
public UserDetailsService userDetailsService() {
// ... expose a UserDetailsService
}
}
Note that you might already be publishing a UserDetailsService in another way, but I've included it here as a reminder that the authentication provider needs one in order to work.
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();
}
}
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());
}