Spring with two security configurations - failed API login redirects to form login page. How to change? - spring-boot

I have a Spring Boot application with two security configurations (two WebSecurityConfigurerAdapters), one for a REST API with "/api/**" endpoints, and one for a web front-end at all other endpoints. The security configuration is here on Github and here's some relevant parts of it:
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
jwtAuthenticationFilter.setPostOnly(true);
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
.and();
}
}
The JWTAuthenticationFilter is a custom subclass of UsernamePasswordAuthenticationFilter that processes sign-in attempts to the REST API (by HTTP POST with a JSON body to /api/login) and returns a JWT token in the "Authorization" header if successful.
So here's the issue: failed login attempts to /api/login (either with bad credentials or missing JSON body) are redirecting to the HTML login form /api/login. Non-authenticated requests to other "/api/**" endpoints result in a simple JSON response such as:
{
"timestamp": "2019-11-22T21:03:07.892+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/v1/agency"
}
{
"timestamp": "2019-11-22T21:04:46.663+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/v1/badlink"
}
Attempts to access other protected URLs (not starting with "/api/") by a non-authenticated user redirect to the login form /login, which is the desired behavior. But I don't want API calls to /api/login to redirect to that form!
How can I code the correct behavior for failed API logins? Is it a question of adding a new handler for that filter? Or maybe adding an exclusion to some behavior I've already defined?
More detail on the exception and handling:
I looked at the logs, and the exception being thrown for either bad credentials or malformed JSON is a subclass of org.springframework.security.authentication.AuthenticationException. The logs show, for example:
webapp_1 | 2019-11-25 15:30:16.048 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
(...stack trace...)
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#7f9648b6
webapp_1 | 2019-11-25 15:30:16.133 DEBUG 1 --- [nio-8080-exec-6] n.j.webapps.granite.home.HomeController : Accessing /login page.
When I access another URL, for example one that doesn't exist such as /api/x, it looks very different. It's pretty verbose but it looks like the server is trying to redirect to /error and is not finding that to be an authorized URL. Interestingly if I try this in a web browser I get the error formatted with my custom error page (error.html), but if I access it with Postman I just get a JSON message. A sample of the logs:
webapp_1 | 2019-11-25 16:07:22.157 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
webapp_1 |
webapp_1 | org.springframework.security.access.AccessDeniedException: Access is denied
...
webapp_1 | 2019-11-25 16:07:22.174 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
webapp_1 | 2019-11-25 16:07:22.175 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
...
webapp_1 | 2019-11-25 16:07:22.211 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/api/**'
...
webapp_1 | 2019-11-25 16:07:22.214 DEBUG 1 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy : /error reached end of additional filter chain; proceeding with original chain
webapp_1 | 2019-11-25 16:07:22.226 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
webapp_1 | 2019-11-25 16:07:22.230 DEBUG 1 --- [nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
webapp_1 | 2019-11-25 16:07:22.564 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
webapp_1 | 2019-11-25 16:07:22.577 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Mon Nov 25 16:07:22 GMT 2019, status=403, error=Forbidden, message=Access Denied, path=/a (truncated)...]
webapp_1 | 2019-11-25 16:07:22.903 DEBUG 1 --- [nio-8080-exec-6] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
webapp_1 | 2019-11-25 16:07:22.905 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 403
So it looks like what I maybe need to do is to configure the "authentication failure handler" for the REST API to go to "/error" instead of going to "/login", but only for endpoints under /api/**.

I added an #Override of unsuccessfulAuthentication() to my Authentication filter
The grandparent class (AbstractAuthenticationProcessingFilter) has a method for unsuccessful authentications (i.e. AuthenticationException) which delegates to an authentication failure handler class. I could have created my own custom authentication failure handler, but instead decided to simply override the unsuccessfulAuthentication method with some code that sends back a response with a 401 status and a JSON error message:
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"authentication error?\"}");
}
...and added a custom AuthenticationEntryPoint to make the other errors match
This doesn't have the exact form of the error messages I had seen at other endpoints, so I also created a custom AuthenticationEntryPoint (the class that handles unauthorized requests to protected endpoints) and it does basically the same thing. My implementation:
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"unauthorized?\"}");
}
}
Now the security configuration for the REST endpoints looks like this (note the addition of ".exceptionHandling().authenticationEntryPoint()":
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint())
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
I'll need to work on my error messages so they're more informative and secure.
And this doesn't exactly answer my original question, which was how to get those default-style JSON responses I liked, but it does allow me to customize the errors from all types of access failures independently of the web configuration. (Endpoints outside of /api/** still work with the web form login.)
Full code as of the current commit: github

Related

Why does Spring Security reject my Keycloak auth token with "No AuthenticationProvider found"?

I'm trying to figure out why my Spring Boot application is rejecting my Keycloak JWT bearer token with a "No AuthenticationProvider found" error message.
I have a few services running in a docker compose environment:
ui (angular) -> proxy (nginx) -> rest api (spring boot) -> auth service (keycloak)
The angular ui pulls the correct keycloak client from the rest service, and then authenticates without issue. I get back a JWT token, and then turn around and hand that to follow on requests to the rest api in a header Authorization: bearer [token].
In the rest API, I can see the correct bearer token come in as a header:
2022-02-11 01:01:31.411 DEBUG 13 --- [nio-8080-exec-4] o.a.coyote.http11.Http11InputBuffer : Received [GET /api/v3/accounts HTTP/1.0
X-Real-IP: 192.168.80.1
X-Forwarded-For: 192.168.80.1
Host: rest-api.mylocal.com
Connection: close
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciO...
...
2022-02-11 01:01:31.421 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.PreAuthActionsHandler : adminRequest http://rest-api.mylocal.com/api/v3/accounts
...
So the bearer token is there, and with https://jwt.io/ I can verify it's what I would expect:
{
"exp": 1644515847,
...
"iss": "http://auth-service.mylocal.com/auth/realms/LocalTestRealm",
...
"typ": "Bearer",
"azp": "LocalTestClient",
...
"allowed-origins": [
"http://web-ui.mylocal.com"
],
"realm_access": {
"roles": [
"offline_access",
"default-roles-localtestrealm",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email profile",
...
}
Processing continues by the rest api - it contacts the keycloak service and pulls the well known config:
...
2022-02-11 01:01:33.321 INFO 13 --- [nio-8080-exec-4] o.keycloak.adapters.KeycloakDeployment : Loaded URLs from http://auth-service.mylocal.com/auth/realms/LocalTestRealm/.well-known/openid-configuration
...
Finally it looks like it successfully parses the bearer token apart, grabs the user and authenticates them:
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Releasing connection [{}->http://auth-service.mylocal.com:80][null]
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Pooling connection [{}->http://auth-service.mylocal.com:80][null]; keep alive indefinitely
2022-02-11 01:01:33.521 DEBUG 13 --- [nio-8080-exec-4] o.a.h.impl.conn.tsccm.ConnPoolByRoute : Notifying no-one, there are no waiting threads
2022-02-11 01:01:33.530 DEBUG 13 --- [nio-8080-exec-4] o.k.a.rotation.JWKPublicKeyLocator : Realm public keys successfully retrieved for client LocalTestClient. New kids: [8a7dIQFASdC8BHa0mUWwZX7RBBJSeJItdmzah0Ybpcw]
2022-02-11 01:01:33.546 DEBUG 13 --- [nio-8080-exec-4] o.k.a.BearerTokenRequestAuthenticator : successful authorized
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] o.k.a.RefreshableKeycloakSecurityContext : checking whether to refresh.
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : useResourceRoleMappings
2022-02-11 01:01:33.550 TRACE 13 --- [nio-8080-exec-4] org.keycloak.adapters.AdapterUtils : Setting roles:
2022-02-11 01:01:33.555 DEBUG 13 --- [nio-8080-exec-4] a.s.a.SpringSecurityRequestAuthenticator : Completing bearer authentication. Bearer roles: []
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : User 'bf7307ca-9352-4a02-b288-0565e2b57292' invoking 'http://rest-api.mylocal.com/api/v3/accounts' on client 'LocalTestClient'
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] o.k.adapters.RequestAuthenticator : Bearer AUTHENTICATED
2022-02-11 01:01:33.556 DEBUG 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Auth outcome: AUTHENTICATED
and then immediately after that fails with the No AuthenticationProvider found error:
2022-02-11 01:01:33.559 TRACE 13 --- [nio-8080-exec-4] f.KeycloakAuthenticationProcessingFilter : Failed to process authentication request
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:234) ~[spring-security-core-5.5.1.jar!/:5.5.1]
I'm at a loss how it can say Bearer AUTHENTICATED followed by Auth outcome: AUTHENTICATED followed by No AuthenticationProvider found... I'm assuming it somehow can't convert this bearer token into a Keycloak token, even though it definitely came from my Keycloak server.
My app config:
#ComponentScan({"com.mycompany"})
#Configuration
#EnableJpaRepositories(basePackages = "com.mycompany")
#EntityScan("com.mycompany")
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class ApplicationConfiguration
extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
// These paths (comma separated) are allowed to all
.antMatchers("/api/v3/auth/config").permitAll()
.and()
.authorizeRequests()
// Everything else should be authenticated
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
// This just pulls the Keycloak config from a DB instead of the config file
return new CustomKeycloakConfigResolver();
// return new KeycloakSpringBootConfigResolver();
}
}
Missing the global config to autowire in a Keycloak auth provider:
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth)
throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider =
keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper()
);
auth.authenticationProvider(keycloakAuthenticationProvider);
}

Spring Security LDAP - Using AuthenticationManagerBuilder rejects context

I've managed to successfully use LDAP to retrieve users from an ActiveDirectory.
However, to do so I had to manually instantiate the BaseLdapPathContextSource like this:
Working Spring Security configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapProperties.getServer());
contextSource.setUserDn(ldapProperties.getUserDN());
contextSource.setPassword(ldapProperties.getPassword());
contextSource.setBase(ldapProperties.getRootDN());
contextSource.afterPropertiesSet();
// #formatter:off
auth.ldapAuthentication()
.contextSource(contextSource)
.userDnPatterns(new String[]{ ldapProperties.getUserOU() })
.userSearchBase("")
.userSearchFilter("(sAMAccountName={0})")
.ldapAuthoritiesPopulator((userData, username) ->
Collections.singleton(new SimpleGrantedAuthority("CLIENT"))
);
// #formatter:on
}
If instead of this I use the methods provided by the builder, then every login attempt gets rejected.
Not working Spring Security configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(ldapProperties.getServer())
.managerDn(ldapProperties.getUserDN())
.managerPassword(ldapProperties.getPassword())
.root(ldapProperties.getRootDN())
.and()
.userDnPatterns(new String[]{ ldapProperties.getUserOU() })
.userSearchBase("")
.userSearchFilter("(sAMAccountName={0})")
.ldapAuthoritiesPopulator((userData, username) ->
Collections.singleton(new SimpleGrantedAuthority("CLIENT"))
);
}
I've debugged the ContextSourceBuilder, and it is building a DefaultSpringSecurityContextSource too, similarly of how I am doing it.
The only difference between the manually built context and the one provided by the builder seems to be that objectPostProcessors are applied to it after beign built. However, most of them just autowire beans.
Log provided on a failed authentication (that should have succeeded):
2018-09-25 15:45:25.679 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.LdapAuthenticationProvider
2018-09-25 15:45:25.680 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.LdapAuthenticationProvider : Processing authentication request for user: mcurrao
2018-09-25 15:45:25.689 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.BindAuthenticator : Attempting to bind as ou=factory
2018-09-25 15:45:25.689 DEBUG 10784 --- [nio-8080-exec-3] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user ou=[my_ou]
2018-09-25 15:45:25.694 DEBUG 10784 --- [nio-8080-exec-3] o.s.s.l.a.BindAuthenticator : Failed to bind as OU=[my_ou]: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090400, comment: AcceptSecurityContext error, data 52e, v1db1
Note: [my_ou] is actually being replaced as my own ou. But as I don't know how sensitive this is, better not risk it.
Note 2: This log is only provided by enabling Spring debug logging. No exception is thrown nor information is given on a login attempt if said debug is not enabled.
This error codes seem to be just "Couldn't find your user" errors.
TL;DR: LDAP authentication does not work if I use the provided contextSource() builder, forcing me to instantiate a context source.

Spring boot client fails to register Spring boot admin (version 2.x)

I am running spring boot application with basic authentication enabled and spring boot admin with UI security enabled .
My spring boot client fails to register to the spring boot admin server . Below are the logs :
2018-08-23 15:17:09.676 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Created POST request for "http://localhost:9001/instances"
2018-08-23 15:17:09.699 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2018-08-23 15:17:09.724 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Writing [Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay)] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#12c0c0b3]
2018-08-23 15:17:09.864 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#5953ec3b8 pairs: {POST /instances HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{Authorization: Basic YWRtaW46YWRtaW4=}{User-Agent: Java/1.8.0_161}{Host: localhost:9001}{Connection: keep-alive}{Content-Length: 287}
2018-08-23 15:17:10.124 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#126c0bea10 pairs: {null: HTTP/1.1 403}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{X-Content-Type-Options: nosniff}{X-Frame-Options: DENY}{X-XSS-Protection: 1 ; mode=block}{Content-Type: text/plain}{Transfer-Encoding: chunked}{Date: Thu, 23 Aug 2018 09:47:10 GMT}
2018-08-23 15:17:10.128 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : POST request for "http://localhost:9001/instances" resulted in 403 (null); invoking error handler
2018-08-23 15:17:10.138 WARN 4992 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator : Failed to register application as Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay) at spring-boot-admin ([http://localhost:9001/instances]): 403 null. Further attempts are logged on DEBUG level
Below are my webSecurity config class from admin server
#SpringBootApplication
#EnableAdminServer
#EnableAutoConfiguration
#Configuration
public class SpringStarterAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringStarterAdminApplication.class, args);
}
// Added for spring boot security login ui for admin
public static class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
"/instances",
"/actuator/**"
);
// #formatter:on
}
}
Not sure what is preventing the client to register to admin Server .
The inner configuration class should be annotated with #Configuration too.
It's even better if you refactor it into a separate class.

Angular 4.0 + Spring boot + Spring Security: TemplateInputException: Error resolving template "login"

I am integrating Spring Security in my first Angular project. I followed many articles and examples including this, this, this, this and this. I want my application to have spring security and authenticate from LDAP. If I keep a login.html in \src\main\resources\templates then I can see the login page. But I want the login page to come from Angular component. So I removed/rename the login.html from \src\main\resources\templates. When I hit localhost:8080, it goes to my LoginController (yu=ou can see the sysout in the log), Then I am getting below error
018-04-28 07:30:34.610 DEBUG 11520 --- [nio-8080-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
2018-04-28 07:30:34.610 DEBUG 11520 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /login reached end of additional filter chain; proceeding with original chain
>>>>>>>>>>>> LoginController.login()...
2018-04-28 07:30:34.611 ERROR 11520 --- [nio-8080-exec-9] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-9] Exception processing template "login": Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers
2018-04-28 07:30:34.611 DEBUG 11520 --- [nio-8080-exec-9] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2018-04-28 07:30:34.611 DEBUG 11520 --- [nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2018-04-28 07:30:34.612 ERROR 11520 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers] with root cause
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:246)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1060)
I tried many ways after Googling, but no luck yet. Any suggestion please..
My Files:
LoginController.java
#RequestMapping("/login")
String login(){
System.out.println(">>>>>>>>>>>> LoginController.login()...");
return "login";
}
WebSecurityConfiguration.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//.httpBasic().and()
.authorizeRequests()
// .antMatchers("/**").permitAll()
.antMatchers("/index.html", "/", "/home", "/login").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login")
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
/*http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/resources/**", "/assets/**", "/css/**", "/js/**", "/fonts/**")
//.antMatchers("/**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(authenticationSuccessHandler)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.httpBasic();
//http.cors();
*/ }
app-routing.module.ts
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'dashboard', redirectTo: '/home', pathMatch: 'full' },
{
path: 'home',
component: DashboardComponent,
data: { title: 'Dashboard' }
},
{
path: 'linesidemonitor',
component: LinesideMonitorComponent,
data: {title: 'Lineside Monitor'}
},
If I use .and().formLogin() then I am getting this error:
2018-04-28 08:32:08.394 DEBUG 10404 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
If I use .httpBasic().and(), then I am getting browser login popup which I don't want
#SK
The reason you are getting the below exception is you are using Controller and not RestController.
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.TemplateRepository.getTemplate(TemplateRepository.java:246)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1104)
So instead of directly writing the output to browser as JSON, it is doing ViewResolver to resolve the view part of MVC.
Replacing Controller with RestController will make the .formLogin().loginPage("/login") to make the browser request with login, and as it permitted you will get the login page of angular.
httpBasic, it is the nature of browser for pop up as it gets a 401 response code.

