140 changed files with 2998 additions and 413 deletions
@ -0,0 +1,19 @@ |
|||
/** |
|||
* 设置打包文件的运行时目标环境(target) |
|||
* 设置方式: 通过命令行 -D 传入目标环境参数 |
|||
* 打包命令如下: |
|||
* 1. gradle bootwar # 默认, target=tomcat |
|||
* 2. gradle bootwar -Dtarget=undertow # undertow, target=undertow |
|||
* 3. gradle bootwar -Dtarget=jetty # jetty, target=jetty |
|||
*/ |
|||
def target =System.getProperty("target") ?: "tomcat"; |
|||
System.setProperty('target',target); |
|||
|
|||
// 根据 targetRuntime 变量的值执行实际的 build.gradle |
|||
apply from: "build-${target}.gradle" |
|||
|
|||
// 应用启动项目无需发布到仓库中 |
|||
publishPublicationPublicationToMavenRepository.enabled=false |
|||
|
|||
// 开启 docker 镜像生成任务 |
|||
jibBuildTar.enabled =true |
@ -0,0 +1,15 @@ |
|||
println "[Jetty] 环境 ......" |
|||
|
|||
configurations { |
|||
all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" |
|||
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core" |
|||
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket" |
|||
} |
|||
|
|||
dependencies { |
|||
implementation("org.springframework.boot:spring-boot-starter-jetty") |
|||
|
|||
providedRuntime( |
|||
"org.springframework.boot:spring-boot-starter-jetty", |
|||
) |
|||
} |
@ -0,0 +1,7 @@ |
|||
println "[Tomcat] 环境 ......" |
|||
|
|||
dependencies { |
|||
providedRuntime( |
|||
"org.springframework.boot:spring-boot-starter-tomcat", |
|||
) |
|||
} |
@ -0,0 +1,15 @@ |
|||
println "[Undertow] 环境 ......" |
|||
|
|||
configurations { |
|||
all*.exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" |
|||
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-core" |
|||
all*.exclude group: "org.apache.tomcat.embed", module: "tomcat-embed-websocket" |
|||
} |
|||
|
|||
dependencies { |
|||
implementation("org.springframework.boot:spring-boot-starter-undertow") |
|||
|
|||
providedRuntime( |
|||
"org.springframework.boot:spring-boot-starter-undertow", |
|||
) |
|||
} |
@ -0,0 +1,21 @@ |
|||
apply plugin: 'war' |
|||
apply plugin: 'com.google.cloud.tools.jib' |
|||
|
|||
apply from: "build-common.gradle" |
|||
|
|||
dependencies { |
|||
implementation("org.springframework.boot:spring-boot-starter-web"){ |
|||
exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" |
|||
} |
|||
} |
|||
|
|||
dependencies { |
|||
implementation( |
|||
"jakarta.servlet:jakarta.servlet-api", |
|||
"org.springframework.boot:spring-boot-starter-web", |
|||
"org.springframework.boot:spring-boot-starter-security", |
|||
"org.springframework.boot:spring-boot-starter-jdbc", |
|||
"org.springframework.security:spring-security-oauth2-authorization-server:0.4.5", |
|||
"com.h2database:h2" |
|||
) |
|||
} |
@ -0,0 +1,32 @@ |
|||
/* |
|||
* Copyright 2020-2021 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample; |
|||
|
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
|
|||
/** |
|||
* @author Joe Grandja |
|||
* @since 0.0.1 |
|||
*/ |
|||
@SpringBootApplication |
|||
public class DefaultAuthorizationServerApplication { |
|||
|
|||
public static void main(String[] args) { |
|||
SpringApplication.run(DefaultAuthorizationServerApplication.class, args); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,147 @@ |
|||
/* |
|||
* Copyright 2020-2022 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample.config; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import com.nimbusds.jose.jwk.JWKSet; |
|||
import com.nimbusds.jose.jwk.RSAKey; |
|||
import com.nimbusds.jose.jwk.source.JWKSource; |
|||
import com.nimbusds.jose.proc.SecurityContext; |
|||
import sample.jose.Jwks; |
|||
|
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.core.Ordered; |
|||
import org.springframework.core.annotation.Order; |
|||
import org.springframework.jdbc.core.JdbcTemplate; |
|||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; |
|||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; |
|||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; |
|||
import org.springframework.security.config.Customizer; |
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; |
|||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
|||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
|||
import org.springframework.security.oauth2.core.oidc.OidcScopes; |
|||
import org.springframework.security.oauth2.jwt.JwtDecoder; |
|||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; |
|||
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
|||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; |
|||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
|||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; |
|||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; |
|||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; |
|||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; |
|||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; |
|||
import org.springframework.security.web.SecurityFilterChain; |
|||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; |
|||
|
|||
/** |
|||
* @author Joe Grandja |
|||
* @since 0.0.1 |
|||
*/ |
|||
@Configuration(proxyBeanMethods = false) |
|||
public class AuthorizationServerConfig { |
|||
|
|||
@Bean |
|||
@Order(Ordered.HIGHEST_PRECEDENCE) |
|||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); |
|||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) |
|||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
|||
|
|||
// @formatter:off
|
|||
http |
|||
.exceptionHandling(exceptions -> |
|||
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) |
|||
) |
|||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); |
|||
// @formatter:on
|
|||
return http.build(); |
|||
} |
|||
|
|||
// @formatter:off
|
|||
@Bean |
|||
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { |
|||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) |
|||
.clientId("messaging-client") |
|||
.clientSecret("{noop}secret") |
|||
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) |
|||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) |
|||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) |
|||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) |
|||
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc") |
|||
.redirectUri("http://127.0.0.1:8080/authorized") |
|||
.scope(OidcScopes.OPENID) |
|||
.scope(OidcScopes.PROFILE) |
|||
.scope("message.read") |
|||
.scope("message.write") |
|||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) |
|||
.build(); |
|||
|
|||
// Save registered client in db as if in-memory
|
|||
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); |
|||
registeredClientRepository.save(registeredClient); |
|||
|
|||
return registeredClientRepository; |
|||
} |
|||
// @formatter:on
|
|||
|
|||
@Bean |
|||
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { |
|||
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); |
|||
} |
|||
|
|||
@Bean |
|||
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { |
|||
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); |
|||
} |
|||
|
|||
@Bean |
|||
public JWKSource<SecurityContext> jwkSource() { |
|||
RSAKey rsaKey = Jwks.generateRsa(); |
|||
JWKSet jwkSet = new JWKSet(rsaKey); |
|||
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); |
|||
} |
|||
|
|||
@Bean |
|||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { |
|||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); |
|||
} |
|||
|
|||
@Bean |
|||
public AuthorizationServerSettings authorizationServerSettings() { |
|||
return AuthorizationServerSettings.builder().build(); |
|||
} |
|||
|
|||
@Bean |
|||
public EmbeddedDatabase embeddedDatabase() { |
|||
// @formatter:off
|
|||
return new EmbeddedDatabaseBuilder() |
|||
.generateUniqueName(true) |
|||
.setType(EmbeddedDatabaseType.H2) |
|||
.setScriptEncoding("UTF-8") |
|||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") |
|||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") |
|||
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") |
|||
.build(); |
|||
// @formatter:on
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,60 @@ |
|||
/* |
|||
* Copyright 2020-2021 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample.config; |
|||
|
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|||
import org.springframework.security.core.userdetails.User; |
|||
import org.springframework.security.core.userdetails.UserDetails; |
|||
import org.springframework.security.core.userdetails.UserDetailsService; |
|||
import org.springframework.security.provisioning.InMemoryUserDetailsManager; |
|||
import org.springframework.security.web.SecurityFilterChain; |
|||
|
|||
import static org.springframework.security.config.Customizer.withDefaults; |
|||
|
|||
/** |
|||
* @author Joe Grandja |
|||
* @since 0.1.0 |
|||
*/ |
|||
@EnableWebSecurity |
|||
public class DefaultSecurityConfig { |
|||
|
|||
// @formatter:off
|
|||
@Bean |
|||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
http |
|||
.authorizeRequests(authorizeRequests -> |
|||
authorizeRequests.anyRequest().authenticated() |
|||
) |
|||
.formLogin(withDefaults()); |
|||
return http.build(); |
|||
} |
|||
// @formatter:on
|
|||
|
|||
// @formatter:off
|
|||
@Bean |
|||
UserDetailsService users() { |
|||
UserDetails user = User.withDefaultPasswordEncoder() |
|||
.username("user1") |
|||
.password("password") |
|||
.roles("USER") |
|||
.build(); |
|||
return new InMemoryUserDetailsManager(user); |
|||
} |
|||
// @formatter:on
|
|||
|
|||
} |
@ -0,0 +1,74 @@ |
|||
/* |
|||
* Copyright 2020-2021 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample.jose; |
|||
|
|||
import java.security.KeyPair; |
|||
import java.security.interfaces.ECPrivateKey; |
|||
import java.security.interfaces.ECPublicKey; |
|||
import java.security.interfaces.RSAPrivateKey; |
|||
import java.security.interfaces.RSAPublicKey; |
|||
import java.util.UUID; |
|||
|
|||
import javax.crypto.SecretKey; |
|||
|
|||
import com.nimbusds.jose.jwk.Curve; |
|||
import com.nimbusds.jose.jwk.ECKey; |
|||
import com.nimbusds.jose.jwk.OctetSequenceKey; |
|||
import com.nimbusds.jose.jwk.RSAKey; |
|||
|
|||
/** |
|||
* @author Joe Grandja |
|||
* @since 0.1.0 |
|||
*/ |
|||
public final class Jwks { |
|||
|
|||
private Jwks() { |
|||
} |
|||
|
|||
public static RSAKey generateRsa() { |
|||
KeyPair keyPair = KeyGeneratorUtils.generateRsaKey(); |
|||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); |
|||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); |
|||
// @formatter:off
|
|||
return new RSAKey.Builder(publicKey) |
|||
.privateKey(privateKey) |
|||
.keyID(UUID.randomUUID().toString()) |
|||
.build(); |
|||
// @formatter:on
|
|||
} |
|||
|
|||
public static ECKey generateEc() { |
|||
KeyPair keyPair = KeyGeneratorUtils.generateEcKey(); |
|||
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); |
|||
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); |
|||
Curve curve = Curve.forECParameterSpec(publicKey.getParams()); |
|||
// @formatter:off
|
|||
return new ECKey.Builder(curve, publicKey) |
|||
.privateKey(privateKey) |
|||
.keyID(UUID.randomUUID().toString()) |
|||
.build(); |
|||
// @formatter:on
|
|||
} |
|||
|
|||
public static OctetSequenceKey generateSecret() { |
|||
SecretKey secretKey = KeyGeneratorUtils.generateSecretKey(); |
|||
// @formatter:off
|
|||
return new OctetSequenceKey.Builder(secretKey) |
|||
.keyID(UUID.randomUUID().toString()) |
|||
.build(); |
|||
// @formatter:on
|
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
/* |
|||
* Copyright 2020-2021 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample.jose; |
|||
|
|||
import java.math.BigInteger; |
|||
import java.security.KeyPair; |
|||
import java.security.KeyPairGenerator; |
|||
import java.security.spec.ECFieldFp; |
|||
import java.security.spec.ECParameterSpec; |
|||
import java.security.spec.ECPoint; |
|||
import java.security.spec.EllipticCurve; |
|||
|
|||
import javax.crypto.KeyGenerator; |
|||
import javax.crypto.SecretKey; |
|||
|
|||
/** |
|||
* @author Joe Grandja |
|||
* @since 0.1.0 |
|||
*/ |
|||
final class KeyGeneratorUtils { |
|||
|
|||
private KeyGeneratorUtils() { |
|||
} |
|||
|
|||
static SecretKey generateSecretKey() { |
|||
SecretKey hmacKey; |
|||
try { |
|||
hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey(); |
|||
} catch (Exception ex) { |
|||
throw new IllegalStateException(ex); |
|||
} |
|||
return hmacKey; |
|||
} |
|||
|
|||
static KeyPair generateRsaKey() { |
|||
KeyPair keyPair; |
|||
try { |
|||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); |
|||
keyPairGenerator.initialize(2048); |
|||
keyPair = keyPairGenerator.generateKeyPair(); |
|||
} catch (Exception ex) { |
|||
throw new IllegalStateException(ex); |
|||
} |
|||
return keyPair; |
|||
} |
|||
|
|||
static KeyPair generateEcKey() { |
|||
EllipticCurve ellipticCurve = new EllipticCurve( |
|||
new ECFieldFp( |
|||
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")), |
|||
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"), |
|||
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291")); |
|||
ECPoint ecPoint = new ECPoint( |
|||
new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"), |
|||
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109")); |
|||
ECParameterSpec ecParameterSpec = new ECParameterSpec( |
|||
ellipticCurve, |
|||
ecPoint, |
|||
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), |
|||
1); |
|||
|
|||
KeyPair keyPair; |
|||
try { |
|||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); |
|||
keyPairGenerator.initialize(ecParameterSpec); |
|||
keyPair = keyPairGenerator.generateKeyPair(); |
|||
} catch (Exception ex) { |
|||
throw new IllegalStateException(ex); |
|||
} |
|||
return keyPair; |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
server: |
|||
port: 9000 |
|||
|
|||
logging: |
|||
level: |
|||
root: INFO |
|||
org.springframework.web: INFO |
|||
org.springframework.security: INFO |
|||
org.springframework.security.oauth2: INFO |
|||
# org.springframework.boot.autoconfigure: DEBUG |
@ -0,0 +1,137 @@ |
|||
/* |
|||
* Copyright 2020-2022 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample; |
|||
|
|||
import java.io.IOException; |
|||
|
|||
import com.gargoylesoftware.htmlunit.Page; |
|||
import com.gargoylesoftware.htmlunit.WebClient; |
|||
import com.gargoylesoftware.htmlunit.WebResponse; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlButton; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlElement; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlInput; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlPage; |
|||
import org.junit.jupiter.api.BeforeEach; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.junit.jupiter.api.extension.ExtendWith; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
|||
import org.springframework.web.util.UriComponentsBuilder; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
/** |
|||
* Integration tests for the sample Authorization Server. |
|||
* |
|||
* @author Daniel Garnier-Moiroux |
|||
*/ |
|||
@ExtendWith(SpringExtension.class) |
|||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) |
|||
@AutoConfigureMockMvc |
|||
public class DefaultAuthorizationServerApplicationTests { |
|||
private static final String REDIRECT_URI = "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc"; |
|||
|
|||
private static final String AUTHORIZATION_REQUEST = UriComponentsBuilder |
|||
.fromPath("/oauth2/authorize") |
|||
.queryParam("response_type", "code") |
|||
.queryParam("client_id", "messaging-client") |
|||
.queryParam("scope", "openid") |
|||
.queryParam("state", "some-state") |
|||
.queryParam("redirect_uri", REDIRECT_URI) |
|||
.toUriString(); |
|||
|
|||
@Autowired |
|||
private WebClient webClient; |
|||
|
|||
@BeforeEach |
|||
public void setUp() { |
|||
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(true); |
|||
this.webClient.getOptions().setRedirectEnabled(true); |
|||
this.webClient.getCookieManager().clearCookies(); // log out
|
|||
} |
|||
|
|||
@Test |
|||
public void whenLoginSuccessfulThenDisplayNotFoundError() throws IOException { |
|||
HtmlPage page = this.webClient.getPage("/"); |
|||
|
|||
assertLoginPage(page); |
|||
|
|||
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); |
|||
WebResponse signInResponse = signIn(page, "user1", "password").getWebResponse(); |
|||
assertThat(signInResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); // there is no "default" index page
|
|||
} |
|||
|
|||
@Test |
|||
public void whenLoginFailsThenDisplayBadCredentials() throws IOException { |
|||
HtmlPage page = this.webClient.getPage("/"); |
|||
|
|||
HtmlPage loginErrorPage = signIn(page, "user1", "wrong-password"); |
|||
|
|||
HtmlElement alert = loginErrorPage.querySelector("div[role=\"alert\"]"); |
|||
assertThat(alert).isNotNull(); |
|||
assertThat(alert.getTextContent()).isEqualTo("Bad credentials"); |
|||
} |
|||
|
|||
@Test |
|||
public void whenNotLoggedInAndRequestingTokenThenRedirectsToLogin() throws IOException { |
|||
HtmlPage page = this.webClient.getPage(AUTHORIZATION_REQUEST); |
|||
|
|||
assertLoginPage(page); |
|||
} |
|||
|
|||
@Test |
|||
public void whenLoggingInAndRequestingTokenThenRedirectsToClientApplication() throws IOException { |
|||
// Log in
|
|||
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); |
|||
this.webClient.getOptions().setRedirectEnabled(false); |
|||
signIn(this.webClient.getPage("/login"), "user1", "password"); |
|||
|
|||
// Request token
|
|||
WebResponse response = this.webClient.getPage(AUTHORIZATION_REQUEST).getWebResponse(); |
|||
|
|||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); |
|||
String location = response.getResponseHeaderValue("location"); |
|||
assertThat(location).startsWith(REDIRECT_URI); |
|||
assertThat(location).contains("code="); |
|||
} |
|||
|
|||
private static <P extends Page> P signIn(HtmlPage page, String username, String password) throws IOException { |
|||
HtmlInput usernameInput = page.querySelector("input[name=\"username\"]"); |
|||
HtmlInput passwordInput = page.querySelector("input[name=\"password\"]"); |
|||
HtmlButton signInButton = page.querySelector("button"); |
|||
|
|||
usernameInput.type(username); |
|||
passwordInput.type(password); |
|||
return signInButton.click(); |
|||
} |
|||
|
|||
private static void assertLoginPage(HtmlPage page) { |
|||
assertThat(page.getUrl().toString()).endsWith("/login"); |
|||
|
|||
HtmlInput usernameInput = page.querySelector("input[name=\"username\"]"); |
|||
HtmlInput passwordInput = page.querySelector("input[name=\"password\"]"); |
|||
HtmlButton signInButton = page.querySelector("button"); |
|||
|
|||
assertThat(usernameInput).isNotNull(); |
|||
assertThat(passwordInput).isNotNull(); |
|||
assertThat(signInButton.getTextContent()).isEqualTo("Sign in"); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,126 @@ |
|||
/* |
|||
* Copyright 2020-2022 the original author or authors. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* https://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package sample; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import com.gargoylesoftware.htmlunit.WebClient; |
|||
import com.gargoylesoftware.htmlunit.WebResponse; |
|||
import com.gargoylesoftware.htmlunit.html.DomElement; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; |
|||
import com.gargoylesoftware.htmlunit.html.HtmlPage; |
|||
import org.junit.jupiter.api.BeforeEach; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.junit.jupiter.api.extension.ExtendWith; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.boot.test.mock.mockito.MockBean; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; |
|||
import org.springframework.security.test.context.support.WithMockUser; |
|||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
|||
import org.springframework.web.util.UriComponentsBuilder; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.Mockito.when; |
|||
|
|||
/** |
|||
* Consent screen integration tests for the sample Authorization Server. |
|||
* |
|||
* @author Dmitriy Dubson |
|||
*/ |
|||
@ExtendWith(SpringExtension.class) |
|||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) |
|||
@AutoConfigureMockMvc |
|||
public class DefaultAuthorizationServerConsentTests { |
|||
|
|||
@Autowired |
|||
private WebClient webClient; |
|||
|
|||
@MockBean |
|||
private OAuth2AuthorizationConsentService authorizationConsentService; |
|||
|
|||
private final String redirectUri = "http://127.0.0.1/login/oauth2/code/messaging-client-oidc"; |
|||
|
|||
private final String authorizationRequestUri = UriComponentsBuilder |
|||
.fromPath("/oauth2/authorize") |
|||
.queryParam("response_type", "code") |
|||
.queryParam("client_id", "messaging-client") |
|||
.queryParam("scope", "openid message.read message.write") |
|||
.queryParam("state", "state") |
|||
.queryParam("redirect_uri", this.redirectUri) |
|||
.toUriString(); |
|||
|
|||
@BeforeEach |
|||
public void setUp() { |
|||
this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); |
|||
this.webClient.getOptions().setRedirectEnabled(true); |
|||
this.webClient.getCookieManager().clearCookies(); |
|||
when(this.authorizationConsentService.findById(any(), any())).thenReturn(null); |
|||
} |
|||
|
|||
@Test |
|||
@WithMockUser("user1") |
|||
public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException { |
|||
final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri); |
|||
assertThat(consentPage.getTitleText()).isEqualTo("Consent required"); |
|||
|
|||
List<HtmlCheckBoxInput> scopes = new ArrayList<>(); |
|||
consentPage.querySelectorAll("input[name='scope']").forEach(scope -> |
|||
scopes.add((HtmlCheckBoxInput) scope)); |
|||
for (HtmlCheckBoxInput scope : scopes) { |
|||
scope.click(); |
|||
} |
|||
|
|||
List<String> scopeIds = new ArrayList<>(); |
|||
scopes.forEach(scope -> { |
|||
assertThat(scope.isChecked()).isTrue(); |
|||
scopeIds.add(scope.getId()); |
|||
}); |
|||
assertThat(scopeIds).containsExactlyInAnyOrder("message.read", "message.write"); |
|||
|
|||
DomElement submitConsentButton = consentPage.querySelector("button[id='submit-consent']"); |
|||
this.webClient.getOptions().setRedirectEnabled(false); |
|||
|
|||
WebResponse approveConsentResponse = submitConsentButton.click().getWebResponse(); |
|||
assertThat(approveConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); |
|||
String location = approveConsentResponse.getResponseHeaderValue("location"); |
|||
assertThat(location).startsWith(this.redirectUri); |
|||
assertThat(location).contains("code="); |
|||
} |
|||
|
|||
@Test |
|||
@WithMockUser("user1") |
|||
public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException { |
|||
final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri); |
|||
assertThat(consentPage.getTitleText()).isEqualTo("Consent required"); |
|||
|
|||
DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']"); |
|||
this.webClient.getOptions().setRedirectEnabled(false); |
|||
|
|||
WebResponse cancelConsentResponse = cancelConsentButton.click().getWebResponse(); |
|||
assertThat(cancelConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value()); |
|||
String location = cancelConsentResponse.getResponseHeaderValue("location"); |
|||
assertThat(location).startsWith(this.redirectUri); |
|||
assertThat(location).contains("error=access_denied"); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,67 @@ |
|||
package io.sc.engine.rule.core.po.function; |
|||
|
|||
public class Function { |
|||
protected String id; |
|||
protected String name; |
|||
protected String signature; |
|||
protected String description; |
|||
private Boolean enable; |
|||
private String mathXml; |
|||
private String body; |
|||
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(String id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getSignature() { |
|||
return signature; |
|||
} |
|||
|
|||
public void setSignature(String signature) { |
|||
this.signature = signature; |
|||
} |
|||
|
|||
public String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
public void setDescription(String description) { |
|||
this.description = description; |
|||
} |
|||
|
|||
public Boolean getEnable() { |
|||
return enable; |
|||
} |
|||
|
|||
public void setEnable(Boolean enable) { |
|||
this.enable = enable; |
|||
} |
|||
|
|||
public String getMathXml() { |
|||
return mathXml; |
|||
} |
|||
|
|||
public void setMathXml(String mathXml) { |
|||
this.mathXml = mathXml; |
|||
} |
|||
|
|||
public String getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public void setBody(String body) { |
|||
this.body = body; |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<#assign functions=$wrapper.functions!> |
|||
<#list functions! as function> |
|||
<#if (function.enable)!> |
|||
${function.body} |
|||
</#if> |
|||
</#list> |
@ -0,0 +1,148 @@ |
|||
<template> |
|||
<div style="height: 100%"> |
|||
<w-grid |
|||
ref="gridRef" |
|||
:title="$t('re.function.grid.title')" |
|||
:config-button="true" |
|||
db-click-operation="edit" |
|||
selection="multiple" |
|||
:checkbox-selection="false" |
|||
:data-url="Environment.apiContextPath('/api/re/function')" |
|||
:pageable="false" |
|||
:sort-by="['-lastModifyDate']" |
|||
:toolbar-configure="{ noIcon: false }" |
|||
:toolbar-actions="[ |
|||
'refresh', |
|||
'separator', |
|||
'add', |
|||
'edit', |
|||
'remove', |
|||
'separator', |
|||
'view', |
|||
'separator', |
|||
{ |
|||
name: 'import', |
|||
label: $t('import'), |
|||
icon: 'file_upload', |
|||
click: () => { |
|||
importDialogRef.open(); |
|||
}, |
|||
}, |
|||
{ |
|||
extend: 'export', |
|||
click: (arg) => { |
|||
Downloader.get(Environment.apiContextPath('/api/re/function/export')); |
|||
}, |
|||
}, |
|||
]" |
|||
:columns="[ |
|||
{ width: 70, name: 'enable', label: $t('enable'), format: Formater.enableTag(), align: 'center', sortable: false }, |
|||
{ width: 120, name: 'name', label: $t('name') }, |
|||
{ width: 150, name: 'signature', label: $t('re.function.grid.entity.signature') }, |
|||
{ width: 200, name: 'description', label: $t('description') }, |
|||
{ |
|||
width: 230, |
|||
name: 'mathXml', |
|||
label: $t('re.function.grid.entity.mathXml'), |
|||
format: (value) => { |
|||
return { |
|||
componentType: 'w-math', |
|||
attrs: { |
|||
modelValue: value, |
|||
readOnly: true, |
|||
zoom: 2, |
|||
}, |
|||
}; |
|||
}, |
|||
}, |
|||
{ |
|||
width: '100%', |
|||
name: 'body', |
|||
label: $t('re.function.grid.entity.body'), |
|||
format: (value) => { |
|||
return { |
|||
componentType: 'w-code-mirror', |
|||
attrs: { |
|||
modelValue: value, |
|||
readOnly: true, |
|||
toolbar: false, |
|||
rows: 3, |
|||
lang: 'java', |
|||
}, |
|||
}; |
|||
}, |
|||
}, |
|||
]" |
|||
:editor="{ |
|||
dialog: { |
|||
width: '900px', |
|||
}, |
|||
form: { |
|||
colsNum: 1, |
|||
fields: [ |
|||
{ name: 'name', label: $t('name'), type: 'text', required: true }, |
|||
{ name: 'signature', label: $t('re.function.grid.entity.signature'), type: 'text', required: true }, |
|||
{ name: 'description', label: $t('description'), type: 'text' }, |
|||
{ |
|||
name: 'mathXml', |
|||
label: $t('re.function.grid.entity.mathXml'), |
|||
type: 'code-mirror', |
|||
rows: 5, |
|||
lang: 'xml', |
|||
toolbar: false, |
|||
defaultValue: `<mrow> |
|||
<mo>函数名</mo> |
|||
<mi>(</mi> |
|||
<mspace></mspace> |
|||
<mi>参数1</mi> |
|||
<mspace></mspace> |
|||
<mo>,</mo> |
|||
<mspace></mspace> |
|||
<mi>参数2</mi> |
|||
<mspace></mspace> |
|||
<mi>)</mi> |
|||
</mrow>`, |
|||
}, |
|||
{ name: 'body', label: $t('re.function.grid.entity.body'), type: 'code-mirror', rows: 10, lang: 'java', toolbar: false }, |
|||
{ name: 'enable', label: $t('enable'), type: 'checkbox', defaultValue: true }, |
|||
], |
|||
}, |
|||
}" |
|||
:viewer="{ |
|||
panel: { |
|||
columnNum: 1, |
|||
fields: [ |
|||
{ name: 'id', label: $t('id') }, |
|||
{ name: 'name', label: $t('name') }, |
|||
{ name: 'signature', label: $t('re.function.grid.entity.signature') }, |
|||
{ name: 'description', label: $t('description') }, |
|||
{ name: 'mathXml', label: $t('re.function.grid.entity.mathXml') }, |
|||
{ name: 'body', label: $t('re.function.grid.entity.body'), format: Formater.none() }, |
|||
{ name: 'enable', label: $t('enable'), format: Formater.none() }, |
|||
{ name: 'dataComeFrom', label: $t('dataComeFrom') }, |
|||
{ name: 'creator', label: $t('creator') }, |
|||
{ name: 'createDate', label: $t('createDate') }, |
|||
{ name: 'lastModifier', label: $t('lastModifier') }, |
|||
{ name: 'lastModifyDate', label: $t('lastModifyDate'), format: Formater.none() }, |
|||
], |
|||
}, |
|||
}" |
|||
></w-grid> |
|||
<ImportDialog |
|||
ref="importDialogRef" |
|||
@after-imported=" |
|||
() => { |
|||
gridRef.refresh(); |
|||
} |
|||
" |
|||
></ImportDialog> |
|||
</div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
import { Environment, Formater, Downloader } from 'platform-core'; |
|||
import ImportDialog from './ImportDialog.vue'; |
|||
|
|||
const gridRef = ref(); |
|||
const importDialogRef = ref(); |
|||
</script> |
@ -0,0 +1,72 @@ |
|||
<template> |
|||
<w-dialog ref="dialogRef" :title="$t('re.function.import.dialog.title')" width="600px" :can-maximize="false"> |
|||
<q-form action="post"> |
|||
<div class="row py-1"> |
|||
<div class="col-1"></div> |
|||
<div class="col-10"> |
|||
<q-file ref="fileRef" v-model="modelValue.file" :label="$t('file.single.tip')" dense outlined clearable counter accept=".json"> |
|||
<template #prepend> |
|||
<q-icon name="cloud_upload" /> |
|||
</template> |
|||
</q-file> |
|||
</div> |
|||
<div class="col-1"></div> |
|||
</div> |
|||
<div class="row py-1"> |
|||
<div class="col-1"></div> |
|||
<div class="col-10 row justify-center q-gutter-md py-2"> |
|||
<q-btn icon="bi-database-up" :label="$t('import')" color="primary" @click="importData"></q-btn> |
|||
</div> |
|||
<div class="col-1"></div> |
|||
</div> |
|||
</q-form> |
|||
</w-dialog> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { ref, reactive } from 'vue'; |
|||
import { axios, Environment } from 'platform-core'; |
|||
|
|||
const emit = defineEmits<{ |
|||
(e: 'afterImported', evt: Event): void; |
|||
}>(); |
|||
|
|||
const dialogRef = ref(); |
|||
const modelValue = reactive({ |
|||
file: undefined, |
|||
}); |
|||
const fileRef = ref(); |
|||
|
|||
const importData = () => { |
|||
axios |
|||
.post( |
|||
Environment.apiContextPath('/api/re/function/import'), |
|||
{ |
|||
file: fileRef.value.nativeEl.files[0], |
|||
}, |
|||
{ |
|||
loading: true, |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data', |
|||
}, |
|||
}, |
|||
) |
|||
.then(() => { |
|||
close(); |
|||
emit('afterImported'); |
|||
}); |
|||
}; |
|||
|
|||
const open = () => { |
|||
modelValue.file = undefined; |
|||
dialogRef.value.show(); |
|||
}; |
|||
|
|||
const close = () => { |
|||
dialogRef.value.hide(); |
|||
}; |
|||
|
|||
defineExpose({ |
|||
open, |
|||
close, |
|||
}); |
|||
</script> |
@ -1,4 +0,0 @@ |
|||
<template> |
|||
<div>functions</div> |
|||
</template> |
|||
<script setup lang="ts"></script> |
@ -0,0 +1,69 @@ |
|||
package io.sc.engine.rule.server.function.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import io.sc.engine.rule.core.po.function.Function; |
|||
import io.sc.engine.rule.server.function.converter.FunctionEntityConverter; |
|||
import io.sc.engine.rule.server.function.entity.FunctionEntity; |
|||
import io.sc.engine.rule.server.function.repository.FunctionRepository; |
|||
import io.sc.engine.rule.server.function.service.FunctionService; |
|||
import io.sc.engine.rule.server.function.vo.FunctionVo; |
|||
import io.sc.engine.rule.server.lib.converter.LibEntityConverter; |
|||
import io.sc.platform.core.util.ObjectMapper4Json; |
|||
import io.sc.platform.mvc.controller.support.RestCrudController; |
|||
import io.sc.platform.mvc.support.FileDownloader; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.ByteArrayInputStream; |
|||
import java.io.InputStream; |
|||
import java.util.List; |
|||
import java.util.Locale; |
|||
|
|||
/** |
|||
* 自定义函数管理器 Controller |
|||
* |
|||
*/ |
|||
@RestController("io.sc.engine.rule.server.function.controller.FunctionWebController") |
|||
@RequestMapping("/api/re/function") |
|||
public class FunctionWebController extends RestCrudController<FunctionVo, FunctionEntity,String, FunctionRepository, FunctionService> { |
|||
private static final Logger log = LoggerFactory.getLogger(FunctionWebController.class); |
|||
|
|||
|
|||
|
|||
@RequestMapping(value="import",method= {RequestMethod.POST}) |
|||
@ResponseBody |
|||
public void imports(@RequestParam(name="file",required=false) MultipartFile multipartFile, Locale locale) throws Exception{ |
|||
if(multipartFile!=null && !multipartFile.isEmpty()) { |
|||
List<Function> functions =null; |
|||
//解析文件
|
|||
try { |
|||
functions = ObjectMapper4Json.getMapper().readValue(multipartFile.getInputStream(), new TypeReference<List<Function>>(){}); |
|||
} catch (Exception e) { |
|||
log.error("",e); |
|||
throw e; |
|||
} |
|||
//导入
|
|||
try { |
|||
service.imports(FunctionEntityConverter.fromPo(functions)); |
|||
}catch (Exception e) { |
|||
log.error("",e); |
|||
throw e; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@GetMapping(value="export") |
|||
@ResponseBody |
|||
public void exports(HttpServletRequest request, HttpServletResponse response) throws Exception{ |
|||
List<Function> pos =service.exports(); |
|||
if(pos!=null) { |
|||
String json = ObjectMapper4Json.getMapper().writeValueAsString(pos); |
|||
InputStream in =new ByteArrayInputStream(json.getBytes("UTF-8")); |
|||
FileDownloader.download(request, response, "UserDefinedFunctions.json", in); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
package io.sc.engine.rule.server.function.converter; |
|||
|
|||
import io.sc.engine.rule.core.po.function.Function; |
|||
import io.sc.engine.rule.server.function.entity.FunctionEntity; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 自定义函数实体转换器,用于实现持久化实体和PO对象之间的互相转换 |
|||
*/ |
|||
public class FunctionEntityConverter { |
|||
/** |
|||
* 将实体转换成 PO 对象 |
|||
* @param entity 实体对象 |
|||
* @return PO 对象 |
|||
*/ |
|||
public static Function toPo(FunctionEntity entity) { |
|||
if(entity!=null) { |
|||
Function po =new Function(); |
|||
po.setId(entity.getId()); |
|||
po.setName(entity.getName()); |
|||
po.setSignature(entity.getSignature()); |
|||
po.setDescription(entity.getDescription()); |
|||
po.setEnable(entity.getEnable()); |
|||
po.setMathXml(entity.getMathXml()); |
|||
po.setBody(entity.getBody()); |
|||
return po; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 将实体集合转换成 PO 对象集合 |
|||
* @param entities 实体集合 |
|||
* @return PO 对象集合 |
|||
*/ |
|||
public static List<Function> toPo(List<? extends FunctionEntity> entities){ |
|||
if(entities!=null && entities.size()>0) { |
|||
List<Function> pos =new ArrayList<>(entities.size()); |
|||
for(FunctionEntity entity : entities) { |
|||
pos.add(toPo(entity)); |
|||
} |
|||
return pos; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 将 PO 对象转换成实体对象 |
|||
* @param po PO 对象 |
|||
* @return 实体对象 |
|||
*/ |
|||
public static FunctionEntity fromPo(Function po) { |
|||
if(po!=null) { |
|||
FunctionEntity entity =new FunctionEntity(); |
|||
entity.setId(po.getId()); |
|||
entity.setName(po.getName()); |
|||
entity.setSignature(po.getSignature()); |
|||
entity.setDescription(po.getDescription()); |
|||
entity.setEnable(po.getEnable()); |
|||
entity.setMathXml(po.getMathXml()); |
|||
entity.setBody(po.getBody()); |
|||
return entity; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 将 PO 对象集合转换成实体对象集合 |
|||
* @param pos PO 对象集合 |
|||
* @return 实体对象集合 |
|||
*/ |
|||
public static List<FunctionEntity> fromPo(List<Function> pos){ |
|||
if(pos!=null && pos.size()>0) { |
|||
List<FunctionEntity> entities =new ArrayList<>(pos.size()); |
|||
for(Function po : pos) { |
|||
entities.add(fromPo(po)); |
|||
} |
|||
return entities; |
|||
} |
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,163 @@ |
|||
package io.sc.engine.rule.server.function.entity; |
|||
|
|||
import io.sc.engine.rule.server.function.vo.FunctionVo; |
|||
import io.sc.platform.orm.DeepClone; |
|||
import io.sc.platform.orm.IdClearable; |
|||
import io.sc.platform.orm.converter.NumericBooleanConverter; |
|||
import io.sc.platform.orm.entity.AuditorEntity; |
|||
import org.hibernate.annotations.GenericGenerator; |
|||
import org.springframework.beans.BeanUtils; |
|||
|
|||
import javax.persistence.*; |
|||
import javax.validation.constraints.Size; |
|||
|
|||
/** |
|||
* 用户自定义函数实体类 |
|||
*/ |
|||
@Entity(name="io.sc.engine.rule.server.function.entity.FunctionEntity") |
|||
@Table(name="RE_FUNCTION") |
|||
public class FunctionEntity extends AuditorEntity<FunctionVo> implements DeepClone, IdClearable { |
|||
//ID,主键
|
|||
@Id |
|||
@GeneratedValue(generator = "system-uuid") |
|||
@GenericGenerator(name = "system-uuid", strategy = "uuid2") |
|||
@Column(name="ID_", length=36) |
|||
@Size(max=36) |
|||
protected String id; |
|||
|
|||
//名称
|
|||
@Column(name="NAME_", length=255) |
|||
@Size(max=255) |
|||
protected String name; |
|||
|
|||
//描述
|
|||
@Column(name="DESCRIPTION_", length=255) |
|||
@Size(max=255) |
|||
protected String description; |
|||
|
|||
//是否可用
|
|||
@Column(name="ENABLE_") |
|||
@Convert(converter=NumericBooleanConverter.class) |
|||
private Boolean enable; |
|||
|
|||
//函数签名
|
|||
@Column(name="SIGNATURE_", length=255) |
|||
@Size(max=255) |
|||
private String signature; |
|||
|
|||
//math xml
|
|||
@Column(name="MATH_XML_", length=255) |
|||
@Size(max=255) |
|||
private String mathXml; |
|||
|
|||
// 函数体
|
|||
@Column(name="BODY_") |
|||
private String body; |
|||
|
|||
@Override |
|||
public FunctionVo toVo() { |
|||
FunctionVo vo =new FunctionVo(); |
|||
super.toVo(vo); |
|||
vo.setId(this.getId()); |
|||
vo.setName(this.getName()); |
|||
vo.setSignature(this.getSignature()); |
|||
vo.setDescription(this.getDescription()); |
|||
vo.setEnable(this.getEnable()); |
|||
vo.setMathXml(this.getMathXml()); |
|||
vo.setBody(this.getBody()); |
|||
return vo; |
|||
} |
|||
|
|||
public @Size(max = 36) String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(@Size(max = 36) String id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public @Size(max = 255) String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(@Size(max = 255) String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public @Size(max = 255) String getSignature() { |
|||
return signature; |
|||
} |
|||
|
|||
public void setSignature(@Size(max = 255) String signature) { |
|||
this.signature = signature; |
|||
} |
|||
|
|||
public @Size(max = 255) String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
public void setDescription(@Size(max = 255) String description) { |
|||
this.description = description; |
|||
} |
|||
|
|||
public Boolean getEnable() { |
|||
return enable; |
|||
} |
|||
|
|||
public void setEnable(Boolean enable) { |
|||
this.enable = enable; |
|||
} |
|||
|
|||
public @Size(max = 255) String getMathXml() { |
|||
return mathXml; |
|||
} |
|||
|
|||
public void setMathXml(@Size(max = 255) String mathXml) { |
|||
this.mathXml = mathXml; |
|||
} |
|||
|
|||
public String getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public void setBody(String body) { |
|||
this.body = body; |
|||
} |
|||
|
|||
@Override |
|||
public Object deepClone() throws Exception { |
|||
FunctionEntity entity =new FunctionEntity(); |
|||
BeanUtils.copyProperties(this, entity); |
|||
return entity; |
|||
} |
|||
|
|||
@Override |
|||
public void clearId() { |
|||
this.setId(null); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
final int prime = 31; |
|||
int result = super.hashCode(); |
|||
result = prime * result + ((id == null) ? 0 : id.hashCode()); |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object obj) { |
|||
if (this == obj) |
|||
return true; |
|||
if (!super.equals(obj)) |
|||
return false; |
|||
if (getClass() != obj.getClass()) |
|||
return false; |
|||
FunctionEntity other = (FunctionEntity) obj; |
|||
if (id == null) { |
|||
if (other.id != null) |
|||
return false; |
|||
} else if (!id.equals(other.id)) |
|||
return false; |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
package io.sc.engine.rule.server.function.repository; |
|||
|
|||
import io.sc.engine.rule.server.function.entity.FunctionEntity; |
|||
import io.sc.platform.orm.repository.DaoRepository; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
@Service("io.sc.engine.rule.server.function.repository.FunctionRepository") |
|||
public interface FunctionRepository extends DaoRepository<FunctionEntity,String>{ |
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
package io.sc.engine.rule.server.function.service; |
|||
|
|||
import io.sc.engine.rule.core.po.function.Function; |
|||
import io.sc.engine.rule.core.po.lib.Lib; |
|||
import io.sc.engine.rule.server.function.entity.FunctionEntity; |
|||
import io.sc.engine.rule.server.function.repository.FunctionRepository; |
|||
import io.sc.engine.rule.server.lib.entity.LibEntity; |
|||
import io.sc.platform.orm.service.DaoService; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 自定义函数服务接口 |
|||
*/ |
|||
public interface FunctionService extends DaoService<FunctionEntity, String, FunctionRepository>{ |
|||
public void imports(List<FunctionEntity> entities) throws Exception; |
|||
public List<Function> exports() throws Exception; |
|||
} |
@ -0,0 +1,37 @@ |
|||
package io.sc.engine.rule.server.function.service.impl; |
|||
|
|||
import io.sc.engine.rule.core.po.function.Function; |
|||
import io.sc.engine.rule.server.function.converter.FunctionEntityConverter; |
|||
import io.sc.engine.rule.server.function.entity.FunctionEntity; |
|||
import io.sc.engine.rule.server.function.repository.FunctionRepository; |
|||
import io.sc.engine.rule.server.function.service.FunctionService; |
|||
import io.sc.platform.orm.service.impl.DaoServiceImpl; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import javax.transaction.Transactional; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@Service("io.sc.engine.rule.server.function.service.impl.FunctionServiceImpl") |
|||
public class FunctionServiceImpl extends DaoServiceImpl<FunctionEntity, String, FunctionRepository> implements FunctionService { |
|||
@Override |
|||
@Transactional |
|||
public void imports(List<FunctionEntity> entities) throws Exception { |
|||
if(entities==null || entities.isEmpty()){ |
|||
return; |
|||
} |
|||
for(FunctionEntity entity : entities){ |
|||
entity.clearId(); |
|||
repository.save(entity); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<Function> exports() throws Exception { |
|||
List<FunctionEntity> entities =repository.findAll(); |
|||
if(entities==null || entities.isEmpty()) { |
|||
return Collections.emptyList(); |
|||
} |
|||
return FunctionEntityConverter.toPo(entities); |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
package io.sc.engine.rule.server.function.vo; |
|||
|
|||
import io.sc.platform.orm.api.vo.AuditorVo; |
|||
|
|||
public class FunctionVo extends AuditorVo { |
|||
protected String id; |
|||
protected String name; |
|||
protected String signature; |
|||
protected String description; |
|||
private Boolean enable; |
|||
private String mathXml; |
|||
private String body; |
|||
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(String id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getSignature() { |
|||
return signature; |
|||
} |
|||
|
|||
public void setSignature(String signature) { |
|||
this.signature = signature; |
|||
} |
|||
|
|||
public String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
public void setDescription(String description) { |
|||
this.description = description; |
|||
} |
|||
|
|||
public Boolean getEnable() { |
|||
return enable; |
|||
} |
|||
|
|||
public void setEnable(Boolean enable) { |
|||
this.enable = enable; |
|||
} |
|||
|
|||
public String getMathXml() { |
|||
return mathXml; |
|||
} |
|||
|
|||
public void setMathXml(String mathXml) { |
|||
this.mathXml = mathXml; |
|||
} |
|||
|
|||
public String getBody() { |
|||
return body; |
|||
} |
|||
|
|||
public void setBody(String body) { |
|||
this.body = body; |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
[ { |
|||
"id" : "a5602f84-60b6-4bc0-bede-0984a0fa23ba", |
|||
"name" : "DIV", |
|||
"signature" : "DIV(a,b)", |
|||
"description" : "特殊除法, a 除以 b, 用于处理分母为零", |
|||
"enable" : false, |
|||
"mathXml" : "<mspace></mspace>\n<mrow>\n <mo>DIV</mo>\n <mi>(</mi>\n <mspace></mspace>\n <mi>a</mi>\n <mspace></mspace>\n <mo>,</mo>\n <mspace></mspace>\n <mi>b</mi>\n <mspace></mspace>\n <mi>)</mi>\n</mrow>\n<mspace></mspace>", |
|||
"body" : "def DIV(Double a, Double b){\n if(b==0){\n if(a>0){\n return 999;\n }else if(a<0){\n return -999;\n }else{\n return 0;\n }\n }\n}" |
|||
} ] |
@ -0,0 +1,2 @@ |
|||
@import 'quasar/src/css/index.sass' |
|||
@import 'quasar/src/css/variables.sass' |
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<div id="graph-container"></div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { onMounted, vue } from 'vue'; |
|||
import { type CellStyle, Graph, InternalEvent } from '@maxgraph/core'; |
|||
|
|||
onMounted(() => { |
|||
const container = <HTMLElement>document.getElementById('graph-container'); |
|||
// Disables the built-in context menu |
|||
InternalEvent.disableContextMenu(container); |
|||
|
|||
const graph = new Graph(container); |
|||
graph.setPanning(true); // Use mouse right button for panning |
|||
// Gets the default parent for inserting new cells. This |
|||
// is normally the first child of the root (ie. layer 0). |
|||
const parent = graph.getDefaultParent(); |
|||
|
|||
// Adds cells to the model in a single step |
|||
graph.batchUpdate(() => { |
|||
const vertex01 = graph.insertVertex({ |
|||
parent, |
|||
position: [10, 10], |
|||
size: [100, 100], |
|||
value: 'rectangle', |
|||
}); |
|||
const vertex02 = graph.insertVertex({ |
|||
parent, |
|||
position: [350, 90], |
|||
size: [50, 50], |
|||
style: { |
|||
fillColor: 'orange', |
|||
shape: 'ellipse', |
|||
verticalAlign: 'top', |
|||
verticalLabelPosition: 'bottom', |
|||
}, |
|||
value: 'ellipse', |
|||
}); |
|||
graph.insertEdge({ |
|||
parent, |
|||
source: vertex01, |
|||
target: vertex02, |
|||
value: 'edge', |
|||
style: { |
|||
edgeStyle: 'orthogonalEdgeStyle', |
|||
rounded: true, |
|||
}, |
|||
}); |
|||
}); |
|||
}); |
|||
</script> |
@ -0,0 +1,131 @@ |
|||
package io.sc.platform.core.plugins.item; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
/** |
|||
* 可导出资源插件 |
|||
*/ |
|||
public class ExportableResource{ |
|||
private String type; |
|||
private String name; |
|||
private String description; |
|||
private String[] sources; |
|||
private String selectedSource; |
|||
private String target; |
|||
private String configurationFileUrl; |
|||
|
|||
/** |
|||
* 获取可导出文件类型,支持的类型包括: |
|||
* file: 单个文件 |
|||
* dir: 目录 |
|||
* @return 可导出文件类型 |
|||
*/ |
|||
public String getType() { |
|||
return type; |
|||
} |
|||
|
|||
/** |
|||
* 设置可导出文件类型,支持的类型包括: |
|||
* file: 单个文件 |
|||
* dir: 目录 |
|||
* @param type 可导出文件类型 |
|||
*/ |
|||
public void setType(String type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
/** |
|||
* 获取可导出文件名 |
|||
* @return 可导出文件名 |
|||
*/ |
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
/** |
|||
* 设置可导出文件名 |
|||
* @param name 可导出文件名 |
|||
*/ |
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
/** |
|||
* 获取可导出文件描述 |
|||
* @return 可导出文件描述 |
|||
*/ |
|||
public String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
/** |
|||
* 设置可导出文件描述 |
|||
* @param description 可导出文件描述 |
|||
*/ |
|||
public void setDescription(String description) { |
|||
this.description = description; |
|||
} |
|||
|
|||
/** |
|||
* 获取可导出文件可选来源列表 |
|||
* @return 可导出文件可选来源列表 |
|||
*/ |
|||
public String[] getSources() { |
|||
return sources; |
|||
} |
|||
|
|||
/** |
|||
* 设置可导出文件可选来源列表 |
|||
* @param sources 可导出文件可选来源列表 |
|||
*/ |
|||
public void setSources(String[] sources) { |
|||
this.sources = sources; |
|||
} |
|||
|
|||
/** |
|||
* 获取被选择的可导出文件来源 |
|||
* @return 被选择的可导出文件来源 |
|||
*/ |
|||
public String getSelectedSource() { |
|||
return selectedSource; |
|||
} |
|||
|
|||
/** |
|||
* 设置被选择的可导出文件来源 |
|||
* @param selectedSource 被选择的可导出文件来源 |
|||
*/ |
|||
public void setSelectedSource(String selectedSource) { |
|||
this.selectedSource = selectedSource; |
|||
} |
|||
|
|||
/** |
|||
* 获取可导出文件的目标文件 |
|||
* @return 可导出文件的目标文件 |
|||
*/ |
|||
public String getTarget() { |
|||
return target; |
|||
} |
|||
|
|||
/** |
|||
* 设置可导出文件的目标文件 |
|||
* @param target 可导出文件的目标文件 |
|||
*/ |
|||
public void setTarget(String target) { |
|||
this.target = target; |
|||
} |
|||
|
|||
public String getConfigurationFileUrl() { |
|||
return configurationFileUrl; |
|||
} |
|||
|
|||
public void setConfigurationFileUrl(String configurationFileUrl) { |
|||
this.configurationFileUrl = configurationFileUrl; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "ExportableResource [type=" + type + ", name=" + name + ", description=" + description |
|||
+ ", sources=" + Arrays.toString(sources) + ", selectedSource=" + selectedSource + ", target=" + target |
|||
+ "]"; |
|||
} |
|||
} |
@ -1,5 +1,7 @@ |
|||
[ |
|||
{"name":"dir.config" ,"path":"/config" ,"autoCreate":true}, |
|||
{"name":"dir.config.https" ,"path":"/config/https" ,"autoCreate":true}, |
|||
{"name":"dir.config.oauth2" ,"path":"/config/oauth2" ,"autoCreate":true}, |
|||
{"name":"dir.log" ,"path":"/logs" ,"autoCreate":true}, |
|||
{"name":"dir.work" ,"path":"/work" ,"autoCreate":true} |
|||
] |
|||
|
@ -0,0 +1,9 @@ |
|||
[ |
|||
{ |
|||
"type" :"file", |
|||
"name" :"keystore.p12", |
|||
"description" :"keystore.p12", |
|||
"sources" :["classpath:/keystore/keystore.p12"], |
|||
"target" :"${dir.config.https}/keystore.p12" |
|||
} |
|||
] |
@ -1,57 +1,35 @@ |
|||
[appendix] |
|||
= 环境信息 |
|||
== 腾讯云外网服务器 |
|||
== 公司服务器 |
|||
=== proxmox |
|||
[options=header,cols="1,2"] |
|||
|=== |
|||
| 配置项 | 值 |
|||
| 申请日期 | 2023-6-27,有效期 1 年 |
|||
| IP地址 | 124.222.99.204 |
|||
| 操作系统 | proxmox |
|||
| IP地址 | 172.16.70.99 |
|||
| UI 服务端口 | 8006 |
|||
| 端口 | 22 |
|||
| administrator | Wspsc@139@Wspsc |
|||
| root | wspsc@139 |
|||
| wangshaoping | wspsc@139 |
|||
| root | wspsc |
|||
|=== |
|||
|
|||
=== 连接方法 |
|||
[source,bash] |
|||
---- |
|||
# ssh 连接 |
|||
ssh root@124.222.99.204 |
|||
|
|||
# scp |
|||
scp 本地文件 root@124.222.99.204:远程文件夹 |
|||
---- |
|||
|
|||
=== 部署的应用 |
|||
|=== |
|||
| 应用名称 | 端口 | 用户名 | 密码 | 访问地址 |
|||
| nexus | 8000 | admin | Yusys@fxyw888 | http://124.222.99.204:8000 |
|||
| gitlib | 9000 | wangshaoping | wspsc@139 | http://124.222.99.204:9000 |
|||
|=== |
|||
|
|||
== 公司金融云外网服务器 |
|||
=== 100 |
|||
[options=header,cols="1,2"] |
|||
|=== |
|||
| 配置项 | 值 |
|||
| IP地址 | 210.12.198.142 |
|||
| 端口 | 8000 |
|||
| root | Yusys@fxyw65 |
|||
| wangshaoping | wspsc@139 |
|||
| 操作系统 | Ubuntu 24.04 LTS |
|||
| IP地址 | 172.16.70.100 |
|||
| 端口 | 22 |
|||
| wsp | Yusystem@fxyw@888 |
|||
| root | Yusystem@fxyw@888 |
|||
|=== |
|||
|
|||
=== 连接方法 |
|||
[source,bash] |
|||
---- |
|||
# ssh 连接 |
|||
ssh root@210.12.198.142 -p 8000 |
|||
|
|||
# scp |
|||
scp -P 8000 本地文件 root@210.12.198.142:远程文件夹 |
|||
---- |
|||
|
|||
=== 部署的应用 |
|||
=== 101 |
|||
[options=header,cols="1,2"] |
|||
|=== |
|||
| 应用名称 | 端口 | 用户名 | 密码 | 访问地址 |
|||
| gitlab | 9000 | admin | Yusys@fxyw888 | http://210.12.198.142:9000 |
|||
| zentao | 80->7000 | admin | Yusys@fxyw888 | http://210.12.198.142/zentao |
|||
| 配置项 | 值 |
|||
| 操作系统 | Ubuntu 24.04 LTS |
|||
| IP地址 | 172.16.70.101 |
|||
| 端口 | 22 |
|||
| wsp | admin123 |
|||
| root | admin123 |
|||
|=== |
@ -0,0 +1,52 @@ |
|||
= 市场风险 |
|||
== 市场风险计量方法 |
|||
. 缺口分析 |
|||
. 久期分析 |
|||
. 外汇敞口分析 |
|||
. 敏感性分析 |
|||
. 风险价值(VaR) |
|||
|
|||
=== 缺口分析 |
|||
缺口分析用来衡量利率变动对银行当期收益的影响。具体而言,就是将银行的所有生息资产和付息负债按照重新定价的期限划分到不同的时间段(如一个月以内, |
|||
1至3个月,3个月至1年,1至5年,5年以上等)。在每个时间段内,将利率敏感性资产减去利率敏感性负债,再加上表外业务头寸,就得到该时段内的重新定价“缺口”。 |
|||
以该缺口乘以假定的利率变动,即得出这一利率变动对净利息收入变动的大致影响。 |
|||
|
|||
资产(包括表外业务头寸)> 负债时,就产生了正缺口,即资产敏感型缺口,此时,市场利率下降会导致银行的净利息收入下降。 |
|||
相反,当某一时段内的负债 > 资产(包括表外业务头寸)时,就产生了负缺口,即负债敏感型缺口,此时,市场利率上升会导致银行的净利息收入下降。 |
|||
|
|||
=== 久期分析 |
|||
久期分析也称为持续期分析或期限弹性分析,也是对银行资产负债利率敏感度进行分析的重要方法,主要用于衡量利率变动对银行整体经济价值的影响。 |
|||
|
|||
具体而言,就是对个时段的缺口赋予响应的敏感性权重,得到加权缺口,然后对所有时段的加权缺口进行汇总,以此估算某一给定的小幅(通常小于1%) |
|||
利率变动可能会对银行经济价值产生的影响。 |
|||
|
|||
久期(也称持续期),也叫麦考利久期,是使用加权平均数的形式计算固定收益产品的平均到期时间。用于对固定收益产品的利率敏感程度或利率弹性的衡量 |
|||
(固定收益产品的价值与市场利率成反比,久期就是用来描述这个固定收益产品价值与市场利率变化的敏感性)。 |
|||
|
|||
当市场利率发生变化时,固定收益产品(贷款、证券)的价格将发生反比例的变动,其变动程度取决于久期的长度,久期越长,其变动幅度也就越大。 |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue