I have a SpringBoot based app, with multiple endpoints. Because of different clients that will be accessing the endpoints I would like to have different Authentication providers protecting them. Some endpoints would be protected by Kerberos (KerberosServiceAuthenticationProvider -- http://docs.spring.io/autorepo/docs/spring-security-kerberos/1.0.0.RC1/reference/htmlsingle/). Some endpoints would be protected by AD/LDAP (ActiveDirectoryLdapAuthenticationProvider).
I currently have it working with Kerberos OR LDAP, but not both:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//For Kerberos
auth.authenticationProvider(kerberosAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider());
//For LDAP
//auth.authenticationProvider(customAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, APPLICATION_ADMIN_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.PUT, APPLICATION_ADMIN_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, APPLICATION_ADMIN_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(CLIENT_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(SWAGGER_ENDPOINTS)
.permitAll()
.and()
.authorizeRequests()
.antMatchers(MANAGER_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_MANAGER_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers(TRUSTED_AGENT_ENDPOINTS)
.hasAnyAuthority(AUTHENTICATED_TRUSTED_AGENT_AUTHORITIES)
.and()
.authorizeRequests()
.antMatchers("/kerb/**")
.hasAnyAuthority(AUTHENTICATED_APPLICATION_ADMIN_AUTHORITIES)
.and()
.addFilterBefore(spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class)
.httpBasic()
.and()
.csrf()
.disable();
}
}
#Bean
public AuthenticationProvider customAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
ldapDomain, ldapUrl);
SimpleCaseAndWhitespaceMitigatingAuthoritiesMapper authoritiesMapper = new SimpleCaseAndWhitespaceMitigatingAuthoritiesMapper();
provider.setAuthoritiesMapper(authoritiesMapper);
provider.setConvertSubErrorCodesToExceptions(true);
return provider;
}
#Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true);
provider.setKerberosClient(client);
provider.setUserDetailsService(kerberosUserService());
return provider;
}
#Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(kerberosUserService());
return provider;
}
#Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(kerberosPrincipal);
File f = new File(keytabFile);
try {
LOG.info(String.format("Absolute: %s, Canonical: %s", f.getAbsolutePath(), f.getCanonicalPath()));
if(f.exists()){
LOG.info("File exists.");
}
else{
LOG.info("File DOES NOT exist.");
}
} catch (IOException e) {
e.printStackTrace();
}
ticketValidator.setKeyTabLocation(new FileSystemResource(f));
ticketValidator.setDebug(true);
return ticketValidator;
}
#Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
#Bean
public KerberosUserDetailsService kerberosUserService() {
return new KerberosUserDetailsService();
}
Anyway to get this to work for both? I was thinking about making a custom authentication provider that would handle the requests, but wasn't sure if that would work.
Easiest think to do is have to SpringDistpatcherServlets in your Web.xml based on url mapping. Each url mapping is then in a different spring context. Each spring context can then have its own security.
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(kerberosAuthenticationProvider());
auth.authenticationProvider(kerberosServiceAuthenticationProvider());
auth.authenticationProvider(customAuthenticationProvider());
}
What you are doing is adding both authenticationProviders for Kerberos and your custom one to the AuthenticationManagerBuilder. This should register all of them.
What happens during runtime:
There is a ProviderManager handling all of your registered AuthenticationProvider and the build in one.
First it tries to authenticate as anonymous user. If the requested URL is set to permitAll this is the end of the story
Then the ProviderManager iterates over all of your AuthenticationProvider in the order you provided them. It checks if they support the authentication and tries to authenticate with them. If it fails it moves to the next one (saving the exception if there was one)
Finally there is a DaoAuthenticationProvider handling normal username-password-credentials
If any provider succeeds, the user is logged in, otherwise the saved exception is thrown.
Conclusion:
What you did should be quite close if not exactly what you want. For your Kerberos protected endpoint it would use the kerberos AuthenticationProvider. For the other endpoints it would try and fail Kerberos and move on to your custom provider.
If something is still not working I would recommend setting a breakpoint within the class org.springframework.security.authentication.ProviderManager and see how it is handling your provider.
Related
I'm trying to make security using oauth2 resource server with google as authentication provider and add custom roles from database by getting email from JWT token and searching for it in database.
This is my configuration
#Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/csrf").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer().jwt().decoder(jwtDecoder()).jwtAuthenticationConverter(jwtAuthenticationConverter())
.and()
.and()
.cors().and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
return http.build();
}
JWT decoder and authentication converter
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("roles", customClaim -> getUserRolesFromDatabase()));
jwtDecoder.setClaimSetConverter(converter);
jwtDecoder.setJwtValidator(withIssuer);
return jwtDecoder;
}
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
public List<String> getUserRolesFromDatabase() {
return Collections.singletonList("USER");
}
How do I get email claim from bearer token to use it in getUserRolesFromDatabase() and search for roles in database?
This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 1 year ago.
I have the following configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class SamlConfig extends WebSecurityConfigurerAdapter {
#Value("${enable_csrf}")
private Boolean enableCsrf;
#Autowired
private SamlUserService samlUserService;
public SamlWebSecurityConfig() {
super();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.userDetailsService(samlUserService)
.serviceProvider()
.keyStore()
.storeFilePath("path")
.password("password")
.keyname("alias")
.keyPassword("password")
.and()
.protocol("https")
.hostname(String.format("%s:%s","localhost", "8080"))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath("metadata");
if (!enableCsrf) {
http.csrf().disable();
}
}
}
#Configuration
#Order(2)
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
This works for the SAML, but the basic login returns an error: 403 forbidden.
I modified the BasicConfig with this, and SAML doesn't work anymore but basic authentication works. All the endpoints are for both SAML and basic authentication, just different login page.
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
For some reasons sometimes it works, sometimes not. I also tried to modify the #Order and still not working.
In Spring Security, there are two things that are alike but do things completely differently, requestMatchers().antMatchers() and authorizeRequests().antMatchers().
The requestMatchers tells HttpSecurity to only invoke the SecurityFilterChain if the provided RequestMatcher was matched.
The authorizeRequests allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
In your case, you have two SecurityFilterChains. But only the one with the highest priority is being invoked, this happens because you did not give any requestMatchers to it, therefore it will match every request. And only one SecurityFilterChain is called per request, thus it will not invoke the next one.
So, you should inform the requestMatchers for your configurations, like so:
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/sso", "/saml/**")
)
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
...
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/basic", "/**")
)
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
I rewrite the security configuration class from this tutorial.
I try to enter the entries without login, the server redirect to the login page well, but submit the user credentials to login processing URL return page not found. How should I fix it?
My Spring Security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
PersistentTokenRepository tokenRepository;
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Configuration
#Order(1)
ublic static class MobileSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/mobile/**")
.authorizeRequests()
.anyRequest().hasRole("MOBILE")
.and()
.formLogin()
.loginPage("/mobile_login")
.loginProcessingUrl("/mobile_login_processing_url")
.usernameParameter("ssoId")
.passwordParameter("password")
.defaultSuccessUrl("/mobile/menu")
.and()
.logout()
.logoutUrl("/mobile_logout")
.logoutSuccessUrl("/mobile_login?logout")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/Access_Denied")
.defaultAuthenticationEntryPointFor(authenticationEntryPoint(), new AntPathRequestMatcher("/mobile/**"));
}
#Bean
Public AuthenticationEntryPoint authenticationEntryPoint(){
return new LoginUrlAuthenticationEntryPoint("/mobile_login");
}
}
#Configuration
#Order(2)
public static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
PersistentTokenRepository tokenRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/web/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/web_login")
.loginProcessingUrl("/web_login_processing_url")
.usernameParameter("ssoId")
.passwordParameter("password")
.defaultSuccessUrl("/web/list")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/web_login?logout")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
PersistentTokenBasedRememberMeServices tokenBasedservice = new PersistentTokenBasedRememberMeServices(
"remember-me", userDetailsService, tokenRepository);
return tokenBasedservice;
}
#Bean
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
return new AuthenticationTrustResolverImpl();
}
}
Your login processing URL /mobile_login_processing_url is unavailable.
You configured a formLogin, which adds a UsernamePasswordAuthenticationFilter for a HttpSecurity, see FormLoginConfigurer:
The following Filters are populated
UsernamePasswordAuthenticationFilter
but your HttpSecurityis restricted to the pattern /mobile/**, see HttpSecurity#antMatcher:
Allows configuring the HttpSecurity to only be invoked when matching the provided ant pattern.
so with URL /mobile_login_processing_url the HttpSecurityis not invoked and therefore the UsernamePasswordAuthenticationFilter is never applied. Without this filter, the form login is not processible, see UsernamePasswordAuthenticationFilter:
Processes an authentication form submission.
You have to change your login processing URL to /mobile/mobile_login_processing_url. See your modified configuration:
#Configuration
#Order(1)
ublic static class MobileSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/mobile/**")
.authorizeRequests()
.anyRequest().hasRole("MOBILE")
.and()
.formLogin()
.loginPage("/mobile_login")
.loginProcessingUrl("/mobile/mobile_login_processing_url")
.usernameParameter("ssoId")
.passwordParameter("password")
.defaultSuccessUrl("/mobile/menu")
.and()
.logout()
.logoutUrl("/mobile_logout")
.logoutSuccessUrl("/mobile_login?logout")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/Access_Denied")
.defaultAuthenticationEntryPointFor(authenticationEntryPoint(), new AntPathRequestMatcher("/mobile/**"));
}
#Bean
Public AuthenticationEntryPoint authenticationEntryPoint(){
return new LoginUrlAuthenticationEntryPoint("/mobile_login");
}
}
I am using Spring boot 1.3.2 with Spring Security.
I have following configure(HttpSecurity http) method to inforce authentication
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher csrfRequestMatcher = new RequestMatcher() {
private AntPathRequestMatcher[] requestMatchers = {
new AntPathRequestMatcher("/iams/w/*")
};
#Override
public boolean matches(HttpServletRequest request) {
for (AntPathRequestMatcher rm : requestMatchers) {
if (rm.matches(request)) { return true; }
}
return false;
} // method matches
};
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
and I have following configure(WebSecurity web) method to ignore some of the urls as below;
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/myapp/docs/**",
"/myapp/docs/*",
"/myapp/docs/index.html",
"/resources/**",
"/static/**");
}
But http request to http://127.0.0.1:9000/myapp/docs/index.html still reuires username/password ( authentication ) and returns "status":401,"error":"Unauthorized"...
Actually none of the ignore url on WebSecurity is working since it also requires authentication. If I provide the auth then it works. How can I simply ignore some urls (like "/myapp/docs/**" ) here. I have following definition in the SecurityConfig class
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
What am I missing ? Please Advise.
It would probably be easier to use as simple a set of patterns as possible to leave unsecured, and then simply say that everything else IS secured.
This may be closer to what you want:
public static final String[] NOT_SECURED = {"/iams/docs/**","/static/**"};
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(NOT_SECURED);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(NOT_SECURED).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.csrf().disable();
}
There is an error order in your code.
http
.csrf()
.requireCsrfProtectionMatcher(csrfRequestMatcher)
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
Therefore, any request is needed to be authenticated. You can directly use antMatchers.
http
.authorizeRequests()
.antMatchers("/iams/w/*")
.authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.csrf().disable()
I hope it's helpful for you.
Thank you for your response but with your suggestion, my "/iams/w/*" is not protected at all. I can get to all these urls; "/iams/docs/**" , "/iams/w/" and "/iams/api/" without basic auth. Below is the set up as per your suggestion. Here I want to protect "/iams/w" and "/iams/api/" with username/password but let everyone get to "/iams/docs/*" without username/password. This is spring boot restful based implementation but want to expose some urls like docs so that it can be accessed by all and not the api calls.
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/iams/docs/**",
"/iams/docs/*",
"/iams/docs/index.html",
"/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/iams/api/**","/iams/api/v1/*")
.authenticated()
.and()
.httpBasic()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.csrf().disable();
}
My current java security config looks as follows:
#Configuration
#EnableWebSecurity
public class RootConfig extends WebSecurityConfigurerAdapter {
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("tester").password("passwd").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
When I perform a GET request using a browser, I'll get an error 403.
I would expect to get a browser popup asking me for a username / password.
What might be the problem?
UPDATE: This is fixed in Spring Security 3.2.0.RC1+
This is a bug in the Security Java Configuration that will be resolved for the next release. I have created SEC-2198 to track it. For now, a work around is to use something like the following:
#Bean
public BasicAuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
basicAuthEntryPoint.setRealmName("My Realm");
return basicAuthEntryPoint;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(entryPoint())
.and()
.authorizeUrls()
.anyRequest().authenticated()
.and()
.httpBasic();
}
PS: Thanks for giving Spring Security Java Configuration a try! Keep the feedback up :)
With Spring Security 4.2.3 and probably before you can simply use this configuration:
#Configuration
#EnableWebSecurity
public class CommonWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Autowired
public void dlcmlUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("tom").password("111").roles("USER");
}
}