应用安全

To use Spring Security with a Single Web Page Application, like the ones generated by JHipster, you need Ajax login/logout/error views. We have configured Spring Security in order to use those views correctly, and of course we generate all the JavaScript and HTML code for you.

默认情况下, JHipster 设置了四种用户:

  • “system”, 记录系统日志的用户,一些自动操作的执行者;(译注:只是一个常量)
  • “anonymousUser”, 匿名用户可以自行动作时记录的用户;
  • “user”, 普通用户,记录以 “ROLE_USER”。默认密码是:”user”;
  • “admin”, 超级管理员,拥有 “ROLE_USER” 和 “ROLE_ADMIN” 两种角色。默认密码是 “admin”

For security reasons, you should change those default passwords in production.

JHipster provides 4 main security mechanisms:

  1. JSON Web Tokens (JWT)
  2. Session-based authentication
  3. OAuth2 and OpenID Connect
  4. JHipster User Account and Authentication (UAA) (which has a separate documentation page as this is more complex)

JWT 认证

JSON Web Token (JWT) JWT 认证是一种无状态安全机制,所以这是一个非常棒的选择如果你希望扩展你的应用到多个服务器上。

请注意,当使用 微服务架构 时这是默认选项。

这个认证机制不是 Spring Security 默认提供的,而是 JHipster 提供的 Java JWT 项目 集成。

解决方案是使用一个安全令牌,保存了用户的登录名和权限。当令牌被签发后,是不可以被修改的。

Securing JWT

  • JHipster uses a secret key, which can be configured using two Spring Boot properties: jhipster.security.authentication.jwt.secret and jhipster.security.authentication.jwt.base64-secret. The second option uses a Base64-encoded string, so it is considered more secured and thus it is recommended. If both properties are configured, the secret property (less secured) will be used, for legacy reasons. A warning will be shown at application startup if you don’t use the Base64 property.
  • Those keys should have a minimum length of 512 bits: if they are not long enough, you will not be able to use them to login. If that happens, there will be a clear warning at the console to explain that issue.
  • 安全秘钥可以在 application-*.yml 里设置。 As those keys must be kept secret, you should store them in a secure way for your production profile. It can be set up using the usual Spring Boot property configuration: using a Spring Cloud Config server like the JHipster Registry (our recommended option), using an environment variable, or even a specific application-prod.yml file which is SCP’d by a sysadmin into the same directory as your application’s executable WAR file.
  • You should change the default “user” and “admin” passwords. The easiest way to do this is to deploy your application, login as “user/user” and then “admin/admin”, and for each of them use the “Account > Password” menu to change the password.

基于 HTTP Session 认证

经典的 Spring Security 认证机制,我们优化过了不少。这种方式使用 HTTP Session,所以是有状态(stateful)的方式:如果你希望 在多个服务器上扩展你的应用,你需要一个负载均衡器并 sticky(持久化?)session 来让用户停留在同一个服务器上。

Securing Session-based authentication

  • For remember-me authentication, the remember-me key is configured in the application-dev.yml and application-prod.yml files, as the jhipster.security.remember-me.key property. As this key must be kept secret, you should store it in a secure way for your production profile. It can be set up using the usual Spring Boot property configuration: using a Spring Cloud Config server like the JHipster Registry (our recommended option), using an environment variable, or even a specific application-prod.yml file which is SCP’d by a sysadmin into the same directory as your application’s executable WAR file.
  • You should change the default “user” and “admin” passwords. The easiest way to do this is to deploy your application, login as “user/user” and then “admin/admin”, and for each of them use the “Account > Password” menu to change the password.

优化过的 remember-me 机制

我们修改了 Spring Security 的 remember-me 机制,你会获得一个唯一的令牌(token),并存储在数据库中 (SQL 或 NoSQL,取决于你的选择)。还存储了更多的信息,让你知道这些令牌来自哪里:IP 地址,浏览器,日期等。 我们还创建了一个管理界面,你可以删除(invalidate)这些 session,举例说你在另一台电脑上忘了登出。

我们提供了一个完整的 cookie 防窃取包含机制:我们将你的安全信息存储在一个 cookie 中,同时在数据库中,用户每一次登入都会修改这些值并且检查它们是否被修改过。这样的话,即使你的 cookie 被盗,他也只能最多使用一次。

CSRF(Cross-site request forgery 跨站请求伪造)保护

Spring Security 和 Angular 都具备 CSRF 内建(开箱即用)的保护功能,但是,不幸的是他们没法使用相同的 cookie 和 HTTP 头上!事实上,你可能对这些 CSRF 工具毫无防护。所以,我们重新配置了这些工具来让他们正确工作。

OAuth 2.0 认证

OAuth 是一种有状态的安全机制,类似 HTTP Session。Spring Security 提供了 OAuth 2.0 的支持, , and this is leveraged by JHipster with its @EnableOAuth2Sso annotation.
如果你不清楚什么是 OAuth 或 OpenID 连接器 (OIDC) ,请参考这篇文章 What the Heck is OAuth?

Keycloak

Keycloak is the default OpenID Connect server configured with JHipster.

为了登入你的应用,你需要启动一个 Keycloak 应用使之运行。JHipster 团队已经创建了一个 Docker 容器的镜像,包含了一些用户和角色。如下方式启动 Keycloak 服务:

docker-compose -f src/main/docker/keycloak.yml up

If you want to use Keycloak with Docker Compose, be sure to read our Docker Compose documentation, and configure correctly your /etc/hosts for Keycloak.

