Skip to content

Commit

Permalink
Fix pagination issue and security settings refactoring (#514)
Browse files Browse the repository at this point in the history
* Fix #378 - fix pagination issue

* Security settings refactoring
  • Loading branch information
maznag authored Jan 5, 2021
1 parent f24ba26 commit 35e9d61
Show file tree
Hide file tree
Showing 24 changed files with 137 additions and 128 deletions.
51 changes: 31 additions & 20 deletions docs/Features/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ You can find the jwt configuration in `appsettings.json`
},
```

## Enforce HTTPS

You can enforce HTTPS by setting `"EnforceHttps": true` in `appsettings.Development.json` or `appsettings.Production.json`.

```json
"Security": {
"EnforceHttps": true
},
```

For more details, please see [Enforce HTTPS in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1&tabs=visual-studio#http-strict-transport-security-protocol-hsts)


## OAuth2 and OpenID Connect

OAuth is a stateful security mechanism, like HTTP Session. Spring Security provides excellent OAuth 2.0 and OIDC support, and this is leveraged by JHipster. If you're not sure what OAuth and OpenID Connect (OIDC) are, please see [What the Heck is OAuth?](https://developer.okta.com/blog/2017/06/21/what-the-heck-is-oauth)
Expand All @@ -35,16 +48,15 @@ The security settings in `appsettings.json` are configured for this image.
```
appsettings.json:
...
"jhipster": {
"Security": {
"Authentication": {
"OAuth2": {
"Provider": {
"IssuerUri": "http://localhost:9080/auth/realms/jhipster",
"LogOutUri": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/logout",
"ClientId": "web_app",
"ClientSecret": "web_app"
}
"Security": {
"Authentication": {
"OAuth2": {
"Provider": {
"IssuerUri": "http://localhost:9080/auth/realms/jhipster",
"LogOutUri": "http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/logout",
"ClientId": "web_app",
"ClientSecret": "web_app"
}
```

Keycloak uses an embedded H2 database by default, so you will lose the created users if you restart your Docker container. To keep your data, please read the [Keycloak Docker documentation](https://hub.docker.com/r/jboss/keycloak/). One solution, with keeping the H2 database, is to do the following:
Expand All @@ -63,16 +75,15 @@ Modify `appsettings.json` to use your Okta settings. Hint: replace `{yourOktaDom
```
appsettings.json:
...
"jhipster": {
"Security": {
"Authentication": {
"OAuth2": {
"Provider": {
"IssuerUri": "https://{yourOktaDomain}/oauth2/default",
"LogOutUri": "https://{yourOktaDomain}/oauth2/default/v1/logout",
"ClientId": "client_id",
"ClientSecret": "client_secret"
}
"Security": {
"Authentication": {
"OAuth2": {
"Provider": {
"IssuerUri": "https://{yourOktaDomain}/oauth2/default",
"LogOutUri": "https://{yourOktaDomain}/oauth2/default/v1/logout",
"ClientId": "client_id",
"ClientSecret": "client_secret"
}
```

Create an OIDC App in Okta to get a `{client-id}` and `{client-secret}`. To do this, log in to your Okta Developer account and navigate to **Applications** > **Add Application**. Click **Web** and click the **Next** button. Give the app a name you’ll remember, and specify `http://localhost:[port]/login/oauth2/code/oidc` as a Login redirect URI. Click **Done**, then edit your app to add `http://localhost:[port]` as a Logout redirect URI. Copy the client ID and secret into your `application.yml` file.
Expand Down
2 changes: 1 addition & 1 deletion generators/common/templates/dotnetcore/README.md.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ To format the dotnet code, run

To launch your application's tests, run:

dotnet test --list-tests --verbosity normal
dotnet test --verbosity normal
<% if (!skipClient) { %>
### Client tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ namespace <%= namespace %>.Test.Controllers
{
public <%= pascalizedEntityClass %>ResourceIntTest()
{
_factory = new NhipsterWebApplicationFactory<TestStartup>().WithMockUser();
_factory = new AppWebApplicationFactory<TestStartup>().WithMockUser();
_client = _factory.CreateClient();

_<%= camelCasedEntityClass %>Repository = _factory.GetRequiredService<I<%= pascalizedEntityClass %>Repository>();
Expand Down Expand Up @@ -188,7 +188,7 @@ namespace <%= namespace %>.Test.Controllers
<%_ } _%>

<%_ }); _%>
private readonly NhipsterWebApplicationFactory<TestStartup> _factory;
private readonly AppWebApplicationFactory<TestStartup> _factory;
private readonly HttpClient _client;
private readonly I<%= pascalizedEntityClass %>Repository _<%= camelCasedEntityClass %>Repository;

Expand Down
12 changes: 6 additions & 6 deletions generators/server/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,9 @@ const serverFiles = {
path: SERVER_SRC_DIR,
templates: [
{
file: 'Project.Infrastructure/Configuration/JHipsterSettings.cs',
file: 'Project.Infrastructure/Configuration/SecuritySettings.cs',
renameTo: generator =>
`${generator.pascalizedBaseName}${constants.PROJECT_INFRASTRUCTURE_SUFFIX}/Configuration/JHipsterSettings.cs`,
`${generator.pascalizedBaseName}${constants.PROJECT_INFRASTRUCTURE_SUFFIX}/Configuration/SecuritySettings.cs`,
},
],
},
Expand Down Expand Up @@ -503,8 +503,8 @@ const serverFiles = {
path: SERVER_SRC_DIR,
templates: [
{
file: 'Project/Configuration/NhipsterStartup.cs',
renameTo: generator => `${generator.mainProjectDir}/Configuration/NhipsterStartup.cs`,
file: 'Project/Configuration/AppSettingsStartup.cs',
renameTo: generator => `${generator.mainProjectDir}/Configuration/AppSettingsStartup.cs`,
},
],
},
Expand Down Expand Up @@ -984,8 +984,8 @@ const serverFiles = {
path: SERVER_TEST_DIR,
templates: [
{
file: 'Project.Test/Setup/NhipsterWebApplicationFactory.cs',
renameTo: generator => `${generator.testProjectDir}/Setup/NhipsterWebApplicationFactory.cs`,
file: 'Project.Test/Setup/AppWebApplicationFactory.cs',
renameTo: generator => `${generator.testProjectDir}/Setup/AppWebApplicationFactory.cs`,
},
],
},
Expand Down
6 changes: 1 addition & 5 deletions generators/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,7 @@ module.exports = class extends ServerGenerator {
)}`
)
);
this.log(
chalk.green(
`Test your .Net Core application:\n${chalk.yellow.bold('dotnet test --list-tests --verbosity normal')}`
)
);
this.log(chalk.green(`Test your .Net Core application:\n${chalk.yellow.bold('dotnet test --verbosity normal')}`));
});
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{
"jhipster": {
"Security": {
"Authentication": {
"Jwt": {
"Base64Secret": "bXktc2VjcmV0LWtleS13aGljaC1zaG91bGQtYmUtY2hhbmdlZC1pbi1wcm9kdWN0aW9uLWFuZC1iZS1iYXNlNjQtZW5jb2RlZAo=",
"TokenValidityInSeconds": 86400,
"TokenValidityInSecondsForRememberMe": 2592000
}
{
"Security": {
"Authentication": {
"Jwt": {
"Base64Secret": "bXktc2VjcmV0LWtleS13aGljaC1zaG91bGQtYmUtY2hhbmdlZC1pbi1wcm9kdWN0aW9uLWFuZC1iZS1iYXNlNjQtZW5jb2RlZAo=",
"TokenValidityInSeconds": 86400,
"TokenValidityInSecondsForRememberMe": 2592000
}
},
"Cors": {
Expand All @@ -16,6 +14,7 @@
"ExposedHeaders": "Authorization,Link,X-Total-Count,X-Pagination",
"AllowCredentials": true,
"MaxAge": 1800
}
},
"EnforceHttps": false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ namespace <%= namespace %>.Domain.Services
{
}

// private readonly JHipsterSettings _jhipsterSettings;
// private readonly SecuritySettings _securitySettings;

// public MailService(IOptions<JHipsterSettings> jhipsterSettings)
// public MailService(IOptions<SecuritySettings> securitySettings)
// {
// _jhipsterSettings = jhipsterSettings.Value;
// _securitySettings = securitySettings.Value;
// }

public virtual Task SendPasswordResetMail(User user)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,11 @@
-%>
namespace <%= namespace %>.Infrastructure.Configuration
{
public class JHipsterSettings
{
public Security Security { get; set; }

public Cors Cors { get; set; }
}

public class Security
public class SecuritySettings
{
public Authentication Authentication { get; set; }
public Cors Cors { get; set; }
public bool EnforceHttps { get; set; }
}

<%_ if (authenticationType === 'jwt') { _%>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ using Microsoft.Extensions.DependencyInjection;

namespace <%= namespace %>.Configuration
{
public static class NhipsterSettingsConfiguration
public static class AppSettingsConfiguration
{
public static IServiceCollection AddNhipsterModule(this IServiceCollection services, IConfiguration configuration)
public static IServiceCollection AddAppSettingsModule(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<JHipsterSettings>(configuration.GetSection("jhipster"));
// Use this to load settings from appSettings file
services.Configure<SecuritySettings>(options => configuration.GetSection("security").Bind(options));

return services;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,23 @@ namespace <%= namespace %>.Configuration
<%_ if (authenticationType === 'jwt') { _%>
public static IServiceCollection AddSecurityModule(this IServiceCollection services)
<%_ } else { _%>
public static IServiceCollection AddSecurityModule(this IServiceCollection services, JHipsterSettings jhipsterSettings)
public static IServiceCollection AddSecurityModule(this IServiceCollection services, SecuritySettings securitySettings)
<%_ } _%>
{
<%_ if (authenticationType === 'jwt') { _%>
//TODO Retrieve the signing key properly (DRY with TokenProvider)
var opt = services.BuildServiceProvider().GetRequiredService<IOptions<JHipsterSettings>>();
var jhipsterSettings = opt.Value;
var opt = services.BuildServiceProvider().GetRequiredService<IOptions<SecuritySettings>>();
var securitySettings = opt.Value;
byte[] keyBytes;
var secret = jhipsterSettings.Security.Authentication.Jwt.Secret;
var secret = securitySettings.Authentication.Jwt.Secret;
if (!string.IsNullOrWhiteSpace(secret))
{
keyBytes = Encoding.ASCII.GetBytes(secret);
}
else
{
keyBytes = Convert.FromBase64String(jhipsterSettings.Security.Authentication.Jwt.Base64Secret);
keyBytes = Convert.FromBase64String(securitySettings.Authentication.Jwt.Base64Secret);
}
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
Expand Down Expand Up @@ -130,9 +130,9 @@ namespace <%= namespace %>.Configuration
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = jhipsterSettings.Security.Authentication.OAuth2.Provider.IssuerUri;
options.ClientId = jhipsterSettings.Security.Authentication.OAuth2.Provider.ClientId;
options.ClientSecret = jhipsterSettings.Security.Authentication.OAuth2.Provider.ClientSecret;
options.Authority = securitySettings.Authentication.OAuth2.Provider.IssuerUri;
options.ClientId = securitySettings.Authentication.OAuth2.Provider.ClientId;
options.ClientSecret = securitySettings.Authentication.OAuth2.Provider.ClientSecret;
options.SaveTokens = true;
options.ResponseType = OpenIdConnectResponseType.Code;
options.RequireHttpsMetadata = false; // dev only
Expand All @@ -151,16 +151,18 @@ namespace <%= namespace %>.Configuration
}

public static IApplicationBuilder UseApplicationSecurity(this IApplicationBuilder app,
JHipsterSettings jhipsterSettings)
SecuritySettings securitySettings)
{
app.UseCors(CorsPolicyBuilder(jhipsterSettings.Cors));
app.UseCors(CorsPolicyBuilder(securitySettings.Cors));
app.UseAuthentication();
<%_ if (authenticationType === 'oauth2') { _%>
app.UseAuthorization();
<%_ } _%>
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
app.UseHttpsRedirection();
if (securitySettings.EnforceHttps)
{
app.UseHsts();
app.UseHttpsRedirection();
}
return app;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ namespace <%= namespace %>.Controllers
[ApiController]
public class AuthController : ControllerBase
{
private readonly JHipsterSettings _settings;
private readonly SecuritySettings _settings;

public AuthController(IOptions<JHipsterSettings> settings)
public AuthController(IOptions<SecuritySettings> settings)
{
_settings = settings.Value;
}
Expand All @@ -37,7 +37,7 @@ namespace <%= namespace %>.Controllers
await HttpContext.SignOutAsync();
return Ok(new
{
logoutUrl = _settings.Security.Authentication.OAuth2.Provider.LogOutUri,
logoutUrl = _settings.Authentication.OAuth2.Provider.LogOutUri,
idToken = await HttpContext.GetTokenAsync("id_token")
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace <%= namespace %>.Security.Jwt
{
private const string AuthoritiesKey = "auth";

private readonly JHipsterSettings _jhipsterSettings;
private readonly SecuritySettings _securitySettings;

private readonly JwtSecurityTokenHandler _jwtSecurityTokenHandler;

Expand All @@ -50,10 +50,10 @@ namespace <%= namespace %>.Security.Jwt
private long _tokenValidityInSecondsForRememberMe;


public TokenProvider(ILogger<TokenProvider> log, IOptions<JHipsterSettings> jhipsterSettings)
public TokenProvider(ILogger<TokenProvider> log, IOptions<SecuritySettings> securitySettings)
{
_log = log;
_jhipsterSettings = jhipsterSettings.Value;
_securitySettings = securitySettings.Value;
_jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
Init();
}
Expand Down Expand Up @@ -100,24 +100,24 @@ namespace <%= namespace %>.Security.Jwt
private void Init()
{
byte[] keyBytes;
var secret = _jhipsterSettings.Security.Authentication.Jwt.Secret;
var secret = _securitySettings.Authentication.Jwt.Secret;

if (!string.IsNullOrWhiteSpace(secret))
{
_log.LogWarning("Warning: the JWT key used is not Base64-encoded. " +
"We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security.");
"We recommend using the `security.authentication.jwt.base64-secret` key for optimum security.");
keyBytes = Encoding.ASCII.GetBytes(secret);
}
else
{
_log.LogDebug("Using a Base64-encoded JWT secret key");
keyBytes = Convert.FromBase64String(_jhipsterSettings.Security.Authentication.Jwt.Base64Secret);
keyBytes = Convert.FromBase64String(_securitySettings.Authentication.Jwt.Base64Secret);
}

_key = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256Signature);
_tokenValidityInSeconds = _jhipsterSettings.Security.Authentication.Jwt.TokenValidityInSeconds;
_tokenValidityInSeconds = _securitySettings.Authentication.Jwt.TokenValidityInSeconds;
_tokenValidityInSecondsForRememberMe =
_jhipsterSettings.Security.Authentication.Jwt.TokenValidityInSecondsForRememberMe;
_securitySettings.Authentication.Jwt.TokenValidityInSecondsForRememberMe;
}

private static ClaimsIdentity CreateSubject(IPrincipal principal)
Expand Down
Loading

0 comments on commit 35e9d61

Please sign in to comment.