Keycloak spring adapter - check that the authToken is active with every http request - spring-boot

Problem I want to solve:
For every call made to the service I want to check that the token is active, if it isn't active I want to redirect the user to the login page.
Current setup: Grails 3.2.9 , Keycloak 3.4.3
Ideas so far:
This article looked promising: https://www.linkedin.com/pulse/json-web-token-jwt-spring-security-real-world-example-boris-trivic
In my security config I added a token filter
#Bean
public TokenAuthenticationFilter authenticationTokenFilter() throws Exception {
return new TokenAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure http
http
.addFilterBefore(authenticationTokenFilter(), BasicAuthenticationFilter.class)
.logout()
.logoutSuccessUrl("/sso/login") // Override Keycloak's default '/'
.and()
.authorizeRequests()
.antMatchers("/assets/*").permitAll()
.anyRequest().hasAnyAuthority("ROLE_ADMIN")
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
My TokenAuthenticationFilter just prints out the request headers at the moment :
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private String getToken( HttpServletRequest request ) {
Enumeration headerEnumeration = request.getHeaderNames();
while (headerEnumeration.hasMoreElements()) {
println "${ headerEnumeration.nextElement()}"
}
return null;
}
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String authToken = getToken( request );
}
}
Which returns:
host
user-agent
accept
accept-language
accept-encoding
cookie
connection
upgrade-insecure-requests
cache-control
The code/logic I want to implement in the filter is something like:
KeycloakAuthenticationToken token = SecurityContextHolder.context?.authentication
RefreshableKeycloakSecurityContext context = token.getCredentials()
if(!context.isActive()){
// send the user to the login page
}
However I'm lost as to how to get there.
Any help greatly appreciated

As far as I understand, your question is about "how to check the token is active?" and not "how to redirect the user to login page?".
As I see you added the tag "spring-boot" and "keycloak" maybe you could use "Keycloak Spring Boot Adapter". Assuming you use the version 3.4 of Keycloak (v4.0 still in beta version), you can found some documentation here.
If you can't (or don't want to) use Spring Boot Adapter, here is the part of the KeycloakSecurityContextRequestFilter source code that could be interesting for your case:
KeycloakSecurityContext keycloakSecurityContext = getKeycloakPrincipal();
if (keycloakSecurityContext instanceof RefreshableKeycloakSecurityContext) {
RefreshableKeycloakSecurityContext refreshableSecurityContext = (RefreshableKeycloakSecurityContext) keycloakSecurityContext;
if (refreshableSecurityContext.isActive()) {
...
} else {
...
}
}
and here is the (Java) source code of the getKeycloakPrincipal method:
private KeycloakSecurityContext getKeycloakPrincipal() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof KeycloakPrincipal) {
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
}
}
return null;
}
And if you want to understand how the Authentication is set in the SecurityContextHolder, please read this piece of (Java) code from KeycloakAuthenticationProcessingFilter:
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (authResult instanceof KeycloakAuthenticationToken && ((KeycloakAuthenticationToken) authResult).isInteractive()) {
super.successfulAuthentication(request, response, chain, authResult);
return;
}
...
SecurityContextHolder.getContext().setAuthentication(authResult);
...
try {
chain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
As an alternative you could also check this github repository of dynamind:
https://github.com/dynamind/grails3-spring-security-keycloak-minimal
Hoping that can help.
Best regards,
Jocker.

Related

JWT Authentication causes conflicts with regular authentication. I can't log in from the login page

I'm a newbie when talking about Spring Security, specially with JWT and CORS, so I apologise in advance if I don't speak clearly about the matter.
We were asked to make an application which simulates a private clinic website, on which patients can make an appointment with a doctor and buy products from the pharmacy. Doctors can introduce information in the database about their patients. Our project has a Restful API as well, which can be accessed through a mobile app (or Postman). What the API does is showing a list of products we have stored in the database.
All users can log in through a log in form, which uses Spring Security. On the other hand, if we wanted to retrieve the information of our API, CORS and JWT are used in addition to Spring Security.
The problem comes when I set up a custom authorization filter our teacher gave us to do this (I have commented the line that does this). We can access our API using Postman perfectly: we log in with the admin user and pass the authorization token to our API route, and in return we get the list of products. But when the filter is working, we can no longer use the log in form of our website to authenticate. The whole proccess goes like this:
The application starts at the main page (localhost:8080/inicio).
In the main page there is a 'Login' button which appears when the user is not authenticated . Clicking it takes us to the log in form.
Once in the log in form (localhost:8080/auth/login) we fill all the fields neccesary for us to log in as an user from the database (in this case, username: admin, password: admin).
We submit the form, which takes us to the petition in charge of the authentication proccess (localhost:8080/login/login-post).
At the end of the proccess, we are redirected back to the main page. The "Login" button should appear as "Logout" when the user is authenticated. But it doesn't. We cannot navigate to other pages the authenticated user should have access to neither.
No error messages are provided by the console, and all it does is taking me back to the main page without having the user authenticated.
This is my Spring Security configuration class:
#Autowired
#Qualifier("userService")
private UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// .addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "/css/**", "/img/**", "/js/**", "/vendor/**", "/inicio/**", "/pacientes/altaPaciente/**", "/pacientes/addPaciente/**", "/auth/**").permitAll()
.antMatchers(HttpMethod.GET, "/authRest/**").permitAll()
.antMatchers(HttpMethod.POST, "/authRest/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.defaultSuccessUrl("/inicio/", true)
.loginProcessingUrl("/auth/login-post")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/auth/login?logout")
.permitAll();
}
And my JWT Authorization filter:
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "mySecretKey";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
if (checkJWTToken(request, response)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
} else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
} catch(ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
}
private Claims validateToken(HttpServletRequest request) {
String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
return Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(jwtToken).getBody();
}
private void setUpSpringAuthentication(Claims claims) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
claims.getSubject(),
null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
String authenticationHeader = request.getHeader(HEADER);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX)) {
return false;
}
return true;
}
EDIT: As requested, here are the logs I get when I try to log in as an existing user in the database using the web form: https://pastebin.com/7SYX2MZF
the fault is probably (after discussion)
somewhere here:
if (checkJWTToken(request, response)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
} else {
SecurityContextHolder.clearContext();
}
a check is done in checkJWTToken for the presence of a Authorization header and if there is none, the current SecurityContext is cleared, meaning it will remove whatever principal present.
This removes whomever is previously logged in, which in turn the principal that is constructed when logging in initially.
So you login, the securitycontext is populated by the principal, then it's suddenly removed in the next filter.