Spring Boot with WSO2 OAuth2 - logoutSuccessUrl is not working

We are implementing the service using Spring Boot (1.2.2) with WSO2 IS (5.x) as OAuth2 Identity provider.
As part of logout we wanted to run some cleaning activities and then redirect to login page. Following is the Spring Security Configuration class
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
#Autowired
private MmaLogoutHandler logoutHandler;
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logoff", "GET"))
.logoutSuccessUrl("/#/home")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID","remember-me")
.permitAll()
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(customerAuthorizationFilter(), AbstractPreAuthenticatedProcessingFilter.class);
}
When the logout is called, the log shows that spring security has cleared the session, but it fails to redirect -
2015-09-01 14:23:05.983 DEBUG 12936 --- [nio-8443-exec-8] o.s.s.w.a.logout.LogoutFilter : Logging out user 'org.springframework.security.oauth2.provider.OAuth2Authentication#e6269aa6: Principal: mrpink_bd3d5b71-b212-11e4-ac24-22000b4791d2; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, sessionId=<SESSION>, tokenType=bearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER' and transferring to logout destination
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] o.s.s.w.a.l.SecurityContextLogoutHandler : Invalidating session: B7B50DC106F50EDA84ACFEF229DE167B
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] .s.s.w.a.l.SimpleUrlLogoutSuccessHandler : Using default Url: /#/home
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] .s.s.w.a.l.SimpleUrlLogoutSuccessHandler : Response has already been committed. Unable to redirect to /#/home
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
As per log the redirection fails and the page is not actually redirected.
Is this due to OAuth2 or there is already default log out handler with Spring boot that is needed to be overridden?

Resources