Spring security AccessDecisionManager: roleVoter, Acl Voter - spring

I'm trying to setup a Spring Security 3.2 project using Java Config and no XML at all.
I want to have an Access decision voter that supports both RoleHierarchyVoter and AclEntryVoters. This is the configuration I'm using:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AclEntryVoter aclUpdatePropertyVoter;
#Autowired
private AclEntryVoter aclDeletePropertyVoter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/")
.and()
.authorizeRequests()
.accessDecisionManager(accessDecisionManager())
.antMatchers("/login", "/signup/email", "/logout", "/search", "/").permitAll()
.anyRequest().authenticated();
}
#Bean
public RoleHierarchyVoter roleVoter() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_USER > ROLE_ANONYMOUS");
RoleHierarchyVoter roleHierarchyVoter = new RoleHierarchyVoter(roleHierarchy);
return roleHierarchyVoter;
}
#Bean
public AffirmativeBased accessDecisionManager() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<>();
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
decisionVoters.add(webExpressionVoter);
decisionVoters.add(roleVoter());
decisionVoters.add(aclDeletePropertyVoter);
decisionVoters.add(aclUpdatePropertyVoter);
AffirmativeBased affirmativeBased = new AffirmativeBased(decisionVoters);
return affirmativeBased;
}
}
However, when the app gets initialized I get the following exception:
I get the exception:
java.lang.IllegalArgumentException: AccessDecisionManager does not support secure object class: class org.springframework.security.web.FilterInvocation
When debugging the code I can see that when AbstractAccessDecisionManager is called and the following code is executed:
public boolean supports(Class<?> clazz) {
for (AccessDecisionVoter voter : this.decisionVoters) {
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
RoleHierarchyVoter support FilterInvocation, however AclEntryVoters fail to pass it. What I'm doing wrong in the configuration? How can I set the project so that it supports both types of voters? Thanks a lot in advance

As you've observed, the acl voters don't support filter invocations as they are intended for checking secured methods, not web requests.
You should configure a separate AccessDecisionManager for use with your method security and add the acl voters to that.

Related

Spring-Security OAUTH2 and PKCE, IdentityServer4 How do I add code_challenge info

I am trying to access Identity Server. When I try to access I get an error that says
MessageTemplate: code_challenge is missing
I have a basic Spring Boot Application. How do I get the app to add in the Code Challenge and code Challenge type.
I have tried to add in this:
#Configuration
public class OAuth2ClientConfiguration {
#Bean
public SecurityWebFilterChain pkceFilterChain(ServerHttpSecurity http, ServerOAuth2AuthorizationRequestResolver resolver) {
http.authorizeExchange(r -> r.anyExchange().authenticated());
http.oauth2Login(auth -> auth.authorizationRequestResolver(resolver));
return http.build();
}
#Bean
public ServerOAuth2AuthorizationRequestResolver pkceResolver(ReactiveClientRegistrationRepository repo) {
DefaultServerOAuth2AuthorizationRequestResolver resolver = new DefaultServerOAuth2AuthorizationRequestResolver(repo);
resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
return resolver;
}
but that causes an error saying that
Description:
Parameter 0 of method pkceFilterChain in com.landstar.security.poc.securitypoc.config.OAuth2ClientConfiguration required a bean of type 'org.springframework.security.config.web.server.ServerHttpSecurity' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.config.web.server.ServerHttpSecurity' in your configuration.
Additional Info
#Configuration
#EnableWebSecurity
public class SecurityConfig {
private static final String[] WHITE_LIST_URLS = {
"/user",
"/helloPublic",
"/register",
"/verifyRegistration*",
"/resendVerifyToken*"
};
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.authorizeHttpRequests()
.antMatchers(WHITE_LIST_URLS).permitAll()
.antMatchers("/api/**").authenticated()
.and()
.oauth2Login(oauth2login ->
oauth2login.loginPage("/auth2/authorization/landstar"))
.logout(l -> l
.logoutSuccessUrl("/").permitAll()
)
.oauth2Client(Customizer.withDefaults());
return http.build();
}
}
Please, any help.!!! thanks

How to configure ldap in spring-security 5.7 while retaining basic form login

I'm trying to configure my webSecurity to use both ldap and basic authentication (jdbc) with the new component-based security configuration (no WebSecurityConfigurerAdapter) but I can't get it to use both.
The required result is for spring to first attempt ldap, and if it doesn't find (or just fails for now is good enough) attempt to login using basic autentication.
The project is a migration from an older Spring-Boot version and with WebSecurityConfigurerAdapter the following code is what worked:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().antMatchers("/services/**").permitAll().anyRequest().authenticated();
http.httpBasic();
http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
http.logout().permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetails);
//#formatter:off
auth.ldapAuthentication()
.userSearchFilter("(uid={0})")
.userSearchBase("ou=people")
.groupSearchFilter("(uniqueMember={0})")
.groupSearchBase("ou=groups")
.groupRoleAttribute("cn")
.rolePrefix("ROLE_")
.userDetailsContextMapper(customLdapUserDetailsContextMapper())
.contextSource()
.url(ldapUrl);
//#formatter:on
}
#Bean
CustomLdapUserDetailsContextMapper customLdapUserDetailsContextMapper()
{
CustomLdapUserDetailsContextMapper mapper = new CustomLdapUserDetailsContextMapper();
mapper.setCustomUserDetailsService(userDetailsService());
return mapper;
}
//Implementation of custom contextMapper is not relevant for example i believe, basicly it maps some ldap roles, but for testing i don't use roles yet
}
and this is what my conversion to the new style looks like:
#Configuration
public class WebSecurityConfig
{
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager ldapAuthenticationManager) throws Exception
{
// #formatter:off
http.authorizeRequests()
.mvcMatchers("/services/**").permitAll()
.mvcMatchers("/resources/**").permitAll()
.mvcMatchers("/webjars/**").permitAll()
.anyRequest().authenticated();
http.httpBasic();
http.formLogin().permitAll().loginPage("/login").defaultSuccessUrl("/customer/overview", true);
http.logout().permitAll();
http.csrf().disable();
http.authenticationManager(ldapAuthenticationManager); //THIS LINE SEEMS TO BE PROBLEMATIC
// #formatter:on
return http.build();
}
#Bean
public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource ldapContextSource, UserDetailsService userDetailsService)
{
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(ldapContextSource);
UserDetailsServiceLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new UserDetailsServiceLdapAuthoritiesPopulator(userDetailsService);
factory.setUserSearchFilter("(uid={0})");
factory.setUserSearchBase("ou=people");
factory.setLdapAuthoritiesPopulator(ldapAuthoritiesPopulator);
return factory.createAuthenticationManager();
}
}
when in the above new code the line http.authenticationManager(ldapAuthenticationManager); is enabled ldap login works fine (and it even binds roles from database user), but basic login doesn't work. however when the line is disabled basic login works but ldap does not.
Any help on how to get spring to use both logins would be much appreciated.
Instead of creating a custom AuthenticationManager, you can create the AuthenticationProvider that will be used for LDAP authentication.
You can configure the provider on HttpSecurity:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, LdapAuthenticator authenticator) throws Exception {
// ...
http.authenticationProvider(
new LdapAuthenticationProvider(authenticator, ldapAuthoritiesPopulator));
// ...
return http.build();
}
#Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
BindAuthenticator authenticator = new BindAuthenticator(contextSource);
authenticator.setUserSearch(
new FilterBasedLdapUserSearch("ou=people", "(uid={0})", contextSource));
return authenticator;
}

