62 changed files with 1032 additions and 559 deletions
@ -1,3 +1,3 @@ |
|||
application.title=\u5B87\u4FE1\u79D1\u6280-\u98CE\u9669\u7BA1\u7406\u5E73\u53F0 |
|||
application.title=\u98CE\u9669\u7BA1\u7406\u5E73\u53F0 |
|||
application.version=$version |
|||
application.copyright=Copyright \u00A9 2019\u20132022 |
@ -0,0 +1,127 @@ |
|||
import { Tools } from '@/platform/utils'; |
|||
import Axios from 'axios'; |
|||
import { Environment } from '@/platform/plugin/environment'; |
|||
|
|||
class Oauth2Manager { |
|||
/** |
|||
* 判断当前页面是否是鉴权服务器登录成功后返回的带授权码的页面 |
|||
* @returns |
|||
*/ |
|||
public static isAuthorizationCodeRedirectUri() { |
|||
return window.location.href.includes('?code='); |
|||
} |
|||
|
|||
/** |
|||
* 通过授权码获取访问令牌和刷新令牌 |
|||
*/ |
|||
public static loadTokens(callback) { |
|||
const code = Tools.getQueryStringValue('code'); |
|||
if (code) { |
|||
const gc = Environment.getConfigure(); |
|||
const axios = Axios.create({}); |
|||
axios.interceptors.request.use((config: any) => { |
|||
config.headers.Authorization = 'Basic ' + window.btoa(gc.oauth2.clientId + ':' + gc.oauth2.clientSecret); |
|||
return config; |
|||
}); |
|||
axios |
|||
.post( |
|||
Environment.apiContextPath('/oauth2/token'), |
|||
{ |
|||
code: code, |
|||
grant_type: 'authorization_code', |
|||
redirect_uri: gc.oauth2.redirectUri, |
|||
}, |
|||
{ |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data', |
|||
}, |
|||
}, |
|||
) |
|||
.then((response) => { |
|||
Oauth2Manager.setLocalAccessToken(response.data.access_token); |
|||
Oauth2Manager.setLocalRefreshToken(response.data.refresh_token); |
|||
if (callback) { |
|||
callback(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
public static revokeTokens(callback) { |
|||
const accessToken = Oauth2Manager.getLocalAccessToken(); |
|||
const refreshToken = Oauth2Manager.getLocalRefreshToken(); |
|||
|
|||
Oauth2Manager.removeLocalAccessToken(); |
|||
Oauth2Manager.removeLocalRefreshToken(); |
|||
|
|||
const gc = Environment.getConfigure(); |
|||
const axios = Axios.create({}); |
|||
axios.interceptors.request.use((config: any) => { |
|||
config.headers.Authorization = 'Basic ' + window.btoa(gc.oauth2.clientId + ':' + gc.oauth2.clientSecret); |
|||
return config; |
|||
}); |
|||
axios |
|||
.post( |
|||
Environment.apiContextPath('/oauth2/revoke'), |
|||
{ |
|||
token: accessToken, |
|||
}, |
|||
{ |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data', |
|||
}, |
|||
}, |
|||
) |
|||
.then((response) => { |
|||
if (callback) { |
|||
callback(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取本地访问令牌 |
|||
*/ |
|||
public static getLocalAccessToken() { |
|||
return localStorage.getItem('access_token'); |
|||
} |
|||
|
|||
/** |
|||
* 设置本地访问令牌 |
|||
* @param accessToken 访问令牌 |
|||
*/ |
|||
public static setLocalAccessToken(accessToken) { |
|||
localStorage.setItem('access_token', accessToken); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
*/ |
|||
public static getLocalRefreshToken() { |
|||
return localStorage.getItem('refresh_token'); |
|||
} |
|||
|
|||
/** |
|||
* 设置本地刷新令牌 |
|||
* @param refreshToken 刷新令牌 |
|||
*/ |
|||
public static setLocalRefreshToken(refreshToken) { |
|||
localStorage.setItem('refresh_token', refreshToken); |
|||
} |
|||
|
|||
/** |
|||
* 移除本地访问令牌 |
|||
*/ |
|||
public static removeLocalAccessToken() { |
|||
localStorage.removeItem('access_token'); |
|||
} |
|||
|
|||
/** |
|||
* 移除本地刷新令牌 |
|||
*/ |
|||
public static removeLocalRefreshToken() { |
|||
localStorage.removeItem('refresh_token'); |
|||
} |
|||
} |
|||
|
|||
export { Oauth2Manager }; |
@ -0,0 +1,7 @@ |
|||
<template> |
|||
<div class="flex justify-center items-center px-2 py-2" style="height: 100%">Oauth2</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
</script> |
After Width: | Height: | Size: 965 KiB |
After Width: | Height: | Size: 4.5 MiB |
After Width: | Height: | Size: 372 KiB |
After Width: | Height: | Size: 1.0 MiB |
After Width: | Height: | Size: 1.1 MiB |
@ -0,0 +1,36 @@ |
|||
= 应用入口 (War 包场景) |
|||
|
|||
== 核心原理 |
|||
|
|||
image::system-design/application-entry/001.png[,100%] |
|||
|
|||
<1> 应用入口 URL |
|||
<2> 用户登录页面 |
|||
<3> 后端处理, 返回视图。 通过查看 io.sc.platform.mvc 模块的源码,在 resources/template 目录中没有 io.sc.platform.mvc.frontend.html 模版文件,该模版文件通过以下步骤生成 |
|||
<4> io.sc.platform.mvc.frontend 是前端模块, public/index.html 文件就是首页模版文件 |
|||
<5> 构建前端,构建后的前端文件位于 dist/public/io.sc.platform.mvc.frontend 目录中 |
|||
<6> 将前端模块打包成 jar时, 将前端模块中的 dist/public/io.sc.platform.mvc.frontend/index.html 文件复制成 resources/template/io.sc.platform.mvc.frontend.html 文件 |
|||
|
|||
== index.html 解析 |
|||
|
|||
image::system-design/application-entry/002.png[,100%] |
|||
|
|||
该 index.html 文件需要加载 configure.js 文件,该文件包含前端运行时的最核心配置信息,主要包括: |
|||
|
|||
. webContextPath: 应用上下文路径 |
|||
. apiContextPaths: 默认后端 API 请求的服务地址前缀 |
|||
|
|||
[source,javascript] |
|||
---- |
|||
<script src="/configure.js" th:src="@{/configure.js}"></script> |
|||
---- |
|||
|
|||
[TIP] |
|||
<script> 的写法, 既包含 src 属性也包含 th:src 属性。 |
|||
在纯前端发布模式下: src 属性生效, th:src 属性被忽略; |
|||
在 war 发布模式下: th:src 生效且覆盖 src 属性 |
|||
|
|||
在 war 包情景下, 加载 /configure.js 文件,该文件如何获得? |
|||
|
|||
image::system-design/application-entry/003.png[,100%] |
|||
|
@ -0,0 +1,3 @@ |
|||
= 系统设计 |
|||
|
|||
include::application-entry/application-entry.adoc[leveloffset=+1] |
@ -0,0 +1,31 @@ |
|||
package io.sc.platform.lcdp.configure.api; |
|||
|
|||
public class Oauth2 { |
|||
private String clientId; // 注册客户端 ID
|
|||
private String clientSecret; // 注册客户端密码
|
|||
private String redirectUri; // 注册客户端回调 URI
|
|||
|
|||
public String getClientId() { |
|||
return clientId; |
|||
} |
|||
|
|||
public void setClientId(String clientId) { |
|||
this.clientId = clientId; |
|||
} |
|||
|
|||
public String getClientSecret() { |
|||
return clientSecret; |
|||
} |
|||
|
|||
public void setClientSecret(String clientSecret) { |
|||
this.clientSecret = clientSecret; |
|||
} |
|||
|
|||
public String getRedirectUri() { |
|||
return redirectUri; |
|||
} |
|||
|
|||
public void setRedirectUri(String redirectUri) { |
|||
this.redirectUri = redirectUri; |
|||
} |
|||
} |
@ -1,70 +0,0 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.configure; |
|||
|
|||
|
|||
import io.sc.platform.core.service.RuntimeService; |
|||
import io.sc.platform.security.SecurityProperties; |
|||
import io.sc.platform.security.service.SecurityConfigureService; |
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.boot.autoconfigure.AutoConfigureOrder; |
|||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.core.Ordered; |
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|||
import org.springframework.security.web.SecurityFilterChain; |
|||
|
|||
/** |
|||
* 框架安全自动配置类 |
|||
*/ |
|||
@Configuration(proxyBeanMethods = false) |
|||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 200) |
|||
@EnableConfigurationProperties(SecurityProperties.class) |
|||
public class PlatformWebSecurityAutoConfiguration { |
|||
private static final Logger log = LoggerFactory.getLogger(PlatformWebSecurityAutoConfiguration.class); |
|||
|
|||
private RuntimeService runtimeService; |
|||
private SecurityConfigureService securityConfigureService; |
|||
private SecurityProperties securityProperties; |
|||
|
|||
public PlatformWebSecurityAutoConfiguration(RuntimeService runtimeService, SecurityConfigureService securityConfigureService, SecurityProperties securityProperties){ |
|||
this.runtimeService =runtimeService; |
|||
this.securityConfigureService =securityConfigureService; |
|||
this.securityProperties =securityProperties; |
|||
} |
|||
|
|||
@Bean |
|||
public SecurityFilterChain auth2WebSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
if(!runtimeService.isReady()) { |
|||
return http.csrf(csrfConfigurer -> { |
|||
csrfConfigurer.disable(); |
|||
}).build(); |
|||
} |
|||
|
|||
return http |
|||
.authorizeRequests(authorizeRequests -> { |
|||
authorizeRequests.antMatchers(securityConfigureService.getIgnoredUrls()).permitAll(); |
|||
authorizeRequests.anyRequest().authenticated(); |
|||
}) |
|||
.csrf(csrfConfigurer -> { |
|||
csrfConfigurer.disable(); |
|||
}) |
|||
.cors(corsConfigurer->{ |
|||
corsConfigurer.configurationSource(securityConfigureService.getCorsConfigurationSource()); |
|||
}) |
|||
.formLogin(formLoginConfigurer -> { |
|||
SecurityProperties.FormLogin formLogin =securityProperties.getFormLogin(); |
|||
formLoginConfigurer.loginPage(formLogin.getLoginPage()).permitAll(); |
|||
formLoginConfigurer.loginProcessingUrl(formLogin.getLoginProcessingUrl()).permitAll(); |
|||
formLoginConfigurer.failureUrl(securityProperties.getFormLogin().getFailureUrl()).permitAll(); |
|||
}) |
|||
.logout(logoutConfigurer -> { |
|||
logoutConfigurer.logoutUrl(securityProperties.getLogout().getLogoutUrl()); |
|||
logoutConfigurer.logoutSuccessUrl(securityProperties.getLogout().getLogoutSuccessUrl()); |
|||
}) |
|||
.headers(headersConfigurer -> { |
|||
headersConfigurer.frameOptions().disable(); |
|||
}) |
|||
.build(); |
|||
} |
|||
} |
@ -1,102 +0,0 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.configure; |
|||
|
|||
import com.nimbusds.jose.jwk.JWKSet; |
|||
import com.nimbusds.jose.jwk.RSAKey; |
|||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet; |
|||
import com.nimbusds.jose.jwk.source.JWKSource; |
|||
import com.nimbusds.jose.proc.SecurityContext; |
|||
import io.sc.platform.core.util.RSAUtil; |
|||
import io.sc.platform.security.oauth2.server.authorization.bean.PlatformRegisteredClientRepository; |
|||
import io.sc.platform.security.oauth2.server.authorization.jpa.repository.ClientRepository; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.AutoConfigureOrder; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|||
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.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.jwt.JwtDecoder; |
|||
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.web.SecurityFilterChain; |
|||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; |
|||
|
|||
import java.security.KeyPair; |
|||
import java.security.NoSuchAlgorithmException; |
|||
import java.security.interfaces.RSAPrivateKey; |
|||
import java.security.interfaces.RSAPublicKey; |
|||
import java.util.UUID; |
|||
|
|||
@Configuration(proxyBeanMethods = false) |
|||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 500) |
|||
public class SecurityAutoConfiguration { |
|||
@Autowired private ClientRepository clientRepository; |
|||
|
|||
@Bean |
|||
@Order(1) |
|||
public SecurityFilterChain auth2AuthorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); |
|||
// Enable OpenID Connect 1.0
|
|||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults()); |
|||
// Redirect to the login page when not authenticated from the
|
|||
// authorization endpoint
|
|||
http.exceptionHandling((exceptions) -> exceptions |
|||
.authenticationEntryPoint( |
|||
new LoginUrlAuthenticationEntryPoint("/login")) |
|||
); |
|||
// Accept access tokens for User Info and/or Client Registration
|
|||
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); |
|||
return http.build(); |
|||
} |
|||
|
|||
@Bean |
|||
@Order(2) |
|||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) |
|||
throws Exception { |
|||
http |
|||
.authorizeHttpRequests((authorize) -> authorize |
|||
.anyRequest().authenticated() |
|||
) |
|||
// Form login handles the redirect to the login page from the
|
|||
// authorization server filter chain
|
|||
.formLogin(Customizer.withDefaults()); |
|||
|
|||
return http.build(); |
|||
} |
|||
|
|||
@Bean |
|||
public RegisteredClientRepository registeredClientRepository() { |
|||
return new PlatformRegisteredClientRepository(clientRepository); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException { |
|||
KeyPair keyPair = RSAUtil.generateKeyPair(); |
|||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); |
|||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); |
|||
RSAKey rsaKey = new RSAKey.Builder(publicKey) |
|||
.privateKey(privateKey) |
|||
.keyID(UUID.randomUUID().toString()) |
|||
.build(); |
|||
JWKSet jwkSet = new JWKSet(rsaKey); |
|||
return new ImmutableJWKSet<>(jwkSet); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { |
|||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public AuthorizationServerSettings authorizationServerSettings() { |
|||
return AuthorizationServerSettings.builder().build(); |
|||
} |
|||
} |
@ -1,149 +0,0 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.configure; |
|||
|
|||
import com.nimbusds.jose.jwk.JWKSet; |
|||
import com.nimbusds.jose.jwk.RSAKey; |
|||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet; |
|||
import com.nimbusds.jose.jwk.source.JWKSource; |
|||
import com.nimbusds.jose.proc.SecurityContext; |
|||
import io.sc.platform.core.service.RuntimeService; |
|||
import io.sc.platform.core.util.RSAUtil; |
|||
import io.sc.platform.security.oauth2.server.authorization.bean.PlatformOAuth2AuthorizationConsentService; |
|||
import io.sc.platform.security.oauth2.server.authorization.bean.PlatformOAuth2AuthorizationService; |
|||
import io.sc.platform.security.oauth2.server.authorization.jpa.repository.AuthorizationConsentRepository; |
|||
import io.sc.platform.security.oauth2.server.authorization.jpa.repository.AuthorizationRepository; |
|||
import io.sc.platform.security.oauth2.server.authorization.jpa.repository.ClientRepository; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.autoconfigure.AutoConfigureOrder; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
|||
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.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.OAuth2AuthorizationConsentService; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
|||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; |
|||
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; |
|||
|
|||
import java.security.KeyPair; |
|||
import java.security.NoSuchAlgorithmException; |
|||
import java.security.interfaces.RSAPrivateKey; |
|||
import java.security.interfaces.RSAPublicKey; |
|||
import java.util.UUID; |
|||
|
|||
@Configuration(proxyBeanMethods = false) |
|||
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 500) |
|||
public class SecurityAutoConfiguration2 { |
|||
@Autowired private RuntimeService runtimeService; |
|||
@Autowired private ClientRepository clientRepository; |
|||
@Autowired private AuthorizationRepository authorizationRepository; |
|||
@Autowired private AuthorizationConsentRepository authorizationConsentRepository; |
|||
|
|||
@Bean |
|||
@Order(1) |
|||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); |
|||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) |
|||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
|||
http |
|||
// Redirect to the login page when not authenticated from the
|
|||
// authorization endpoint
|
|||
.exceptionHandling((exceptions) -> exceptions |
|||
.authenticationEntryPoint( |
|||
new LoginUrlAuthenticationEntryPoint("/login")) |
|||
) |
|||
// Accept access tokens for User Info and/or Client Registration
|
|||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); |
|||
|
|||
return http.build(); |
|||
} |
|||
|
|||
@Bean |
|||
@Order(2) |
|||
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { |
|||
http |
|||
.authorizeHttpRequests((authorize) -> authorize |
|||
.anyRequest().authenticated() |
|||
) |
|||
// Form login handles the redirect to the login page from the
|
|||
// authorization server filter chain
|
|||
.formLogin(Customizer.withDefaults()); |
|||
|
|||
return http.build(); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public RegisteredClientRepository registeredClientRepository() { |
|||
//RegisteredClientRepository result =new PlatformRegisteredClientRepository(clientRepository);
|
|||
//return result;
|
|||
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) |
|||
.clientId("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(); |
|||
|
|||
return new InMemoryRegisteredClientRepository(registeredClient); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public OAuth2AuthorizationConsentService oAuth2AuthorizationConsentService(RegisteredClientRepository registeredClientRepository){ |
|||
return new PlatformOAuth2AuthorizationConsentService(authorizationConsentRepository,registeredClientRepository); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public OAuth2AuthorizationService oAuth2AuthorizationService(RegisteredClientRepository registeredClientRepository){ |
|||
return new PlatformOAuth2AuthorizationService(authorizationRepository,registeredClientRepository); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException { |
|||
KeyPair keyPair = RSAUtil.generateKeyPair(); |
|||
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); |
|||
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); |
|||
RSAKey rsaKey = new RSAKey.Builder(publicKey) |
|||
.privateKey(privateKey) |
|||
.keyID(UUID.randomUUID().toString()) |
|||
.build(); |
|||
JWKSet jwkSet = new JWKSet(rsaKey); |
|||
return new ImmutableJWKSet<>(jwkSet); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { |
|||
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); |
|||
} |
|||
|
|||
@Bean |
|||
@ConditionalOnMissingBean |
|||
public AuthorizationServerSettings authorizationServerSettings() { |
|||
return AuthorizationServerSettings.builder().build(); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.configure.support; |
|||
|
|||
import io.sc.platform.security.support.SecurityUser; |
|||
import io.sc.platform.security.util.SecurityUtil; |
|||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; |
|||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; |
|||
|
|||
/** |
|||
* 自定义 JWT 令牌生成器 |
|||
*/ |
|||
public class PlatformOauth2TokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> { |
|||
@Override |
|||
public void customize(JwtEncodingContext context) { |
|||
Object principal = context.getPrincipal().getPrincipal(); |
|||
if(principal instanceof SecurityUser){ |
|||
SecurityUser user =(SecurityUser)principal; |
|||
context.getClaims().claims(map ->{ |
|||
map.putAll(SecurityUtil.securityUser2map(user)); |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -1,13 +1,48 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import io.sc.platform.core.annotation.IgnoreResponseBodyAdvice; |
|||
import io.sc.platform.core.response.ResponseWrapper; |
|||
import io.sc.platform.core.response.SuccessResponseWrapper; |
|||
import io.sc.platform.core.util.ObjectMapper4Json; |
|||
import io.sc.platform.security.oauth2.Token; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.web.client.RestTemplateBuilder; |
|||
import org.springframework.http.HttpEntity; |
|||
import org.springframework.http.HttpHeaders; |
|||
import org.springframework.http.HttpRequest; |
|||
import org.springframework.http.MediaType; |
|||
import org.springframework.http.client.ClientHttpRequestExecution; |
|||
import org.springframework.http.client.ClientHttpRequestInterceptor; |
|||
import org.springframework.http.client.ClientHttpResponse; |
|||
import org.springframework.http.client.support.BasicAuthenticationInterceptor; |
|||
import org.springframework.util.LinkedMultiValueMap; |
|||
import org.springframework.util.MultiValueMap; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.springframework.web.client.RestTemplate; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.Map; |
|||
|
|||
@RestController |
|||
public class ShowOauth2TokenWebController { |
|||
@RequestMapping("/show-oauth2-token") |
|||
public Object showToken(@RequestParam("code")String code) { |
|||
return code; |
|||
@RequestMapping("/oauth2/authorized-oidc") |
|||
@IgnoreResponseBodyAdvice |
|||
public Object showToken(@RequestParam("code")String code) throws Exception { |
|||
MultiValueMap<String,String> body =new LinkedMultiValueMap<>(); |
|||
body.set("code",code); |
|||
body.set("grant_type","authorization_code"); |
|||
body.set("redirect_uri","http://localhost:8080/oauth2/authorized-oidc"); |
|||
|
|||
HttpHeaders headers =new HttpHeaders(); |
|||
headers.setContentType(MediaType.MULTIPART_FORM_DATA); |
|||
|
|||
HttpEntity<MultiValueMap<String,String>> request =new HttpEntity(body,headers); |
|||
|
|||
RestTemplate restTemplate = new RestTemplateBuilder().basicAuthentication("platform-oidc","secret").build(); |
|||
Token token =restTemplate.postForObject("http://localhost:8080/oauth2/token",request, Token.class); |
|||
return token; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,58 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.password; |
|||
|
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.core.context.SecurityContextHolder; |
|||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
|||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
|||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
|||
import org.springframework.security.web.authentication.AuthenticationConverter; |
|||
import org.springframework.util.LinkedMultiValueMap; |
|||
import org.springframework.util.MultiValueMap; |
|||
import org.springframework.util.StringUtils; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
public class PasswordGrantAuthenticationConverter implements AuthenticationConverter { |
|||
@Override |
|||
public Authentication convert(HttpServletRequest request) { |
|||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); |
|||
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) { |
|||
return null; |
|||
} |
|||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); |
|||
MultiValueMap<String, String> parameters = getParameters(request); |
|||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); |
|||
if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) { |
|||
throw new OAuth2AuthenticationException("username must not be empty!"); |
|||
} |
|||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); |
|||
if (!StringUtils.hasText(password) || |
|||
parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) { |
|||
throw new OAuth2AuthenticationException("password must not be empty!"); |
|||
} |
|||
Map<String, Object> additionalParameters = new HashMap<>(); |
|||
parameters.forEach((key, value) -> { |
|||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && |
|||
!key.equals(OAuth2ParameterNames.CLIENT_ID) && |
|||
!key.equals(OAuth2ParameterNames.CODE)) { |
|||
additionalParameters.put(key, value.get(0)); |
|||
} |
|||
}); |
|||
return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters); |
|||
} |
|||
|
|||
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) { |
|||
Map<String, String[]> parameterMap = request.getParameterMap(); |
|||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size()); |
|||
parameterMap.forEach((key, values) -> { |
|||
if (values.length > 0) { |
|||
for (String value : values) { |
|||
parameters.add(key, value); |
|||
} |
|||
} |
|||
}); |
|||
return parameters; |
|||
} |
|||
} |
@ -0,0 +1,139 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.password; |
|||
|
|||
import org.springframework.security.authentication.AuthenticationProvider; |
|||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.core.AuthenticationException; |
|||
import org.springframework.security.core.userdetails.UserDetails; |
|||
import org.springframework.security.core.userdetails.UserDetailsService; |
|||
import org.springframework.security.crypto.password.PasswordEncoder; |
|||
import org.springframework.security.oauth2.core.*; |
|||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
|||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; |
|||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; |
|||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; |
|||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
|||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; |
|||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; |
|||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; |
|||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; |
|||
import org.springframework.util.Assert; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.security.Principal; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.stream.Collectors; |
|||
import java.util.stream.Stream; |
|||
|
|||
public class PasswordGrantAuthenticationProvider implements AuthenticationProvider { |
|||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; |
|||
|
|||
@Resource private UserDetailsService userDetailsService; |
|||
@Resource private PasswordEncoder passwordEncoder; |
|||
|
|||
private final OAuth2AuthorizationService authorizationService; |
|||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator; |
|||
|
|||
public PasswordGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) { |
|||
Assert.notNull(authorizationService, "authorizationService cannot be null"); |
|||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); |
|||
this.authorizationService = authorizationService; |
|||
this.tokenGenerator = tokenGenerator; |
|||
} |
|||
|
|||
@Override |
|||
public Authentication authenticate(Authentication authentication) throws AuthenticationException { |
|||
PasswordGrantAuthenticationToken passwordGrantAuthenticationToken =(PasswordGrantAuthenticationToken) authentication; |
|||
Map<String, Object> additionalParameters = passwordGrantAuthenticationToken.getAdditionalParameters(); |
|||
// 授权类型
|
|||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType(); |
|||
// 用户名
|
|||
String username = (String)additionalParameters.get(OAuth2ParameterNames.USERNAME); |
|||
// 密码
|
|||
String password = (String)additionalParameters.get(OAuth2ParameterNames.PASSWORD); |
|||
// Scope
|
|||
String requestScopesStr = (String)additionalParameters.get(OAuth2ParameterNames.SCOPE); |
|||
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet()); |
|||
|
|||
OAuth2ClientAuthenticationToken clientPrincipal =getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken); |
|||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); |
|||
|
|||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) { |
|||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); |
|||
} |
|||
|
|||
UserDetails userDetails = userDetailsService.loadUserByUsername(username); |
|||
if(!passwordEncoder.matches(password,userDetails.getPassword())){ |
|||
throw new OAuth2AuthenticationException("password error!"); |
|||
} |
|||
|
|||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(userDetails,clientPrincipal,userDetails.getAuthorities()); |
|||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() |
|||
.registeredClient(registeredClient) |
|||
.principal(usernamePasswordAuthenticationToken) |
|||
.authorizationServerContext(AuthorizationServerContextHolder.getContext()) |
|||
.authorizationGrantType(authorizationGrantType) |
|||
.authorizedScopes(requestScopeSet) |
|||
.authorizationGrant(passwordGrantAuthenticationToken); |
|||
|
|||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) |
|||
.principalName(clientPrincipal.getName()) |
|||
.authorizedScopes(requestScopeSet) |
|||
.attribute(Principal.class.getName(), usernamePasswordAuthenticationToken) |
|||
.authorizationGrantType(authorizationGrantType); |
|||
|
|||
// 访问令牌
|
|||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); |
|||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); |
|||
if (generatedAccessToken == null) { |
|||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, |
|||
"The token generator failed to generate the access token.", ERROR_URI); |
|||
throw new OAuth2AuthenticationException(error); |
|||
} |
|||
|
|||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, |
|||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), |
|||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); |
|||
if (generatedAccessToken instanceof ClaimAccessor) { |
|||
authorizationBuilder.token(accessToken, (metadata) -> |
|||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims())); |
|||
} else { |
|||
authorizationBuilder.accessToken(accessToken); |
|||
} |
|||
|
|||
// 刷新令牌
|
|||
OAuth2RefreshToken refreshToken = null; |
|||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { |
|||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); |
|||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); |
|||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { |
|||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "The token generator failed to generate the refresh token.", ERROR_URI); |
|||
throw new OAuth2AuthenticationException(error); |
|||
} |
|||
authorizationBuilder.refreshToken((OAuth2RefreshToken) generatedRefreshToken); |
|||
} |
|||
|
|||
OAuth2Authorization authorization = authorizationBuilder.build(); |
|||
this.authorizationService.save(authorization); |
|||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters); |
|||
} |
|||
|
|||
@Override |
|||
public boolean supports(Class<?> authentication) { |
|||
return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication); |
|||
} |
|||
|
|||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) { |
|||
OAuth2ClientAuthenticationToken clientPrincipal = null; |
|||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) { |
|||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal(); |
|||
} |
|||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { |
|||
return clientPrincipal; |
|||
} |
|||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
package io.sc.platform.security.oauth2.server.authorization.password; |
|||
|
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
|||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; |
|||
|
|||
import java.util.Map; |
|||
|
|||
public class PasswordGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { |
|||
public PasswordGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) { |
|||
super(AuthorizationGrantType.PASSWORD,clientPrincipal, additionalParameters); |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"includes":[ |
|||
"io/sc/platform/security/oauth2/server/authorization/i18n/initializer" |
|||
] |
|||
} |
@ -1,7 +1,3 @@ |
|||
# Auto Configuration |
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
|||
io.sc.platform.security.oauth2.server.authorization.configure.PlatformOauth2AuthorizationServerAutoConfiguration,\ |
|||
io.sc.platform.security.oauth2.server.authorization.configure.PlatformWebSecurityAutoConfiguration |
|||
|
|||
#org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
|||
#io.sc.platform.security.oauth2.server.authorization.configure.SecurityAutoConfiguration |
|||
io.sc.platform.security.oauth2.server.authorization.configure.PlatformOauth2AuthorizationServerAutoConfiguration |
@ -0,0 +1,2 @@ |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.name=Oauth2 Registered Client Initializer |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.description=Oauth2 Registered Client Initializer |
@ -0,0 +1,2 @@ |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.name=Oauth2 \u8A3B\u518A\u5BA2\u6236\u7AEF\u521D\u59CB\u5316\u5668 |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.description=Oauth2 \u8A3B\u518A\u5BA2\u6236\u7AEF\u521D\u59CB\u5316\u5668 |
@ -0,0 +1,2 @@ |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.name=Oauth2 \u6CE8\u518C\u5BA2\u6237\u7AEF\u521D\u59CB\u5316\u5668 |
|||
io.sc.platform.security.oauth2.server.authorization.initializer.Oauth2RegisteredClientInitializer.description=Oauth2 \u6CE8\u518C\u5BA2\u6237\u7AEF\u521D\u59CB\u5316\u5668 |
@ -0,0 +1,9 @@ |
|||
package io.sc.platform.security; |
|||
|
|||
/** |
|||
* 认证模式 |
|||
*/ |
|||
public enum AuthenticationMode { |
|||
LOGIN_FORM, // 登录表单
|
|||
OAUTH2; // Oauth2
|
|||
} |
@ -0,0 +1,50 @@ |
|||
package io.sc.platform.security.oauth2; |
|||
|
|||
|
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
|
|||
public class Token { |
|||
@JsonProperty("access_token") |
|||
private String accessToken; |
|||
|
|||
@JsonProperty("refresh_token") |
|||
private String refreshToken; |
|||
|
|||
@JsonProperty("token_type") |
|||
private String tokenType; |
|||
|
|||
@JsonProperty("expires_in") |
|||
private String expiresIn; |
|||
|
|||
public String getAccessToken() { |
|||
return accessToken; |
|||
} |
|||
|
|||
public void setAccessToken(String accessToken) { |
|||
this.accessToken = accessToken; |
|||
} |
|||
|
|||
public String getRefreshToken() { |
|||
return refreshToken; |
|||
} |
|||
|
|||
public void setRefreshToken(String refreshToken) { |
|||
this.refreshToken = refreshToken; |
|||
} |
|||
|
|||
public String getTokenType() { |
|||
return tokenType; |
|||
} |
|||
|
|||
public void setTokenType(String tokenType) { |
|||
this.tokenType = tokenType; |
|||
} |
|||
|
|||
public String getExpiresIn() { |
|||
return expiresIn; |
|||
} |
|||
|
|||
public void setExpiresIn(String expiresIn) { |
|||
this.expiresIn = expiresIn; |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
package io.sc.platform.security.support; |
|||
|
|||
import com.fasterxml.jackson.core.JacksonException; |
|||
import com.fasterxml.jackson.core.JsonParser; |
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.DeserializationContext; |
|||
import com.fasterxml.jackson.databind.JsonDeserializer; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.MissingNode; |
|||
import org.springframework.security.core.GrantedAuthority; |
|||
import org.springframework.security.core.authority.SimpleGrantedAuthority; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.List; |
|||
|
|||
public class SecurityRoleDeserializer extends JsonDeserializer<SecurityRole> { |
|||
@Override |
|||
public SecurityRole deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { |
|||
ObjectMapper mapper = (ObjectMapper) p.getCodec(); |
|||
JsonNode tree = mapper.readTree(p); |
|||
|
|||
String id =readJsonNode(tree,"id").asText(); |
|||
String code =readJsonNode(tree,"code").asText(); |
|||
String name = readJsonNode(tree, "name").asText(); |
|||
|
|||
return new SecurityRole(id,code,name); |
|||
} |
|||
|
|||
private JsonNode readJsonNode(JsonNode jsonNode, String field){ |
|||
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
package io.sc.platform.security.support; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
|||
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) |
|||
@JsonDeserialize(using = SecurityRoleDeserializer.class) |
|||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public interface SecurityRoleMix { |
|||
} |
@ -0,0 +1,100 @@ |
|||
package io.sc.platform.security.support; |
|||
|
|||
import com.fasterxml.jackson.core.JacksonException; |
|||
import com.fasterxml.jackson.core.JsonParser; |
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.DeserializationContext; |
|||
import com.fasterxml.jackson.databind.JsonDeserializer; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.node.MissingNode; |
|||
import io.sc.platform.core.util.ObjectMapper4Json; |
|||
import org.springframework.security.core.GrantedAuthority; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
public class SecurityUserDeserializer extends JsonDeserializer<SecurityUser> { |
|||
private static final TypeReference<List<SecurityRole>> SECURITY_ROLE_SET = new TypeReference<List<SecurityRole>>(){}; |
|||
|
|||
@Override |
|||
public SecurityUser deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { |
|||
ObjectMapper mapper = (ObjectMapper) p.getCodec(); |
|||
JsonNode tree = mapper.readTree(p); |
|||
|
|||
String username =readString(tree,"username"); |
|||
String password =readString(tree,"password"); |
|||
password =password==null?"":password; |
|||
boolean accountNonExpired = readBoolean(tree, "accountNonExpired"); |
|||
boolean credentialsNonExpired = readBoolean(tree, "credentialsNonExpired"); |
|||
boolean accountNonLocked = readBoolean(tree, "accountNonLocked"); |
|||
boolean enabled =readBoolean(tree,"enabled"); |
|||
|
|||
String userId =readString(tree,"userId"); |
|||
String loginName =readString(tree,"loginName"); |
|||
String userName =readString(tree,"userName"); |
|||
|
|||
String defaultAppId =readString(tree,"defaultAppId"); |
|||
String defaultAppCode =readString(tree,"defaultAppCode"); |
|||
String defaultAppName =readString(tree,"defaultAppName"); |
|||
String defaultRoleId =readString(tree,"defaultRoleId"); |
|||
String defaultRoleCode =readString(tree,"defaultRoleCode"); |
|||
String defaultRoleName =readString(tree,"defaultRoleName"); |
|||
String defaultOrgId =readString(tree,"defaultOrgId"); |
|||
String defaultOrgCode =readString(tree,"defaultOrgCode"); |
|||
String defaultOrgName =readString(tree,"defaultOrgName"); |
|||
String parentOrgId =readString(tree,"parentOrgId"); |
|||
String parentOrgCode =readString(tree,"parentOrgCode"); |
|||
String parentOrgName =readString(tree,"parentOrgName"); |
|||
String rootOrgId =readString(tree,"rootOrgId"); |
|||
String rootOrgCode =readString(tree,"rootOrgCode"); |
|||
String rootOrgName =readString(tree,"rootOrgName"); |
|||
String corporationCode =readString(tree,"corporationCode"); |
|||
String corporationName =readString(tree,"corporationName"); |
|||
|
|||
List<? extends GrantedAuthority> authorities = new ArrayList<>(); |
|||
JsonNode authoritiesNode =tree.get("authorities"); |
|||
if(authoritiesNode!=null && authoritiesNode.isArray() && authoritiesNode.size()>1){ |
|||
String json =authoritiesNode.get(1).toString(); |
|||
authorities = ObjectMapper4Json.getMapper().readValue(json,SECURITY_ROLE_SET); |
|||
} |
|||
|
|||
SecurityUser securityUser =new SecurityUser(username,password,enabled,accountNonExpired,credentialsNonExpired,accountNonLocked,authorities); |
|||
securityUser.setUserId(userId); |
|||
securityUser.setLoginName(loginName); |
|||
securityUser.setUserName(userName); |
|||
securityUser.setDefaultAppId(defaultAppId); |
|||
securityUser.setDefaultAppCode(defaultAppCode); |
|||
securityUser.setDefaultAppName(defaultAppName); |
|||
securityUser.setDefaultRoleId(defaultRoleId); |
|||
securityUser.setDefaultRoleCode(defaultRoleCode); |
|||
securityUser.setDefaultRoleName(defaultRoleName); |
|||
securityUser.setDefaultOrgId(defaultOrgId); |
|||
securityUser.setDefaultOrgCode(defaultOrgCode); |
|||
securityUser.setDefaultOrgName(defaultOrgName); |
|||
securityUser.setParentOrgId(parentOrgId); |
|||
securityUser.setParentOrgCode(parentOrgCode); |
|||
securityUser.setParentOrgName(parentOrgName); |
|||
securityUser.setRootOrgId(rootOrgId); |
|||
securityUser.setRootOrgCode(rootOrgCode); |
|||
securityUser.setRootOrgName(rootOrgName); |
|||
securityUser.setCorporationCode(corporationCode); |
|||
securityUser.setCorporationName(corporationName); |
|||
|
|||
return securityUser; |
|||
} |
|||
|
|||
private String readString(JsonNode jsonNode, String field){ |
|||
JsonNode node =jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); |
|||
if(node.isNull()){ |
|||
return null; |
|||
} |
|||
return node.asText(); |
|||
} |
|||
|
|||
private boolean readBoolean(JsonNode jsonNode, String field){ |
|||
JsonNode node =jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance(); |
|||
return node.asBoolean(); |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
package io.sc.platform.security.support; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonAutoDetect; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
|||
|
|||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) |
|||
@JsonDeserialize(using = SecurityUserDeserializer.class) |
|||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public interface SecurityUserMix { |
|||
} |
Loading…
Reference in new issue