Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,24 @@ Halo 2.0 的 OAuth2 第三方登录插件。

1. 如果认证失败,回调地址请使用 `http` 尝试。
2. <SITE_URL> 是不包含 `console` 的。
3. 如果你用于部署的服务器无法访问 GitHub,那 GitHub 认证会失败,其它同理,请先确认连通性。
3. 如果你用于部署的服务器无法访问 GitHub,那 GitHub 认证会失败,其它同理,请先确认连通性。请尝试配置代理。

## 代理配置(可选)

如果你部署的 Halo 服务器无法直接访问 GitHub、GitLab 或 Gitee 的 API,你可以配置代理。

配置路径示例:`${Halo 工作目录}/plugins/configs/plugin-oauth2.yaml`。配置示例如下所示:

```yaml
oauth2:
proxy:
enabled: true # 是否启用代理
host: "host.halo.run" # 代理服务器主机名
port: 6666 # 代理服务器端口
username: "proxy-username" # 代理服务器用户名(可选)
password: "proxy-password" # 代理服务器密码(可选)
connect-timeout-millis: 10000 # 连接超时时间,单位:毫秒(可选)
```

## 开发环境

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
import io.netty.channel.ChannelOption;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginReactiveAuthenticationManager;
Expand All @@ -20,10 +22,14 @@
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;
import run.halo.app.security.AuthenticationSecurityWebFilter;

/**
Expand All @@ -39,8 +45,35 @@ public class HaloOAuth2AuthenticationWebFilter implements AuthenticationSecurity
private final WebFilter delegate;

public HaloOAuth2AuthenticationWebFilter(Oauth2LoginConfiguration configuration,
ServerSecurityContextRepository securityContextRepository) {
ServerSecurityContextRepository securityContextRepository,
OAuth2Properties oAuth2Properties) {

var proxy = oAuth2Properties.getProxy();
var client = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000);
if (proxy.isEnabled()) {
client = client.proxy(typeSpec -> {
var builder = typeSpec.type(ProxyProvider.Proxy.HTTP)
.host(proxy.getHost())
.port(proxy.getPort());
if (StringUtils.hasText(proxy.getUsername())) {
builder.username(proxy.getUsername())
.password(u -> proxy.getPassword());
}
if (proxy.getConnectTimeoutMillis() != null
&& proxy.getConnectTimeoutMillis() > 0) {
builder.connectTimeoutMillis(proxy.getConnectTimeoutMillis());
}
}
);
}
var webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(client))
.build();

var accessTokenResponseClient = new WebClientReactiveAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setWebClient(webClient);

var oauth2AuthManager = new OAuth2LoginReactiveAuthenticationManager(
accessTokenResponseClient,
new DefaultReactiveOAuth2UserService()
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/run/halo/oauth/OAuth2Properties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package run.halo.oauth;

import jakarta.validation.Valid;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;

@Data
@ConfigurationProperties("oauth2")
@Validated
public class OAuth2Properties implements Validator {

@Valid
@NestedConfigurationProperty
private final Proxy proxy = new Proxy();

@Override
public boolean supports(Class<?> clazz) {
return OAuth2Properties.class == clazz;
}

@Override
public void validate(Object target, Errors errors) {
var properties = (OAuth2Properties) target;
var p = properties.getProxy();
if (p.isEnabled()) {
if (p.getPort() <= 0 || p.getPort() > 65535) {
errors.rejectValue("proxy.port", "Invalid port number",
"Port must be between 1 and 65535");
}
if (!StringUtils.hasText(p.getHost())) {
errors.rejectValue("proxy.host", "Host cannot be blank when proxy is enabled");
}
if (p.getConnectTimeoutMillis() != null && p.getConnectTimeoutMillis() <= 0) {
errors.rejectValue("proxy.connectTimeoutMillis",
"Connect timeout must be greater than zero");
}
}
}

@Data
public static class Proxy {

/**
* Whether to enable the proxy.
*/
private boolean enabled;

/**
* The host of the proxy server.
*/
private String host;

/**
* The port of the proxy server.
*/
private int port;

/**
* The username for the proxy server.
*/
private String username;

/**
* The password for the proxy server.
*/
private String password;

/**
* The connection timeout in milliseconds.
*/
private Long connectTimeoutMillis;

}
}
2 changes: 2 additions & 0 deletions src/main/java/run/halo/oauth/Oauth2LoginConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
Expand All @@ -29,6 +30,7 @@
@Getter
@Configuration
@EnableAsync
@EnableConfigurationProperties(OAuth2Properties.class)
public class Oauth2LoginConfiguration {
private final ServerWebExchangeMatcher authenticationMatcher;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
Expand Down