automate the OAuth2 refresh_token process with SpringBoot 2

I have a SpringBoot2 application, a MainApp as a resource-server, KeyCloak as AuthorizationServer and a maven module, which is related to the MainApp, as a OAuth2LoginClient.
In other words, in MavenModule I have the follow SecurityConfig:
#Configuration
#PropertySource("classpath:idm.properties")
public class Auth0Provider extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.requestMatchers(PROTECTED_URLS).authenticated()
.anyRequest().authenticated()
)
.oauth2Login().redirectionEndpoint().baseUri("/callback*");
http.csrf().disable();
}
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/idmauth/**")
);
}
There is also a controller that intercepts the protected call:
#Value("${oauth.redirectURL}")
private String redirectURL;
#Autowired
private OAuth2AuthorizedClientService clientService;
#RequestMapping(method = RequestMethod.GET, path = "/redirect")
public RedirectView redirectWithUsingRedirectView(OAuth2AuthenticationToken oauthToken, RedirectAttributes attributes) {
OAuth2AuthorizedClient client =
clientService.loadAuthorizedClient(
oauthToken.getAuthorizedClientRegistrationId(),
oauthToken.getName());
String token = client.getAccessToken().getTokenValue();
attributes.addAttribute("jwt", token);
return new RedirectView(redirectURL);
}
This return the AccessToken to my frontend. Clearly in my idm.properties file I have the spring.oauth2.client.provider and spring.oauth2.client.registration info.
Now the MainApp is a SpringBoot2 WebApp with this simple SecurityConfig:
#EnableWebSecurity
#Configuration
public class Oauth2RestApiSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
And in it's application.properties just the line:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://<host>/protocol/openid-connect/certs
All works fine but, when the token expire, the only way I have currently found to refresh my token
is to manually do this HTTP-POST:
POST /auth/realms/<audience>/protocol/openid-connect/token HTTP/1.1
Host: <host>
Content-Type: application/x-www-form-urlencoded
Content-Length: 844
client_id=<my_client_id>
&client_secret=<my_client_secret>
&refresh_token=<refresh_token_previously_obtained>
&grant_type=refresh_token
Is there a better way to do this? Maybe inside the SecurityConfig or with a specific path inside spring.oauth2.x properties?
Note that refreshing an access token is done on the OAuth 2.0 client side.
This is done automatically by Spring Security if you have configured a WebClient to be used when requesting protected resources.
#Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
When you have done so, the expired OAuth2AccessToken will be refreshed (or renewed) if an OAuth2AuthorizedClientProvider is available to perform the authorization.

Spring Boot webservice (REST) - How to change JUnit 5 tests from basic authentication to OAuth2 (Keycloak)

I have a Spring Boot webservice with REST controllers and with basic authentication (username and password).
On this base I developed JUnit 5 test.
Now I switch to OAuth2, currently trying the Resource Owner Password Credentials grant type.
What do I need to change on my JUnit 5 tests to run now with OAuth2?
Of course, before running my new tests with OAuth2 I have to start first Keycloak, afterwards the tests.
Following is my setup for the current basic authentication and the new OAuth2.
BASIC AUTHENTICATION (old implementation)
On my webservice side the web security config class looks like following:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/articles/**").hasRole("ADMIN")
// More antMatchers...
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().disable();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails admin = User
.withUsername("admin")
.password("{noop}" + "admin123")
.roles("ADMIN")
.build();
// More users...
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(admin);
...
return userDetailsManager;
}
}
For the JUnit 5 tests I allways use the user admin, for example
#SpringBootTest
#AutoConfigureMockMvc
#WithUserDetails(value = "admin")
#TestInstance(Lifecycle.PER_CLASS)
public class MyRestControllerMockMvcTest {
#Autowired
private MockMvc mockMvc;
#BeforeAll
public void init(ApplicationContext appContext) throws Exception {
TestUtils.setupSecurityContext(appContext);
// some initialization
}
#AfterAll
public void cleanup(ApplicationContext appContext) throws Exception {
TestUtils.setupSecurityContext(appContext);
// some cleanup
}
#Test
public void getSomeInformationFromMyRestController() throws Exception {
MvcResult mvcResult = TestUtils.performGet(mockMvc, "...REST controller endpoint...", status().isOk());
MockHttpServletResponse response = mvcResult.getResponse();
ObjectMapper objectMapper = new ObjectMapper();
... = objectMapper.readValue(response.getContentAsString(), ...);
assertNotNull(...);
}
}
public class TestUtils {
public static void setupSecurityContext(ApplicationContext appContext) {
UserDetailsService uds = (UserDetailsService) appContext.getBean("userDetailsService");
UserDetails userDetails = uds.loadUserByUsername ("admin");
Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
}
public static MvcResult performGet(MockMvc mockMvc, String endpoint, ResultMatcher status) throws Exception {
MvcResult mvcResult = mockMvc.perform(get(endpoint))
.andDo(print())
.andExpect(status)
.andReturn();
return mvcResult;
}
}
Looking right now on the test setup in #BeforeAll and #AfterAll I'm not sure all of a sudden if I have to do
TestUtils.setupSecurityContext(appContext);
because now I use
#WithUserDetails(value = "admin")
#TestInstance(Lifecycle.PER_CLASS)
on the class. Just curious if the tests would still run without TestUtils.setupSecurityContext(appContext);, will try.
OAUTH2 (new implementation, replacing basic authentication above)
application.properties
...
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8183/auth/realms/myrealm/protocol/openid-connect/certs
With OAuth2 I changed the web security config class in my webservice (resource server) as following:
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/articles/**").hasRole("ADMIN")
// More antMatchers...
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter())
;
return httpSecurity.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new MyRoleConverter());
return jwtAuthenticationConverter;
}
public class MyRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
#Override
public Collection<GrantedAuthority> convert(final Jwt jwt) {
jwt.getClaims().get("realm_access");
// Create roles
return ...;
}
}
}
My users are now defined in Keycloak.
Keycloak is configured to use Resource Owner Password Credentials.
#jzheaux is right (sure, he's spring-security team member...).
Changes will occure in your security configuration but the test won't change ... for the most part: You'll probably want to have an Authentication of the right type in your test security-context.
If your new security configuration populates security-context with JwtAuthenticationToken, it would be nice to have JwtAuthenticationToken in test security-context too. #WithUserDetails(value = "admin") won't build JwtAuthenticationToken.
You should have a look at this lib I wrote and specifically at #WithMockJwtAuth. Usage is demonstrated there:
#Test
#WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = #OpenIdClaims(sub = "Ch4mpy"))
public void greetJwtCh4mpy() throws Exception {
api.get("/greet").andExpect(content().string("Hello Ch4mpy! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."));
}
P.S.
You'll find in this same git repo samples for other kind of Authentication better adapted to OIDC than JwtAuthenticationToken like KeycloakAuthenticationToken (written by Keycloak team for Keycloak exclusively) or OidcAuthentication (written by myself for any OpenID Connect complient authorization server), along with #WithMockKeycloakAuth and #WithMockOidcAuth

How to configure Spring ACL without XML file

I am trying to add ACL capabilities to my server. I have configured spring security using java file and would like to add ACL in the same manner. How should I do it? All the tutorials I found used XML file.
SecurityInit:
#Order(1)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
SecurityConfig
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
#Component
#ComponentScan(basePackages = {"test.package"})
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
...
#Autowired
protected void registerAuthentication(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// http://stackoverflow.com/a/21100458/162345
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().disable()
.addFilterBefore(...)
.addFilterBefore(...)
// TODO: create a better way to differentiate login to signup
.exceptionHandling()
.authenticationEntryPoint(noRedirectForAnonymous)
.and()
.formLogin()
.successHandler(restAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
.and()
.logout()
.logoutSuccessHandler(noRedirectLogoutSuccessHandler)
.and()
.authorizeRequests()
.antMatchers("/api/keywords/**").permitAll()
.antMatchers("/api/**").authenticated();
}
}
You can configure spring acl with Java configuration class as follow
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfig extends GlobalMethodSecurityConfiguration {
#Autowired
DataSource dataSource;
EhCacheBasedAclCache aclCache() {
EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
factoryBean.setName("aclCache");
factoryBean.setCacheManager(cacheManager.getObject());
return new EhCacheBasedAclCache(factoryBean.getObject());
}
LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ACL_ADMIN"),
new SimpleGrantedAuthority("ROLE_ACL_ADMIN"),
new SimpleGrantedAuthority("ROLE_ACL_ADMIN"));
}
#Bean
JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
return service;
}
#Bean
AclMasterService masterService() {
return new AclMasterService();
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler(){
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
return expressionHandler;
}
}
The important aspect of the configuration are extend from
GlobalMethodSecurityConfiguration
override the method
createExpressionHandler
and enable the Pre and Post anotations with the follow anotation at the begining of the class
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled =
true)
Now you can use anotations like
#PreAuthorize('hasPermission(#object,read)')
see the Contact sample of Spring Security or the spring security reference guide for more uses of #Pre and #Post anotations.
This configuration class was tested on Spring 4 , Spring Security 4.0.1 and Spring Security ACL 3.1.2. If you want configure the authentication you can use a different Java class or override the configure method from this. If you already have a configured ehcache this configuration could not work correctly due to the ehcache is a singleton class and this configuration try to create a new one.
There is no way to configure spring acl without xml file. This is mentioned in spring docs itself.Refer to spring documentation.

Resources