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.version=$version |
||||
application.copyright=Copyright \u00A9 2019\u20132022 |
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,11 +1,12 @@ |
|||||
// 在浏览器 window 对象中新建名为 APP 的容器变量, 用于存放平台的全局变量
|
// 在浏览器 window 对象中新建名为 APP 的容器变量, 用于存放平台的全局变量
|
||||
window.APP = {}; |
window.APP = {}; |
||||
// 全局配置
|
// 全局配置
|
||||
window.APP.configure ={ |
window.APP.configure = { |
||||
// 应用上下文路径
|
// 应用上下文路径
|
||||
webContextPath: '[(@{/})]'.startsWith('[')? '/' : '[(@{/})]', |
webContextPath: '[(@{/})]', |
||||
|
|
||||
// 默认后端 API 请求的服务地址前缀
|
// 默认后端 API 请求的服务地址前缀
|
||||
apiContextPaths: { |
apiContextPaths: { |
||||
DEFAULT: '[(@{/})]'.startsWith('[') ? 'http://localhost:8080/' : '[(@{/})]' |
DEFAULT: '[(@{/})]' |
||||
} |
} |
||||
}; |
}; |
@ -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; |
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.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RequestParam; |
import org.springframework.web.bind.annotation.RequestParam; |
||||
import org.springframework.web.bind.annotation.RestController; |
import org.springframework.web.bind.annotation.RestController; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.Map; |
||||
|
|
||||
@RestController |
@RestController |
||||
public class ShowOauth2TokenWebController { |
public class ShowOauth2TokenWebController { |
||||
@RequestMapping("/show-oauth2-token") |
@RequestMapping("/oauth2/authorized-oidc") |
||||
public Object showToken(@RequestParam("code")String code) { |
@IgnoreResponseBodyAdvice |
||||
return code; |
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 |
# Auto Configuration |
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
||||
io.sc.platform.security.oauth2.server.authorization.configure.PlatformOauth2AuthorizationServerAutoConfiguration,\ |
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 |
|
@ -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