I'm trying to secure a spring-boot web application using spring security and spring-security-cas (SSO with Jasig CAS).
I'm facing a too many redirects error when trying to access a protected resources. The project is available here
Do you see any error in my configuration?
Thanks in advance
redirect loop error screenshot
Finally found out the error:
In SpringSecurity 4.x, CasAuthenticationFilter's defaultFilterProcessesUrl path is changed. So Change '/j_spring_cas_security_check' to '/login/cas' in Configuration.
So in my application.properties file, i had to change
app.service.security=http://localhost:7777/j_spring_cas_security_check
to
app.service.security=http://localhost:7777/login/cas
So the ServiceProperties Bean would become
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("http://localhost:7777/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
Hope it'll help someone else!
Related
I'm having no luck in setting up a simple Spring gateway + oauth2 client with Keycloak standalone. The keycloack part of it works fine. Wireshark shows the token correctly generated.
The gateway security config is as follows. I'm still not sure whether there is a need to permitAll() the login callback url. Some guides suggest that it should be the case, others dont. I suspect the oauth provider manages that part behind the scenes. Nonetheless, with or without permitAll for the "/login/*" path, the result remains the same.
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(e -> e.anyExchange().authenticated());
http.oauth2Login(Customizer.withDefaults());
http.csrf().disable();
return http.build();
}
}
After login the redirect to https://localhost:9000/login seems incorrect, it should retry the original url, say https://localhost:9000/test-service/v1/listall/
EDIT
In order to rule out any misconfigurations, even tried a simplest possible gateway and api resource (un-authenticated) and setup simplest possible relam in keyclock. The results haven't changed :( There are dozens of articles out there doing the exact same thing.
Any pointers, ideas?
Many Thanks
Even I have completed all configurations including "creating a client", "creating a scope" and creating a "user" with this scope. I have encountered this issue again.
The only solution worked for me is adding scope :openid to application.yaml.
You can refer application.yaml Security OAuth config from here:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://${keycloak.base.url}/auth/realms/${realm}
client:
registration:
gateway:
provider: keycloak
client-id: ${client id}
client-secret:${client secret}
scope: openid
provider:
keycloak:
user-name-attribute: preferred_username
issuer-uri: https://${keycloak.base.url}/auth/realms/${realm}
According to offical doc(found at https://www.keycloak.org/docs/latest/securing_apps/#_spring_security_adapter), you should use
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
I figured it out, it was an incorrect user-name-attribute. The correct value is
user-name-attribute: preferred_username
For some reason, I had it set to preferred_name. It would save a lot of debug-time if only spring oauth writes the actual error instead of a generic invalid_grant.
First of all, for anyone troubleshooting Spring Security, I recommend enabling debug logging by setting the logging level in your application.properties or application.yml file.
application.properties format:
logging.level.org.springframework.security=DEBUG
application.yml format:
logging:
level:
org:
springframework:
security: DEBUG
I was having a similar issue when using OAuth2 along with Spring Session. Even after authenticating successfully with Keycloak, I would get this login error whenever my Spring Session had expired.
I do not condone messing around with authentication flows, but I was able to resolve this issue by setting my own authentication entry point on the ServerHttpSecurity:
http.exceptionHandling().authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/keycloak"));
I then had to handle any requests to the "/login" page in my Controller. For me, I just redirect to my default landing page for a root request:
#Controller
#RequestMapping("/")
public class RootController {
#GetMapping({"", "login"})
public Mono<String> index() {
return Mono.just("redirect:/myLandingPage");
}
}
Somehow adding the "scope=openid" in my applications.properties works fine for me
spring.security.oauth2.client.registration.spring-cloud-client.scope=openid
I have a Spring Boot application deployed and configured as AWS Route 53 > AWS Load Balance -> 2 EC2 instances which hosted the Spring Boot application.
The URL for the Swagger is
https://applicationXYZ.company.net/release/swagger-ui.html
I'm able to see the page without any issue. But we can't use the 'Tryout' feature because the Base URL is wrong.
On top of the page I do see information as
[ Base URL: service/release]
I have no idea where 'service' became my base URL. I also hit api-docs and also see 'server' in 'host' field.
Could you please help on this?
Note: I'm using Spring Boot Starter 2.0.8.RELEASE and Swagger 2.9.2 (without any Spring Security)
Thanks,
Did you ever try to make a redirect,
//Do redirection inside controller
#RequestMapping("/swagger")
public String greeting() {
return "redirect:/swagger-ui.html";
}
you can try to add bean too, inside main method,
#Bean
RouterFunction<ServerResponse> routerFunction() {
return route(GET("/swagger"), req ->
ServerResponse.temporaryRedirect(URI.create("swagger-ui.html")).build());
}
refer: How to change Swagger-ui URL prefix?
I have a form:
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary()...
and action:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl, string City)
{
}
occasionally (once a week), I get the error:
The anti-forgery token could not be decrypted. If this application is
hosted by a Web Farm or cluster, ensure that all machines are running
the same version of ASP.NET Web Pages and that the configuration
specifies explicit encryption and validation keys. AutoGenerate cannot
be used in a cluster.
i try add to webconfig:
<machineKey validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps" />
but the error still appears occasionally
I noticed this error occurs, for example when a person came from one computer and then trying another computer
Or sometimes an auto value set with incorrect data type like bool to integer to the form field by any jQuery code please also check it.
I just received this error as well and, in my case, it was caused by the anti-forgery token being applied twice in the same form. The second instance was coming from a partial view so wasn't immediately obvious.
validationKey="AutoGenerate"
This tells ASP.NET to generate a new encryption key for use in encrypting things like authentication tickets and antiforgery tokens every time the application starts up. If you received a request that used a different key (prior to a restart for instance) to encrypt items of the request (e.g. authenication cookies) that this exception can occur.
If you move away from "AutoGenerate" and specify it (the encryption key) specifically, requests that depend on that key to be decrypted correctly and validation will work from app restart to restart. For example:
<machineKey
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"
/>
You can read to your heart's content at MSDN page: How To: Configure MachineKey in ASP.NET
Just generate <machineKey .../> tag from a link for your framework version and insert into <system.web><system.web/> in Web.config if it does not exist.
Hope this helps.
If you get here from google for your own developer machine showing this error, try to clear cookies in the browser. Clear Browser cookies worked for me.
in asp.net Core you should set Data Protection system.I test in Asp.Net Core 2.1 or higher.
there are multi way to do this and you can find more information at Configure Data Protection and Replace the ASP.NET machineKey in ASP.NET Core and key storage providers.
first way: Local file (easy implementation)
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
// ----- finally Add this DataProtection -----
var keysFolder = Path.Combine(WebHostEnvironment.ContentRootPath, "temp-keys");
services.AddDataProtection()
.SetApplicationName("Your_Project_Name")
.PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
.SetDefaultKeyLifetime(TimeSpan.FromDays(14));
}
}
second way: save to db
The Microsoft.AspNetCore.DataProtection.EntityFrameworkCore NuGet
package must be added to the project file
Add MyKeysConnection ConnectionString to your projects
ConnectionStrings in appsettings.json > ConnectionStrings >
MyKeysConnection.
Add MyKeysContext class to your project.
MyKeysContext.cs content:
public class MyKeysContext : DbContext, IDataProtectionKeyContext
{
// A recommended constructor overload when using EF Core
// with dependency injection.
public MyKeysContext(DbContextOptions<MyKeysContext> options)
: base(options) { }
// This maps to the table that stores keys.
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
}
startup.cs content:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// ----- Add this DataProtection -----
// Add a DbContext to store your Database Keys
services.AddDbContext<MyKeysContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyKeysConnection")));
// using Microsoft.AspNetCore.DataProtection;
services.AddDataProtection()
.PersistKeysToDbContext<MyKeysContext>();
// .... Add your services like :
// services.AddControllersWithViews();
// services.AddRazorPages();
}
}
If you use Kubernetes and have more than one pod for your app this will most likely cause the request validation to fail because the pod that generates the RequestValidationToken is not necessarily the pod that will validate the token when POSTing back to your application. The fix should be to configure your nginx-controller or whatever ingress resource you are using and tell it to load balance so that each client uses one pod for all communication.
Update: I managed to fix it by adding the following annotations to my ingress:
https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
Name Description Values
nginx.ingress.kubernetes.io/affinity Sets the affinity type string (in NGINX only cookie is possible
nginx.ingress.kubernetes.io/session-cookie-name Name of the cookie that will be used string (default to INGRESSCOOKIE)
nginx.ingress.kubernetes.io/session-cookie-hash Type of hash that will be used in cookie value sha1/md5/index
I ran into this issue in an area of code where I had a view calling a partial view, however, instead of returning a partial view, I was returning a view.
I changed:
return View(index);
to
return PartialView(index);
in my control and that fixed my problem.
I got this error on .NET Core 2.1. I fixed it by adding the Data Protection service in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection();
....
}
you are calling more than one the #Html.AntiForgeryToken() in your view
I get this error when the page is old ('stale'). A refresh of the token via a page reload resolves my problem. There seems to be some timeout period.
I found a very interesting workaround for this problem, at least in my case. My view was dynamically loading partial views with forms in a div using ajax, all within another form. the master form submits no problem, and one of the partials works but the other doesn't. The ONLY difference between the partial views was at the end of the one that was working was an empty script tag
<script type="text/javascript">
</script>
I removed it and sure enough I got the error. I added an empty script tag to the other partial view and dog gone it, it works! I know it's not the cleanest... but as far as speed and overhead goes...
I know I'm a little late to the party, but I wanted to add another possible solution to this issue. I ran into the same problem on an MVC application I had. The code did not change for the better part of a year and all of the sudden we started receiving these kinds of error messages from the application.
We didn't have multiple instances of the anti-forgery token being applied to the view twice.
We had the machine key set at the global level to Autogenerate because of STIG requirements.
It was exasperating until I got part of the answer here: https://stackoverflow.com/a/2207535/195350:
If your MachineKey is set to AutoGenerate, then your verification
tokens, etc won't survive an application restart - ASP.NET will
generate a new key when it starts up, and then won't be able to
decrypt the tokens correctly.
The issue was that the private memory limit of the application pool was being exceeded. This caused a recycle and, therefore, invalidated the keys for the tokens included in the form. Increasing the private memory limit for the application pool appears to have resolved the issue.
My fix for this was to get the cookie and token values like this:
AntiForgery.GetTokens(null, out var cookieToken, out var formToken);
For those getting this error on Google AppEngine or Google Cloud Run, you'll need to configure your ASP.NET Core website's Data Protection.
The documentation from the Google team is easy to follow and works.
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
A general overview from the Microsoft docs can be found here:
https://cloud.google.com/appengine/docs/flexible/dotnet/application-security#aspnet_core_data_protection_provider
Note that you may also find you're having to login over and over, and other quirky stuff going on. This is all because Google Cloud doesn't do sticky sessions like Azure does and you're actually hitting different instances with each request.
Other errors logged, include:
Identity.Application was not authenticated. Failure message: Unprotect ticket failed
I am trying to write a grails plugin using Spring social core plugin. I get the provider popup and after I enter user and password it is giving me 404. As I debugged the code, it is coming into SpringSocialProviderSignInController handleSignIn() method and it is not getting anything for signup url. In grails plugin this is the code snipet
if (userIds.size() == 0) {
if (log.isDebugEnabled()) {
log.debug("No user found in the repository, creating a new one...")
}
ProviderSignInAttempt signInAttempt = new ProviderSignInAttempt(connection, connectionFactoryLocator, usersConnectionRepository)
request.setAttribute(ProviderSignInAttempt.SESSION_ATTRIBUTE, signInAttempt, RequestAttributes.SCOPE_SESSION)
//TODO: Document this setting
result = request.session.ss_oauth_redirect_on_signIn_attempt ?: config.page.handleSignIn
}
I see that even in the regular spring social web jar this has similar logic. Except in the web there is a default set on signupUrl. I tried giving the same value(/signup) in config.page.handleSignIn but it did not help.
if (userIds.size() == 0) {
ProviderSignInAttempt signInAttempt = new ProviderSignInAttempt(connection, connectionFactoryLocator, usersConnectionRepository);
request.setAttribute(ProviderSignInAttempt.SESSION_ATTRIBUTE, signInAttempt, RequestAttributes.SCOPE_SESSION);
return redirect(signUpUrl);
}
In general, I am trying to understand what this signUpUrl does. I am not able to go further after this. Is it mandatory to give signUpUrl? My understanding was no.
Have asked the same question in spring social forum, but is not getting any response there. Trying my luck here.
Found meaningful answer in spring forum : Spring Forum Link
I am (finally) upgrading my Acegi plugin to Spring Security Core. At the same time, I am upgrading from Grails 1.3.7 to 2.0. My site was fully functional before, but now when I try to get to my default page (which is IS_AUTHENTICATED_ANONYMOUSLY) I am redirected to the auth action of my LoginController. This method was never invoked with Acegi, so I don't know what the problem is. Have I set up my configuration wrong or is there something else I need to be thinking about?
grails.plugins.springsecurity.securityConfigType = SecurityConfigType.InterceptUrlMap
grails.plugins.springsecurity.interceptUrlMap = [
'/blog/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/static/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/consensus/**':['IS_AUTHENTICATED_FULLY'],
'/login/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/signup/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/home/**':['IS_AUTHENTICATED_FULLY'],
'/test/**':['ROLE_ADMIN'],
'/admin/**':['ROLE_ADMIN'],
'/adminmanage/**':['ROLE_ADMIN'],
'/quartz/**':['ROLE_ADMIN'],
'/**/*.css':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/js/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/images/**':['IS_AUTHENTICATED_ANONYMOUSLY'],
'/monitoring**':['ROLE_ADMIN'],
'/**':['IS_AUTHENTICATED_FULLY']
]
My UrlMappings.groovy is:
class UrlMappings {
static mappings = {
"/"(controller:"x", action:"y")
"/z/?"(controller:"x", action:"y")
"/$controller/$action?/$id?"
{
constraints {
// apply constraints here
}
}
"500"(view: '/error')
}
}
I have been reading through the documentation but am having some problems, so I am not sure if there is more relevant code one would need to see. If there is, please let me know and I will add it. Thanks.
Other options in my Config.groovy were incorrect and this caused the problem. Once I corrected them everything worked fine.
Despite it being called out in the documentation, I had security fields that were not prepended with grails.plugins.springsecurity This caused the engine not to recognize them, which for some reason resulted in the call to auth.
After remove openid plugin all requests redirects me to the login page! I don't know what to do... I've already remove everything related.