Spring 5 Cors and Csrf integration for angular js frontend http 401 [duplicate]

This question already has answers here:
CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource
(8 answers)
Closed 3 years ago.
I am trying to execute requests from angular js frontend to spring boot middle ware (spring boot 2.1.4) . The setup used to work as expected before I migrated the app to spring boot.
Post spring boot migration all the filter and security config from web XML has been configured in the form of annotated classes.
Now my requests from UI are getting rejected by spring boot with http 401 error with cors policy (Allowed-Origin)
My current project setup looks like this
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/**").hasAnyRole("ROLE_USER").anyRequest()
.authenticated().and().csrf().csrfTokenRepository(csrfTokenRepository());
}
private CsrfTokenRepository csrfTokenRepository() {
CustomDomainCookieCsrfTokenRepository repository = new CustomDomainCookieCsrfTokenRepository();
repository.setCookieHttpOnly(false);
return repository;
}
}
#WebFilter("/*")
public class ForceCORSFilter extends OncePerRequestFilter {
protected final Logger log = Logger.getLogger(this.getClass());
private CacheService cacheService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
List<String> originList = getCacheService().getValidOriginUrI();
String clientOrigin = request.getHeader("origin");
if (clientOrigin == null) {
// process the request even if origin is null
processValidRequest(request, response, filterChain, clientOrigin);
}
if (clientOrigin != null) {
// Origin should be validated if not null
if (originList.contains(clientOrigin)) {
processValidRequest(request, response, filterChain, clientOrigin);
} else {
log.info("####################### ORIGIN IS INVALID #######################" + clientOrigin);
filterChain.doFilter(request, response);
}
}
} catch (Exception e) {
response.getWriter()
.write("An error has occured while processing the request. Please retry with proper request.");
log.info("An error has occured in the request " + e.getMessage());
}
}
private void processValidRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain,
String clientOriginAllowed) throws IOException, ServletException {
response.addHeader("Access-Control-Allow-Origin", clientOriginAllowed);
response.addHeader("Access-Control-Allow-Credentials", "true");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, HEAD");
response.addHeader("Access-Control-Allow-Headers",
"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers,Authorization, X-XSRF-TOKEN");
} else {
filterChain.doFilter(request, response);
}
}
public CacheService getCacheService() {
return cacheService;
}
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
}
Can someone point out what is wrong here. Why I am still getting
http 401 "No 'Access-Control-Allow-Origin' header is present on the
requested resource" errors.
One issue might be precedence -- your filter isn't run at the right order. You can use #Order(Ordered.HIGHEST_PRECEDENCE) so it is run before the Spring Security filters.
Having said that, Spring has first-class support for CORS already, so there is no need to tediously define a filter at all. See the documentation and an example.

How to enable CORS for Error Response in Spring MVC?

I'm working on application where I use Spring MVC for the Back-end and Angular5 for the Front-end. I have been stuck with implementation of Auth2 security layer including Cross-Origin Resource Sharing. My CORS filter implementation looks like this:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
#WebFilter("/*")
public class WebSecurityCorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, res);
}
}
#Override
public void destroy() {
}
}
I works almost properly, I'm able to obtain access_token and use it to get protected data from ResourcesServer:
{"access_token":"4fcef1f8-4306-4047-9d4d-1c3cf74ecc44","token_type":"bearer","refresh_token":"397016eb-dfb0-4944-a2e0-50c3bd07c250","expires_in":29,"scope":"read
write trust"}
Browser console screenshot
The problem starts when I try to handle the request using expired token. In such case I'm not able to catch the correct ErrorResponeCode by Angular. Instead of 401 i Angular HttpClient got "Unknown Error" with status:0.
It looks like the problem is with CORS policy where the ErrorResponse doesn't include neccessery headers like Access-Control-Allow-Origin (...)
Failed to load http://localhost:8030/api/xxxx: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:8070' is therefore not allowed
access. The response had HTTP status code 401.
ErrorResponse Headers - Screenshot
I have searched for how to enable CORS for ErorrResponse (InvalidTokenException etc.) in Spring MVC . I tried with various approach: accessDeniedHandler and setExceptionTranslator but without success. I really made effort to find the solution myself but I'm a beginner in Spring. I am not sure if this is possible at all.
ANGULAR (UPDATE)
#hrdkisback, it's rather not angular issue, anyway this my code :
#Injectable()
export class HttpInterceptorService implements HttpInterceptor {
addToken(req: HttpRequest<any>, oauthService: AuthenticationService): HttpRequest<any> {
if(oauthService.isTokenExist()){
return req.clone({ setHeaders: { Authorization: 'Bearer ' + oauthService.getAccessToken() }})
}
return req;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
let oauthService = this.inj.get(AuthenticationService);
return next.handle(this.addToken(req,oauthService))
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// process successful responses here
}
}, (error: any) => {
if (error instanceof HttpErrorResponse) {
// Error
console.log(error);
}
});
}
}
Issue solved after I added my CORS filter on ResourcesServer configuration level like this:
The correct configuration that works for me!
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new WebSecurityCorsFilter(), CsrfFilter.class)
...
}
....
}
In my previous configuration I added the filter in the same way but on the top level of MVC Security Configuration and it was the root couse of my issue:
The previous configuration that caused my issue
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new WebSecurityCorsFilter(), CsrfFilter.class)
...
}
....
}
I faced the same problem..I was trying Basic Auth with Angular 5.
The problem is that you don't add the CORS header on error response.
Here is what I did
#Component
public class AuthEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
response.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
}
That would do the trick!

Spring Security Filter is not being triggered by configuration

I am configuring the security of my REST and I don't know how I can secure my methods, however allowing a filter to trigger to set my Authority
http
.authorizeRequests()
.antMatchers(PERSISTENCE_SERVICE_URL)
.hasAuthority(AUTHORITY_PERSISTENCE_SERVICE)
.and()
.csrf()
.disable();
And in my Filter which extends OncePerRequestFilter does something like this
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
this.authenticationImpl.init();
String jwt = request.getHeader("jwt");
String refresh = request.getHeader("refresh");
if(jwt != null) {
this.jwtPropertyExtractor.commitJwt(jwt, refresh);
String jwtId = this.jwtPropertyExtractor.getIdentityId();
String securityRole = this.jwtPropertyExtractor.getSecurityRole();
this.authenticationImpl.setIdentityId(jwtId);
this.authenticationImpl.updateSecurityRole(securityRole);
SecurityContextHolder.getContext().setAuthentication(this.authenticationImpl);
}
filterChain.doFilter(request, response); }
So when I place .hasAuthority(AUTHORITY_PERSISTENCE_SERVICE) in my configuration, my filter is not even being triggered but i need him to set my authentication.
Ok i added
.addFilterBefore(jwtAuthorisationFilter(),
BasicAuthenticationFilter.class);
}
#Bean
public JwtAuthorisationFilter jwtAuthorisationFilter() {
return new JwtAuthorisationFilter(jwtExtractor(),
authenticationImpl());
}
Now its working fine thx for that hint :>

SSO with Spring security

I have an application, where user is pre-authorized by SSO and lands to my page, now I need to make a call to another rest api to get some data, which is running on another server, but it will be use the same authentication. So I just wanted to know, how I can provide the authentication process? Do I need to set the cookie what I am getting from the incoming request.
When the request lands on your page it should have a token or key, in the http AUTHORIZATION header, this should be used with a filter
public class AuthFilter extends OncePerRequestFilter {
private String failureUrl;
private SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
// check your SSO token here
chain.doFilter(request, response);
} catch (OnlineDriverEnquiryException ode) {
failureHandler.setDefaultFailureUrl(failureUrl);
failureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Captcha invalid!"));
}
}
public String getFailureUrl() {
return failureUrl;
}
public void setFailureUrl(String failureUrl) {
this.failureUrl = failureUrl;
}
}
Also read this post on how to set up the auto config. Spring security without form login

Resources