How can I define a custom Authentication provider by using Spring Security with Java Configurations?
I would like to perform a login checking credentials on my own database.
The following does what you need (CustomAuthenticationProvider is your implementation which needs to be managed by Spring)
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
/**
* Do your stuff here
*/
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
}
As shown on baeldung.com, define your authentication provider as follow:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (shouldAuthenticateAgainstThirdPartySystem(username, password)) {
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
}
and following code is corresponding java config:
#Configuration
#EnableWebSecurity
#ComponentScan("org.project.security")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
}
Related
I doing a simple poc of spring security but am running in to issues.
After extending WebSecurityConfigurerAdapter and implementing own UserDetailsService, spring security is permitting all users even without authentication. Please point the flaw in below code
...
#EnableWebSecurity
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService myUserDtlSrv;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDtlSrv);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
my implementation of UserDetailsService
...
#Service
public class MyUserDtlSrv implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("a", "a", Arrays.asList());
}
}
Does not spring security secure every url (resource by default)?
You need to specify what requests are authorized. The following fragment
authorizes authenticated (by form login) requests.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.csrf(t -> t.disable())
.authorizeRequests(t -> t.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
}
}
I am developing a spring boot 2 application.I am trying to implement spring security for that.I have used a custom AutenticationProvider for that.But it is not getting called .Spring authentication is working however. please help me to resolve this issue.I have tried many ways,but it wont worked.I am using jwt to create tokens .
WebSecurityConfigurerAdapter implementation class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private SecurmailSecurityProvider provider;
#Override
protected void configure(AuthenticationManagerBuilder authentication) throws Exception {
authentication.authenticationProvider( getKBServicesAuthenticationProvider());
}
#Bean
protected AuthenticationProvider getKBServicesAuthenticationProvider() {
return new SecurmailSecurityProvider();
}
#Bean
public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().
authenticationProvider(provider).
authorizeRequests()
.antMatchers(MessageController.URL_AUTHENTICATE).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.addFilterBefore(authenticationTokenFilterBean(), SecurityContextHolderAwareRequestFilter.class);
http.headers().frameOptions().disable();
}
Custom Authentication provider class
#Component
#Primary
public class SecurmailSecurityProvider implements AuthenticationProvider {
#Autowired
MessageClientRepository clientRepo;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.isAuthenticated()) return authentication;
SmAuthenticationToken token = (SmAuthenticationToken) authentication;
if (token.getGuid() != null && !token.getGuid().trim().isEmpty()) {
MessageClient client = clientRepo.findByGuid(token.getGuid());
if (client != null) {
return new SmAuthenticationToken(client);
}
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return (SmAuthenticationToken.class.isAssignableFrom(authentication));
}
Already you autowired the CustomAuthentication provider by using
#Autowired
private SecurmailSecurityProvider provider;
again you are creating bean and passing that CustomAuthentication provider
#Bean
protected AuthenticationProvider getKBServicesAuthenticationProvider() {
return new SecurmailSecurityProvider();
}
Instead below code
#Autowired
private SecurmailSecurityProvider provider;
#Override
protected void configure(AuthenticationManagerBuilder authentication) throws Exception {
authentication.authenticationProvider( getKBServicesAuthenticationProvider());
}
#Bean
protected AuthenticationProvider getKBServicesAuthenticationProvider() {
return new SecurmailSecurityProvider();
}
Use this code
#Autowired
private SecurmailSecurityProvider provider;
#Override
protected void configure(AuthenticationManagerBuilder authentication) throws Exception {
authentication.authenticationProvider(provider);
}
And also Custom Authentication Provider implementation should be like this below:
#Component
public class SecurmailSecurityProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (shouldAuthenticateAgainstThirdPartySystem()) {
// use the credentials
// and authenticate against the third-party system
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
I have a security configuration for my application that authenticates the user via LDAP. This works out pretty fine, but now I'd like to add another AuthenticationProvider that does some more checks on the user that tries authenticate. So I tried to add a DbAuthenticationProvider that (for testing purposes) always denies the access. So when I am trying to log in with my domain account (that works for the activeDirectoryLdapAuthenticationProvider) I am not able to access the page because the second provider fails the authentication.
To accomplish this goal, I used the following code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ad.domain}")
private String AD_DOMAIN;
#Value("${ad.url}")
private String AD_URL;
#Autowired
UserRoleComponent userRoleComponent;
#Autowired
DbAuthenticationProvider dbAuthenticationProvider;
private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
this.logger.info("Verify logging level");
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.successHandler(new CustomAuthenticationSuccessHandler()).and().httpBasic().and().logout()
.logoutUrl("/logout").invalidateHttpSession(true).deleteCookies("JSESSIONID");
http.formLogin().defaultSuccessUrl("/", true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.authenticationProvider(dbAuthenticationProvider);
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider(), dbAuthenticationProvider));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN,
AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
And this is my DbAuthenticationProvider:
#Component
public class DbAuthenticationProvider implements AuthenticationProvider {
Logger logger = LoggerFactory.getLogger(DbAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
auth.setAuthenticated(false);
this.logger.info("Got initialized");
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
}
Sadly I am able to log in (the access is not denied as I expected it to be). Did I miss out something?
Spring Won't use more than one AuthenticationProvider to authenticate the request, so the first (in the ArrayList) AuthenticationProvider that support the Authentication object and successfully authenticate the request will be the only one used. in your case it's activeDirectoryLdapAuthenticationProvider.
instead of using ActiveDirectoryLdapAuthenticationProvider, you can use a custom AuthenticationProvider that delegates to LDAP and do additional checks:
CustomerAuthenticationProvider implements AuthenticationProvider{
privtae ActiveDirectoryLdapAuthenticationProvider delegate; // add additional methods to initialize delegate during your configuration
#Override
public Authentication authenticate(Authentication auth) throws
AuthenticationException {
Authentication authentication= delegate.authenticate(auth);
additionalChecks(authentication);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
public void additionalCheck(Authentication authentication){
// throw AuthenticationException when it's not allowed
}
}
That is not how an AuthenticationProvider works, only one will be consulted for authentication. Apparently you want to combine some information from LDAP and from the DB. For this you can configure a custom UserDetailsContextMapper and/or GrantedAuthoritiesMapper. The default implementation will use the information from LDAP to contruct the UserDetails and its GrantedAuthorities however you could implement a strategy which consults the database.
Another solution is to use the LdapUserDetailsService which allows you to use the regular DaoAuthenticationProvider. The name is misleading as it actually requires an UserDetailsService. This AuthenticationProvider does additional checks using the UserDetailsChecker, which by default checks some of the properties on the UserDetails, but can be extended with your additional checks.
NOTE: The LdapUserDetailsService uses plain LDAP so I don't know if that is applicable to the slightly different Active Directory approach!
A final solution could be to create a DelegatingAuthenticationProvider which extends from AbstractUserDetailsAuthenticationProvider so that you can reuse the logic in there to utilize the UserDetailsChecker. The retrieveUser method would then delegate to the actual ActiveDirectoryLdapAuthenticationProvider to do the authentication.
NOTE: Instead of extending the AbstractUserDetailsAuthenticationProvider you could of course also create a simpler version yourself.
All in all I suspect that creating a customized UserDetailsContextMapper would be the easiest and when not found in DB throw an UsernameNotFoundException. This way the normal flow still applies and you can reuse most of the existing infrastructure.
As sample work around on multiple authentication mechanism :
find the code
#Configuration
#EnableWebSecurity
#Profile("container")
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
private AuthenticationProvider authenticationProviderDB;
#Override
#Order(1)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Order(2)
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProviderDB);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/rest/**").authenticated()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication a) throws IOException, ServletException {
//To change body of generated methods,
response.setStatus(HttpServletResponse.SC_OK);
}
})
.failureHandler(new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException ae) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
})
.loginProcessingUrl("/access/login")
.and()
.logout()
.logoutUrl("/access/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication a) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
})
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and()
.csrf()//Disabled CSRF protection
.disable();
}
}
configured two authentication providers in Spring Security
<security:authentication-manager>
<security:authentication-provider ref="AuthenticationProvider " />
<security:authentication-provider ref="dbAuthenticationProvider" />
</security:authentication-manager>
configuration which helps configure multiple authentication providers in java config.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.authenticationProvider(DBauthenticationProvider);
}
#Configuration
#EnableWebSecurity
public class XSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LDAPAuthenticationProvider authenticationProvider;
#Autowired
private DBAuthenticationProvider dbauthenticationProvider;
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.authenticationProvider(dbauthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/","/logout").permitAll()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/index")
.loginProcessingUrl("/perform_login")
.usernameParameter("user")
.passwordParameter("password")
.failureUrl("/index?failed=true")
.defaultSuccessUrl("/test",true)
.permitAll()
.and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/index?logout=true").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/error");
}
}
objectPostProcessor inside the configure method need AuthenticationManagerBuilder to actually build the object before we can access and change the order of the providers
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(new CustomAuthenticationProvider(this.dataSource));
auth.objectPostProcessor(new ObjectPostProcessor<Object>() {
#Override
public <O> O postProcess(O object) {
ProviderManager providerManager = (ProviderManager) object;
Collections.swap(providerManager.getProviders(), 0, 1);
return object;
}
});
}
We have developed a SSO application in spring boot using OAuth [LDAP/ Social Login]. Application has some public pages and some protected pages. The requirement is we want some contents on public pages based on authenticated users. The issue is once we login in one application and visit the public page of other application, the user principal is not available on public pages, unless we visit one of the protected pages of same application. Any suggestion/ sample to achieve this, would be helpful here. Below is my code for resource server.
public class SocialApplication extends WebSecurityConfigurerAdapter {
#Autowired
CustomLdapAuthoritiesPopulator ldapAuthoritiesPopulator;
#Autowired
CustomLogoutSuccessHandler customLogoutSuccessHandler;
#RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/page1Prv");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/login", "/index**", "/webjars/**").permitAll()
.anyRequest().authenticated().and().authorizeRequests().anyRequest().fullyAuthenticated().and()
.formLogin().loginPage("/login").defaultSuccessUrl("/").and().logout()
.logoutSuccessHandler(customLogoutSuccessHandler).permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
//.userDetailsService(userDetailsService);
http.httpBasic().and().authorizeRequests().anyRequest().authenticated().and().csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.ldapAuthentication()
.contextSource()
.url("ldap://localhost:10389/dc=example,dc=com").managerDn("uid=admin,ou=system")
.managerPassword("secret").and()
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator)
.userSearchBase("ou=users").userSearchFilter("(cn={0})");
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/me").authorizeRequests().anyRequest().authenticated();
}
}
public static void main(String[] args) {
SpringApplication.run(SocialApplication.class, args);
}
}
I returned the Principal object from user(). see below code, and it solved my problem.
#RequestMapping({ "/user", "/me" })
public Principal user(Principal principal) {
return principal;
}
Browser Response for localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com is 302 Found, but response for localhost:9999/uaa/login is 401 Unauthorized.
I could get the login token prior to adding the #EnableResourceServer. I am using Spring boot and extending WebSecurityConfigurerAdapter to use authentication Manager with data source. When I tried to add a ResourceServerConfigurerAdapter it wouldn't build. What is the easiest way to allow the login page?
#SpringBootApplication
#RestController
#EnableResourceServer
public class OAuthSvcApplication extends WebMvcConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(OAuthSvcApplication.class);
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
public static void main(String[] args) {
SpringApplication.run(OAuthSvcApplication.class, args);
}
}
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource, Environment env)
throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.checkTokenAccess("hasAuthority('USER')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code",
"refresh_token", "password").scopes("openid");
}
}
}
SpringSecurityFilterChain should always be ordered before other filters.
If you want to add your own authentication for all or some endpoints the best thing to do is add your own WebSecurityConfigurerAdapter with lower order. Modifying the WebSecurityConfigurerAdapter subclass as follows allows the ResourceServer to work with a jdbc authentication mgr:
#Configuration
#Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager).jdbcAuthentication().dataSource(dataSource);
}
}