I have a spring boot microservice for sso authentification using Kerberos.
before the probleme :
The authentication process starts when a user tries to access to the application (angular UI) and follow those steps:
The frontend will call the /login api (authentificaion service).
Using Kerberos and spring security, I retrieve the id_name of the user who triggers the request:
SecurityContextHolder.getContext().getAuthentication().getName().split("@")[0], this id_name is unique inside the company.I use this id_name to query the database and get all the other information about this user (no password) and I send them back to the front end who will inject them in the local storage so he/she doesn't need to connect again next time
This process was working fine until we get a change request to use JWT to secure access to all the microserviecs we have.
At this point I added the needed classes to generate, validate the token and a filter to intercept requests and check if token valid...
The probleme :
The new process is working fine except for one use case:
If first connexion request was made directly from the front end, the request gets blocked and the browser shows a connexion popup. Otherwise, if I call manually, the API from the browser or using postman I get the correct result with Token in the response header.
Note 1 : If I call manually, the /login API from the browser, I can then get connected from application like normal, I can also disconnect and connect again (with different profiles..).
Note 2 : I tried to use the old version of the Web service (before adding JWT change) and I get connected correctly (Token was in the request response)
I added some logs in the RedirectionTokenFilter class to get more information about the request, in the result I can see that the UserPrincipal is null unlike the when the request is coming from Postman.
logs :
System.out.println("UserPrincipal :" + ((HttpServletRequest) request).getUserPrincipal());
System.out.println("Auth type :" + ((HttpServletRequest) request).getAuthType());
System.out.println("Context path :" + ((HttpServletRequest) request).getContextPath());
Enumeration<String> headers = ((HttpServletRequest) request).getHeaderNames();
while(headers.hasMoreElements()){
String param = headers.nextElement();
System.out.println("HeaderName :" + param);
try{
System.out.println("Param " +param+" :" + ((HttpServletRequest) request).getHeader(param));
}catch (Exception e){
}
}
System.out.println("Method :" + ((HttpServletRequest) request).getMethod());
System.out.println("RemoteUser :" + ((HttpServletRequest) request).getRemoteUser());
System.out.println("ServletPath :" + ((HttpServletRequest) request).getServletPath());
Logs output :
UserPrincipal :null
Auth type :null
Context path :/XXXX-Auth
HeaderName :host
Param host :XXXX:9280
HeaderName :connection
Param connection :keep-alive
HeaderName :sec-ch-ua
Param sec-ch-ua :"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
HeaderName :pragma
Param pragma :no-cache
HeaderName :sec-ch-ua-mobile
Param sec-ch-ua-mobile :?0
HeaderName :authorization
Param authorization :null
HeaderName :accept
Param accept :application/json, text/plain,...
HeaderName :cache-control
Param cache-control :no-cache
HeaderName :user-agent
Param user-agent :Mozilla/5.0 (Windows NT 10.0; Win64; x64)....
HeaderName :sec-ch-ua-platform
Param sec-ch-ua-platform :"Windows"
HeaderName :expires
Param expires :Sat, 01 Jan 2000 00:00:00 GMT
HeaderName :origin
Param origin :https://XXXXXX
HeaderName :sec-fetch-site
Param sec-fetch-site :same-site
HeaderName :sec-fetch-mode
Param sec-fetch-mode :cors
HeaderName :sec-fetch-dest
Param sec-fetch-dest :empty
HeaderName :referer
Param referer :https://XXXXXX
HeaderName :accept-encoding
Param accept-encoding :gzip, deflate, br
HeaderName :accept-language
Param accept-language :en-US,en;q=0.9
Method :GET
RemoteUser :null
ServletPath :/security/login
tocken {null}
token empty trying login
context[auth]=org.springframework.security.authentication.AnonymousAuthenticationToken@274dc7e4: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: xx.xx.xx.xx; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
UserID anonymousUser
userDTO null
My spring security configuration (after adding JWT) :
@Override
protected void configure(HttpSecurity http) throws Exception {
log.info("sec configuration..");
http.csrf().disable().cors().and().authorizeRequests().antMatchers("/**").authenticated().and().httpBasic()
.authenticationEntryPoint(restSpenegoEntryPoint()).and()
.addFilterBefore(spnegoAuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(redirectionTokenFilter(), ExceptionTranslationFilter.class).addFilter(filterd)
}
@Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter() throws Exception {
SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter = new SpnegoAuthenticationProcessingFilter();
// spnegoAuthenticationProcessingFilter.setSuccessHandler(customAuthenticationSuccessHandler());
spnegoAuthenticationProcessingFilter.setAuthenticationManager(authenticationManagerBean());
return spnegoAuthenticationProcessingFilter;
}
@Bean
RedirectionTokenFilter redirectionTokenFilter() {
return new RedirectionTokenFilter();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type", "X-Requested-With"
,"Pragma","X-XSS-Protection","X-Frame-Options","X-Content-Type-Options",
"Vary","Transfer-Encoding","Server","Expires","Date",
"Access-Control-Allow-Headers","Access-Control-Allow-Credentials"
));
configuration.setExposedHeaders(Arrays.asList("Content-type", "Authorization"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
RestSpenegoEntryPoint :
public class RestSpenegoEntryPoint extends SpnegoEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Negotiate");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
AccessDeniedHandlerImpl :
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setStatus(401);
response.getWriter().flush();
}
Angular changes (I'm not a front-end dev, I don't know too much about those changes) :
app.module.ts:
@NgModule({
imports: [
providers: [
...
{ provide : HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi : true},
auth-interceptor.ts:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Authservice } from '../service/auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
setHeaders: {
'Authorization': `${Authservice.getToken()}`,
},
});
return next.handle(req);
}
}
webservice.config.ts :
import {Authservice} from '../auth/service/auth.service';
export class WebServicesConfig {
@@ -11,12 +12,14 @@
headers.append('Access-Control-Allow-Headers', 'Content-Type');
headers.append('withCredentials', 'true');
headers.append('Access-Control-Allow-Origin', '*');
headers.append('Authorization', Authservice.getToken());
return headers;
}
static getHeaders1(): Headers {
let headers: Headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', Authservice.getToken());
return headers;
}
}
Please let me know if something is not clear or if there is some missing information.
Thank you in advance.