I have a small test Spring Boot application that includes a controller that contains a simple service. The service is just a test service that causes the browser to display some HTML, as shown below:
#RestController
public class TestController
{
#GetMapping("/testserv")
public String getUserInformation()
{
return("<p>User authenticated!</p>");
}
}
I also have a configuration class designed to require a user to authenticate in order to access the service. The intent is to make all other services in the application accessible without logging in, while accessing /testserv requires login:
#Configuration
#EnableWebSecurity
public class TestSecure extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity httpSecure) throws Exception
{
httpSecure.authorizeRequests()
.antMatchers("/testserv").authenticated()
.anyRequest().permitAll();
}
}
If I run my application without TestSecure class, I can access the /testserv service without problems. However, when I put in the TestSecure class, I get 404 Not Found failures trying to access the service.
I did some more testing. I found that what seems to cause the Not Found failures is the line:
.antMatchers("/testserv").authenticated()
If I comment out that line I can access /testserv. With that line in there, the application suddenly cannot find the /testserv service and generates the 404 Not Found failures.
I believe I have done everything required in order for this to work, but perhaps I am missing something? Can anyone tell me why using antMatchers() would cause a service to be not found? How can I get proper access to this service?
The issue is that you are missing #RequestMapping for the controller. I think just adding that annotation should get your code working.
Updated based on OP comment
You should also use .formLogin() in order to show the login form
Related
I know that there are already similar questions, here and here, regarding this problem but every solution proposed failed to help me. There is also mention to this library in most of those answers but (with all due respect) I would like to avoid depending on an external library just to be able to test a simple controller.
So, I have a very simple api that is accessed using a bearer token generated by keycloak and I would like to test the controller. Something along these lines:
#Test
#DisplayName("Should be ok")
#WithMockUser
void whenCalled_shouldBeOk() throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
mockMvc.perform(
post("/api/url/something")
.content("{}")
.contentType(APPLICATION_JSON)
.with(authentication(authentication))
).andExpect(status().isOk());
}
The problem is that I will always get a null pointer exception thrown by the KeycloakDeploymentBuilder for it's missing the adapter config. In our SecurityConfig we extend the KeycloakWebSecurityConfigurerAdapter and do all the required configurations for the app to work but I am failing to mock/by-pass this process in the test. Normally I find my way around this authentication problems in the tests (when keycloak isn't used) with #WithMockUser annotation but not this time.
Isn't there way to mock the adapter or the filter process in order to by-pass this issue?
I have tried everything that was answered in the other questions (except the library) with no luck. If you have any clue that could be of help or at least point me in the correct direction (since this can be due to a lack of knowledge on spring security from my part) that would very much appreciated.
2023 update
The deprecated Keycloak adapters for Spring (where KeycloakAuthenticationToken is defined) are not compatible with spring-boot 3. Alternatives in the accepted answer to "Use Keycloak Spring Adapter with Spring Boot 3"
Original answer
As I already wrote in my answer to the first question you linked, #WithMockUser populates the security context with a UsernamePaswordAuthenticationToken when your code / conf probably expect a KeycloakAuthenticationToken.
If you read carefully the same answer, you'll find an alternative to using my lib to do this: manually populate the security-context with a KeycloakAuthenticationToken instance or mock in each test.
Minimal sample with Mockito I added to my repo:
#Test
public void test() {
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var account = mock(OidcKeycloakAccount.class);
when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
when(account.getPrincipal()).thenReturn(principal);
final var authentication = mock(KeycloakAuthenticationToken.class);
when(authentication.getAccount()).thenReturn(account);
// post(...).with(authentication(authentication))
// limits to testing secured #Controller with MockMvc
// I prefer to set security context directly instead:
SecurityContextHolder.getContext().setAuthentication(authentication);
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
Maybe, after you mesure how much this clutters your tests (there are very few claims set here) you'll reconsider using my lib (or copying from it as it's opensource).
With my annotation, above sample becomes:
#Test
#WithMockKeycloakAuth
public void test() throws Exception {
//TODO: invoque mockmvc to test #Controller or test any other type of #Component as usual
}
Regarding spring test config with Keycloak involved, you could dig a bit into the tests of spring-addons-keycloak module. You'd find a complete app using KeycloakAuthenticationToken with unit tests (and working test conf).
Last (and maybe off-topic) you could read the repo main README and consider using a more generic OIDC implementation than Keycloak's one. I provide one, along with test annotation, and wrote tutorials on how to extend it to your app specific needs.
How to handle runtime exceptions thrown by Spring Security Authentication Providers ? I'm using Spring Boot 1.4.2 but I feel like this applies also to classic Spring applications.
Let's say I have an ActiveDirectoryLdapAuthenticationProvider configured to authenticate users against my corporate AD. It all works nice with Spring Security when the authentication fails due to bad credentials (an AuthenticationException is thrown which is properly handled by the Spring Security mechanism = the app goes back to the login screen and the authentication error can be shown).
However it so happens that sometimes, the AD is down temporarily and a org.springframework.ldap.CommunicationException is thrown instead. This exception is a runtime exception and therefore isn't trapped by Spring's security mechanism because it doesn't extend AuthenticationException.
What happens in this case, is that the app is redirected to the default error page (which is /error). What I want to do, is to still show the login screen with a custom message.
I've found that I can do that, if I create something like
public class ActiveDirectoryLdapExtendedAuthenticationProvider implements AuthenticationProvider {
private final ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider;
public ActiveDirectoryLdapExtendedAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider adAuthenticationProvider) {
this.adAuthenticationProvider = adAuthenticationProvider;
}
#Override
public Authentication authenticate(Authentication a) throws AuthenticationException {
Authentication auth = null;
try {
auth = adAuthenticationProvider.authenticate(a);
}
catch(CommunicationException communicationException) {
throw new AuthenticationServiceException("Could not reach User Directory. Please try again in a few minutes");
}
return auth;
}
This works but I feel that there must be a better way.
I've tried creating a ControllerAdvice annotated class but it doesn't get called by the POST to login by Spring Security. I would imagine that it's because POST is handled by Spring Security filters, which being Servlet filters, sit above the main Spring MVC dispatcher servlet.
I've also tried to create a SimpleMappingExceptionResolver to handle a CommunicationException and redirect to the login page, but that doesn't work either as my SimpleMappingExceptionResolver doesn't get called either.
The other workaround that I've come up with is to tackle the exception at the error page itself, something like (using Thymeleaf)
<div class="container error" th:switch="${exception}">
<span th:case="'org.springframework.ldap.CommunicationException'">Error communicating with User Directory. Please try again in a few minutes</span>
<span th:case="*">An unexpected error has occurred</span>
</div>
I still feel like there should be a better way. How can I configure the DispatcherServlet to ensure that a CommunicationException is to be redirected to the /login controller, and not the error page ? Or more generically... how can I configure that any exception at the login stage, is shown on the login screen ?
Experienced same problem and solved it creating custom Spring AuthenticationFailureHandler. It caught error by throwing BadCredentialsException in custom AuthenticationProvider class.
I'm using Spring Boot and I want my app to host Oauth2 resource server for accessing my api endpoints on the same server. I also need to have a web interface with secured pages via form login.
For example I have api endpoints /api/v1/** where requests can only be made via having a token from my oauth2 resource server.
Additionally there are endpoints like /account/** where user needs to be logged in via form.
All of this needs to be in one Spring Boot instance for now.
My WebSecurityConfig file:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/account/**").authenticated()
.and()
.httpBasic();
}
}
And in my Oauth2SecurityConfig I have:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/v1/me/**").authenticated();
}
}
The problem is, oauth2 config seems to override the first configuration and all my webpage resources are exposed without asking username password in form login. And if I try accessing my api endpoints I get the expected oauth error response.
Do I need to have them both in one overriden method? Do I need to have 2 instances of HttpSecurity? How can I solve this?
I had faced the same situation. Finally got a solution for this. You just need to make the use of #Order annotation.
Add this to your WebSecurityConfig class
#Order(1)
And add this to your ResourceServerConfiguration class
#Order(2)
To secure all your resources you would have to add
.authorizeRequests().anyRequest().authenticated(). Did you mean that your "/account/**" resource is exposed?
Those two HttpSecurity objects are not the same object. That being said you don't need to have both of them configured, but each one of them serves different purpose. (I have both of them configured in my project and it's working fine)
I would start by reviewing your expectations for this as I am not sure whether it is feasible to have a part of your server protected by OAuth and another part by another authentication mechanism (Form login). Both would be considered .authenticated(). You could manage the access by roles and oauth scopes, but then you would have to also provide both login/logout mechanisms by different filters and maybe another filter to provide persisting of both authentications when navigating through your server. Seems to me like a lot of not so standard work and I would think about different solutions.
I pulled in the example Java configuration project for Spring's SAML extension. No commits seem to have been made to the project for about six months as of my writing this question. I have not done anything to this project except for run maven package against it.
I then run the application in Spring Tool Suite as a Spring Boot Application and the application runs; however, the application does not run without error and the application endpoint is inaccessible (resulting in am error message): "ERROR: Something went wrong in the authentication process".
I haven't registered any certificates, etc (and may very well need to). There are no instructions provided with the GitHub project for starting or working with the application. I have intentionally not posted the guts of the project as I have left it unmodified.
INFORMATION ON THE ERROR
From Chrome Dev. Tools, I can see a 500 Internal Server Error returned from the request to the localhost:8080 application. So, the issue is definitely with the sample application (or something that I have not done).
The following error is logged to the console on application deployment (I've included both an image and the text as the text is proving difficult to format):
Text:
[2015-08-20 14:41:40.551] boot - 9908 INFO [localhost-startStop-1]
--- HttpMethodDirector: I/O exception (javax.net.ssl.SSLPeerUnverifiedException) caught when processing
request: SSL peer failed hostname validation for name: 46.4.112.4
[2015-08-20 14:41:40.551] boot - 9908 INFO [localhost-startStop-1]
--- HttpMethodDirector: Retrying request
[2015-08-20 14:41:40.795] boot - 9908 ERROR [localhost-startStop-1] --- HTTPMetadataProvider:
Error retrieving metadata from https://idp.ssocircle.com/idp-meta.xml
javax.net.ssl.SSLPeerUnverifiedException: SSL peer failed hostname
validation for name: 46.4.112.4
at org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory.verifyHostname(TLSProtocolSocketFactory.java:233)
at
org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory.createSocket(TLSProtocolSocketFactory.java:194)
I have visited the url endpoint provided by ssocircle and the metadata is exposed.
If I visit the /saml/metadata endpoint of the service provider and get some helpful information: an org.opensaml.saml2.metadata.provider.MetadataProviderException exception. The description if which is "No IDP was configured, please update included metadata with at least one IDP"; however, the source of this may be the above described error.
QUESTION
Am I missing something that is readily apparent to start the example application? In other words, what does this error tell me that I need to be investigating? Or, as it is "non-breaking", do I ignore it?
WHY I'M ASKING
The documentation surrounding the deployment of the Sample Java Configuration application is minimal (as in "non-existant"). The self-documentation only provides "hints", such as the following:
// IDP Metadata configuration - paths to metadata of IDPs in circle of trust is here
// Do no forget to call initialize method on providers
#Bean
#Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException {
List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
providers.add(ssoCircleExtendedMetadataProvider());
return new CachingMetadataManager(providers);
}
I am certain there is something I am not doing, particularly since I have not done anything in the deployment of the application except for the run of the mvn package, described above.
The problem occurs due to the sample application's utilization of a deprecated constructor - a deprecation whose warning was explicitly suppressed - for the HTTPMetadataProvider (a fix I will commit, shortly). In configuring the ExtendedMetadataDelegate, the two-parametered constructor is utilized:
#Bean
#Qualifier("idp-ssocircle")
public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider() throws MetadataProviderException {
#SuppressWarnings({ "deprecation"})
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider("https://idp.ssocircle.com/idp-meta.xml", 5000);
// other config.s...
}
If replaced with the non-deprecated constructor that takes a java.util.Timer and an org.apache.commons.httpclient.HttpClient (in addition to the metadata url), the sample application works, beautifully, and no errors are logged.
Additional Non-OP-related Information
I had to do the below to get the Sample SAML app to run
After removing the deprecated constructor, I recommend doing two things:
Follow the steps outlined in 4.2.6 of the documentation, i.e. treat the application during setup as the XML-configured application. All the steps need to be taken to "register" the metada.The application will be unable to register its metadata with the current Java configuration (see below; point no. 2)
Change the default configurations in class WebSecurityConfig (read detail, below)
Configuration Change
In the configuration of the ExtendedMetadataDelegate bean ssoCircleExtendedMetadataProvider, change the ExtendedMetadataDelegate's property values as follows:
// code....
extendedMetadataDelegate.setMetadataTrustCheck(true);
extendedMetadataDelegate.setMetadataRequireSignature(false);
// code....
In the ExtendedMetadata bean (different from above), change the property values as below:
// code....
extendedMetadata.setIdpDiscoveryEnabled(true);
extendedMetadata.setSignMetadata(false);
// code....
"Disclaimer"
Whether or not this should be used in production, I do not know; however, it seems to better reflect both the XML-based configuration and resulting metadata of the XML-configured Service Provider example referenced in the SAML Spring Documentation
Just some hints:
I met this exception when I was trying to set up the HTTP-Artifact profile.
There is a hostnameVerifier in org.opensaml.ws.soap.client.http.TLSProtocolSocketFactory (openws-1.5.1) and a verifyHostname() processing before OpenSAML tries to create socket to connect to other host.
I configured sslHostnameVerification to "allowAll" in org.springframework.security.saml.metadata.ExtendedMetadata, the allowed values are "default", "defaultAndLocalhost", "strict", and "allowAll".
It seems in your case, this exception was thrown when the SP (your saml sample) was trying to download the metadata.xml from IdP (ssocircle). The best way to figure out what is happening is to debug when and where the hostnameVerifier was set.
Or you can try set sslHostnameVerification to "allowAll" in the SSOCircle's ExtendedMetadataDelegate's ExtendedMetadata to have a try first..
checkout this answer: It basically describes a plugin I recently released that allows you to configure Spring Boot and Spring Security SAML this way:
#SpringBootApplication
#EnableSAMLSSO
public class SpringBootSecuritySAMLDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSecuritySAMLDemoApplication.class, args);
}
#Configuration
public static class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
}
#Configuration
public static class MyServiceProviderConfig extends ServiceProviderConfigurerAdapter {
#Override
public void configure(ServiceProviderSecurityBuilder serviceProvider) throws Exception {
serviceProvider
.metadataGenerator()
.entityId("localhost-demo")
.and()
.sso()
.defaultSuccessURL("/home")
.idpSelectionPageURL("/idpselection")
.and()
.logout()
.defaultTargetURL("/")
.and()
.metadataManager()
.metadataLocations("classpath:/idp-ssocircle.xml")
.refreshCheckInterval(0)
.and()
.extendedMetadata()
.idpDiscoveryEnabled(true)
.and()
.keyManager()
.privateKeyDERLocation("classpath:/localhost.key.der")
.publicKeyPEMLocation("classpath:/localhost.cert");
}
}
}
There are a couple Demo apps integrated with SSO Circle
I'd like to keep track of certain things in a Spring Boot/OAuth2/Java Config setting and report those somewhere (think AWS CloudWatch, or Google Analytics, or any other similar services).
To be more precise, I have an Authorization+Resource Server with Spring OAuth2.
Those things I'd like to track would be (though not limited to):
Login failed attempts (for clients and users)
Login successful attempts (for clients and users)
Usage of REST controllers (like "/say_hello_world")
Exceptions (aside from the authentication ones)
I was planning on adding javax.servlet.Filters, but then it gets hairy when logging failed attempts with my Authorization Server (using #EnableAuthorizationServer and extending AuthorizationServerConfigurerAdapter). I think I'd need to either resort to using my custom Exception Translator or figure out how to set/wrap the AuthenticationManager in ClientCredentialsTokenEndpointFilter.
Is there a better way than wrapping a tons of things to collect the info I mentioned above?
Update:
As I mentioned in the comments, I'm not looking for a "log dump". I need to be able to get for example the user id that tried to login but failed, or the invalid access token that was used, etc.
I looked into coding my own ApplicationListener<ApplicationEvent>, detect AuthenticationFailureBadCredentialsEvent and the like and go that route.
For example, I can detect a BadCredentialsException, but then need to figure out if it's an InvalidTokenException or other (which is in the cause of that BadCredentialsException).
Next problem is that I can't extract the access token that was used and failed. Feels awkward and more hacking than it should.
I don't mind going through hoops like those, just wondered if there was a better way.
Update2:
There is one thing that helps "listening" to what's going on in a Spring application and it's the ApplicationListener.
Any events published can be caught by providing an implementation of that interface.
InteractiveAuthenticationSuccessEvent is published when a client successfully authenticates (i.e. clientId exists and secret key is valid)
AuthenticationSuccessEvent is published when a user successfully authenticates (i.e. username exists and password matches)
AuthenticationFailureBadCredentialsEvent is published when a user failed authenticating.
There was a problem with #2 and #3 in that in a ProviderManager a NullEventPublisher is setup by default so I had to change my (Java) config like so to get those authentication events:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
...
#Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationEventPublisher(authenticationEventPublisher)
...
}
...
}
Now there remains the problem of receiving the event when the client failed authentication.
The InteractiveAuthenticationSuccessEvent is triggered by AbstractAuthenticationProcessingFilter (which ClientCredentialsTokenEndpointFilter extends) upon successful authentication. But when authentication fails, it does not publish any event (since at least version 3.0.0 to 4.0.1).
Another way would be to configure the AuthenticationManager the same way I did above by setting up a non-NullEventPublisher but as far as I can tell there's no way to setup ClientCredentialsTokenEndpointFilter in Spring Boot without manually setting up the universe???
You can use AOP in spring. Create advice #Before, #After, #Arround execution of method, or even AfterThrowing to detect any exception occured.
Using this approach, you can separate your auditing process from your business logic.