这些配置文件 src/main/resources/application.yml 已经为这个镜像配置好了。

security:
    basic:
        enabled: false
    oauth2:
        client:
            access-token-uri: http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/token
            user-authorization-uri: http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/auth
            client-id: web_app
            client-secret: web_app
            scope: openid profile email
        resource:
            user-info-uri: http://localhost:9080/auth/realms/jhipster/protocol/openid-connect/userinfo

As by default Keycloak uses an embedded H2 database, you will lose the created users if you restart your Docker container. To keep your data, please read the Keycloak Docker documentation. One solution, with keeping the H2 database, is to do the following:

  • Add a volume that will be persisted: ./keycloak-db:/opt/jboss/keycloak/standalone/data
  • Change the migration strategy from OVERWRITE_EXISTING, to IGNORE_EXISTING (in the command section)

In production, it is required by Keycloak that you use HTTPS. There are several ways to achieve this, including using a reverse proxy or load balancer that will manage HTTPS. We recommend that you read the Keycloak HTTPS documentation to learn more about this topic.

Okta

If you’d like to use Okta instead of Keycloak, you’ll need to change a few things. First, you’ll need to create a free developer account at https://developer.okta.com/signup/. After doing so, you’ll get your own Okta domain, that has a name like https://dev-123456.oktapreview.com.

Modify src/main/resources/application.yml to use your Okta settings. Hint: replace {yourOktaDomain} with your org’s name (e.g., dev-123456.oktapreview).

security:
    basic:
        enabled: false
    oauth2:
        client:
            access-token-uri: https://{yourOktaDomain}.com/oauth2/default/v1/token
            user-authorization-uri: https://{yourOktaDomain}.com/oauth2/default/v1/authorize
            client-id: {client-id}
            client-secret: {client-secret}
            scope: openid profile email
        resource:
            user-info-uri: https://{yourOktaDomain}.com/oauth2/default/v1/userinfo

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:8080 as a Base URI and http://localhost:8080/login as a Login Redirect URI. Click Done and copy the client ID and secret into your application.yml file.

Create a ROLE_ADMIN and ROLE_USER group (Users > Groups > Add Group) and add users to them. You can use the account you signed up with, or create a new user (Users > Add Person). Navigate to API > Authorization Servers, click the Authorization Servers tab and edit the default one. Click the Claims tab and Add Claim. Name it “groups” or “roles”, and include it in the ID Token. Set the value type to “Groups” and set the filter to be a Regex of .*.

NOTE: If you want to use Okta all the time (instead of Keycloak), modify JHipster’s Protractor tests to use this account when running. Do this by changing the credentials in src/test/javascript/e2e/account/account.spec.ts and src/test/javascript/e2e/admin/administration.spec.ts.

After https://keycloak.orgmaking these changes, you should be good to go! If you have any issues, please post them to Stack Overflow. Make sure to tag your question with “jhipster” and “okta”.

可设置环境变量来复写这些属性。举例:

export SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI="https://{yourOktaDomain}.com/oauth2/default/v1/token"
export SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI="https://{yourOktaDomain}.com/oauth2/default/v1/authorize"
export SECURITY_OAUTH2_RESOURCE_USER_INFO_URI="https://{yourOktaDomain}.com/oauth2/default/v1/userinfo"
export SECURITY_OAUTH2_CLIENT_CLIENT_ID="{client-id}"
export SECURITY_OAUTH2_CLIENT_CLIENT_SECRET="{client-secret}"

You can put this in an ~/.okta.env file and run source ~/.okta.env to override Keycloak with Okta.

You can use then set these properties when you deploy to Heroku:

heroku config:set \
  SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI="$SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI" \
  SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI="$SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI" \
  SECURITY_OAUTH2_RESOURCE_USER_INFO_URI="$SECURITY_OAUTH2_RESOURCE_USER_INFO_URI" \
  SECURITY_OAUTH2_CLIENT_CLIENT_ID="$SECURITY_OAUTH2_CLIENT_CLIENT_ID" \
  SECURITY_OAUTH2_CLIENT_CLIENT_SECRET="$SECURITY_OAUTH2_CLIENT_CLIENT_SECRET"

For Cloud Foundry, you can use something like the following, where $appName is the name of your app.

cf set-env $appName SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI "$SECURITY_OAUTH2_CLIENT_ACCESS_TOKEN_URI"
cf set-env $appName SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI "$SECURITY_OAUTH2_CLIENT_USER_AUTHORIZATION_URI"
cf set-env $appName SECURITY_OAUTH2_RESOURCE_USER_INFO_URI "$SECURITY_OAUTH2_RESOURCE_USER_INFO_URI"
cf set-env $appName SECURITY_OAUTH2_CLIENT_CLIENT_ID "$SECURITY_OAUTH2_CLIENT_CLIENT_ID"
cf set-env $appName SECURITY_OAUTH2_CLIENT_CLIENT_SECRET "$SECURITY_OAUTH2_CLIENT_CLIENT_SECRET"

See Use OpenID Connect Support with JHipster to learn more about JHipster and OIDC with Okta.

HTTPS

You can enforce the use of HTTPS when your app is running on Heroku by adding the following configuration to your SecurityConfiguration.java.

@Configuration
public class WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.requiresChannel()
      .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
      .requiresSecure();
  }
}

This will work on both Heroku and Cloud Foundry. For more production tips on Heroku, see Preparing a Spring Boot App for Production on Heroku.