In this tutorial we will learn how to use Spring boot and SPNEGO API to implement the kerberos or integrated authentication. Kerberos is developed by Massachusetts Institute of Technology (MIT) which is used to authenticate between trusted services using KDC tickets. For example email client, HR portal or employee portal in a corporate network where employee doesn't need to provide their user id & password to access these application in the same domain. Once they are logged in to their machine, they can access such services/application with kerberos authentication.
I am using MAC OS X to demonstrate the kerberos authentication. I have used the same machine to configure and run the application backed with kerberos authentication and my KDC database & application reside on the same machine.
Setting up Kerberos in Mac OS X
In logs or console, your will see similar output as given below where it prints the logged in user.
https://github.com/thetechnojournals/spring-tutorials/tree/master/KerberosAuthTutorial
I am using MAC OS X to demonstrate the kerberos authentication. I have used the same machine to configure and run the application backed with kerberos authentication and my KDC database & application reside on the same machine.
Setting up kerberos
First of all we need to setup kerberos where we will configure the database, create user principals, create policies and keytabs. We need to create our application user and application domain which are used by application to retrieve the authenticated user details from trusted KDC. Please refer below link on how to configure Kerberos on MAC OS X for complete details and here we will focus on our application which will use kerberos authentication but first of all we need to configure the kerberos.Setting up Kerberos in Mac OS X
Spring boot application using SPNEGO API
We will create a web application using Spring boot and use SPNEGO API for integrated/ kerberos authentication. Below is the project structure.Maven dependencies
Spring security and kerberos related dependencies:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.kerberos</groupId> <artifactId>spring-security-kerberos-web</artifactId> <version>1.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security.kerberos</groupId> <artifactId>spring-security-kerberos-core</artifactId> <version>1.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security.kerberos</groupId> <artifactId>spring-security-kerberos-client</artifactId> <version>1.0.1.RELEASE</version> </dependency>Spring boot web dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <scope>provided</scope> </dependency>
Application properties (application.yml)
Below is the complete code of properties file. Here we need to configure two things for kerberos, one is user principal and another is keytab file location. Here "myserver.localhost" is our application domain.server:
port: 8080
app:
service-principal: HTTP/myserver.localhost@MYSERVER.LOCALHOST
keytab-location: file:/Users/macuser/krb5/conf/krb5.keytab
logging.level.org.springframework.security: TRACE
spring.mvc.view.prefix: /WEB-INF/pages/
spring.mvc.view.suffix: .html
server.servlet-path: /
KerberosAuthTutorialApplication.java (Main class)
@SpringBootApplication @EnableWebMvc public class KerberosAuthTutorialApplication extends SpringBootServletInitializer{ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(KerberosAuthTutorialApplication.class); } public static void main(String[] args) { SpringApplication.run(KerberosAuthTutorialApplication.class, args); } }
WebSecurityConfig.java (Security configurations)
Class and properties declaration:@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Value("${app.service-principal}") private String servicePrincipal; @Value("${app.keytab-location}") private String keytabLocation;Defining View resolver:
@Bean public ViewResolver getViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/pages/"); resolver.setSuffix(".jsp"); return resolver; }Spring security configuration:
@Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore( spnegoAuthenticationProcessingFilter(authenticationManagerBean()), BasicAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(spnegoEntryPoint()) .and() .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/auth/login").permitAll() .and() .logout() .permitAll(); }SPNEGO and kerberos security bean configurations:
@Bean public SpnegoEntryPoint spnegoEntryPoint() { return new SpnegoEntryPoint("/auth/login"); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(kerberosAuthenticationProvider()) .authenticationProvider(kerberosServiceAuthenticationProvider()) .userDetailsService(kerbUserDetailsService()); } @Bean public KerberosAuthenticationProvider kerberosAuthenticationProvider() { KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider(); SunJaasKerberosClient client = new SunJaasKerberosClient(); client.setDebug(true); provider.setKerberosClient(client); provider.setUserDetailsService(kerbUserDetailsService()); return provider; } @Bean public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter( AuthenticationManager authenticationManager) { SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter(); filter.setAuthenticationManager(authenticationManager); return filter; } @Bean public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider()throws MalformedURLException { KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider(); provider.setTicketValidator(sunJaasKerberosTicketValidator()); provider.setUserDetailsService(kerbUserDetailsService()); return provider; } @Bean public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator()throws MalformedURLException { SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator(); ticketValidator.setServicePrincipal(servicePrincipal); ticketValidator.setKeyTabLocation(new UrlResource(keytabLocation)); ticketValidator.setDebug(true); return ticketValidator; } @Bean public UserDetailsService kerbUserDetailsService() { return (username)->{ return new User(username, "notUsed", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_USER")); }; } }
SampleRestController.java (REST service)
@RestController @RequestMapping("/rest") public class SampleRestController { @GetMapping("/hello") public String sayHello(HttpServletRequest req){ System.out.println("User: "+req.getRemoteUser()); return "Hello, you are welcome!!!"; } }
SpringController.java (Login page endpoint configuration)
@Controller @RequestMapping("/auth") public class SpringController { @RequestMapping("/login") public String login(){ return "loginpage"; } }
loginpage.jsp (Spring login page UI)
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Kerberos Example</title> </head> <body style="text-align:center"> <form action="/login" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
Running application
Now in your project root execute below maven command to run the application.spring-boot:runOnce application is started, you can see below statement in console or log.
2019-12-30 21:11:31.641 DEBUG 6716 --- [ main] w.a.SpnegoAuthenticationProcessingFilter : Filter 'spnegoAuthenticationProcessingFilter' configured for use Debug is true storeKey true useTicketCache false useKeyTab true doNotPrompt true ticketCache is null isInitiator false KeyTab is /Users/macuser/krb5/conf/krb5.keytab refreshKrb5Config is false principal is HTTP/myserver.localhost@MYSERVER.LOCALHOST tryFirstPass is false useFirstPass is false storePass is false clearPass is false principal is HTTP/myserver.localhost@MYSERVER.LOCALHOST Will use keytab Commit SucceededNow open Safari browser in your Mac machine and open URL http://myserver.localhost:8080/rest/hello. You will see below output in browser without providing any credentials to it.
In logs or console, your will see similar output as given below where it prints the logged in user.
User: macuser@MYSERVER.LOCALHOST
GIT Source code
Complete source code is available at below GIT hub URL.https://github.com/thetechnojournals/spring-tutorials/tree/master/KerberosAuthTutorial
I couldn't able to make it work. Always prompting login screen. Below is the log entry.
ReplyDelete2021-04-19 06:19:55.000 DEBUG 1601 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /rest/hello at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2021-04-19 06:19:55.000 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@21cb9a2d: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@1de6: RemoteIpAddress: 192.168.1.2; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
2021-04-19 06:19:55.001 DEBUG 1601 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /rest/hello at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2021-04-19 06:19:55.001 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.session.SessionManagementFilter : Requested session ID 6F99288950CD0B6104A69A374FCA6116 is invalid.
2021-04-19 06:19:55.001 DEBUG 1601 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /rest/hello at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2021-04-19 06:19:55.001 DEBUG 1601 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /rest/hello at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2021-04-19 06:19:55.001 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /rest/hello' doesn't match 'POST /logout'
2021-04-19 06:19:55.002 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/rest/hello'; against '/'
2021-04-19 06:19:55.002 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/rest/hello'; against '/home'
2021-04-19 06:19:55.002 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /rest/hello; Attributes: [authenticated]
2021-04-19 06:19:55.002 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@21cb9a2d: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@1de6: RemoteIpAddress: 192.168.1.2; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2021-04-19 06:19:55.052 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6d3f9317, returned: -1
2021-04-19 06:19:55.056 DEBUG 1601 --- [nio-8080-exec-1] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
you can use the command as "curl --negoctiate -u : http://myserver.localhost:8080/rest/hello" then It will work as well
DeleteIf you use Chrome then please open chrome with parameter: —auth-server-whitelist=“*tchlabs.net”
DeleteHi Rajiv,
ReplyDeleteI get the exact same issue using the KerberosRestTemplate, but when I use curl with --negotiate it works well. still trying to find the reason for this, let me know if you were able to solve the above error
This comment has been removed by the author.
ReplyDeleteHi, I do like guideline, I go to http://myserver.localhost:8080/rest/hello and I always get errors as: Access is denied (user is anonymous); redirecting to authentication entry point.
ReplyDeleteWho know the solution to solve this issue please let me know
Thanks so much for your help
Advanced Topics in Web Designing at APTRON offer a comprehensive exploration of the ever-evolving field of web development. A solid grasp of advanced web design techniques is essential in today's digital landscape.
ReplyDelete