Skip to content

Commit

Permalink
!95 BasicAuthFilter代码格式优化、注释补全
Browse files Browse the repository at this point in the history
Merge pull request !95 from 小灰熊/dev
  • Loading branch information
xiaoymin authored and gitee-org committed Aug 13, 2023
2 parents 136f08d + 3e76aa5 commit 445ec65
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright © 2017-2023 Knife4j([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.xiaoymin.knife4j.spring.gateway.filter;

import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/***
* 抽象basic认证过滤器
*
* @since 1.9.0
* @author <a href="mailto:[email protected]">[email protected]</a>
* 2019/02/02 19:57
*/
@Slf4j
@Getter
public abstract class AbstractBasicAuthFilter {

public static final String BASIC = "Basic";

protected List<Pattern> urlFilters = null;

protected AbstractBasicAuthFilter() {
urlFilters = new ArrayList<>();
urlFilters.add(Pattern.compile(".*?/doc\\.html.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/v2/api-docs.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/v2/api-docs-ext.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/swagger-resources.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/swagger-resources/configuration/ui.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/swagger-resources/configuration/security.*", Pattern.CASE_INSENSITIVE));
// https://gitee.com/xiaoym/knife4j/issues/I6H8BE
urlFilters.add(Pattern.compile(".*?/swagger-ui.*", Pattern.CASE_INSENSITIVE));
urlFilters.add(Pattern.compile(".*?/v3/api-docs.*", Pattern.CASE_INSENSITIVE));
}

/**
* 添加外部过滤规则,正则表达式
*
* @param rule 外部自定义规则
*/
public void addRule(String rule) {
this.urlFilters.add(Pattern.compile(rule, Pattern.CASE_INSENSITIVE));
}

/**
* 添加外部过滤规则,正则表达式
*
* @param rules
*/
public void addRule(Collection<String> rules) {
if (rules != null && !rules.isEmpty()) {
rules.forEach(this::addRule);
}
}

/**
* 判断是否匹配
*
* @param uri
* @return
*/
protected boolean match(String uri) {
// 考虑双斜杠的问题会绕过校验
if (uri != null) {
// https://gitee.com/xiaoym/knife4j/issues/I4XDYE
String newUri = uri.replaceAll("/+", "/");
for (Pattern pattern : getUrlFilters()) {
if (pattern.matcher(newUri).matches()) {
return true;
}
}
}
return false;
}

/**
* base64解码
*
* @param source
* @return
*/
protected String decodeBase64(String source) {
String decodeStr = null;
if (source != null) {
try {
byte[] bytes = Base64.getDecoder().decode(source);
decodeStr = new String(bytes);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return decodeStr;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,81 +14,96 @@
* limitations under the License.
*/


package com.github.xiaoymin.knife4j.spring.gateway.filter.basic;

import com.github.xiaoymin.knife4j.spring.gateway.conf.GlobalConstants;
import com.github.xiaoymin.knife4j.spring.gateway.filter.BasicFilter;
import lombok.Data;
import java.util.Base64;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import org.springframework.web.server.WebSession;

import java.util.Base64;
import com.github.xiaoymin.knife4j.spring.gateway.conf.GlobalConstants;
import com.github.xiaoymin.knife4j.spring.gateway.filter.AbstractBasicAuthFilter;

import lombok.Getter;
import lombok.Setter;
import reactor.core.publisher.Mono;

/**
* basic认证过滤器
*
* @author :ZhRunXin 2023/5/4 0:51
* @email :[email protected]
* @description:Security basic auth for gateway
*/
@Data
public class WebFluxSecurityBasicAuthFilter extends BasicFilter implements WebFilter {

/***
* 是否开启basic验证,默认不开启
*/
private boolean enableBasicAuth = false;

private String userName;

private String password;

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (enableBasicAuth) {
// 只拦截Knife4J资源
if (match(exchange.getRequest().getURI().toString())) {
return exchange.getSession().doOnNext(session -> {
Object attribute = session.getAttribute(GlobalConstants.KNIFE4J_BASIC_AUTH_SESSION);
if (attribute != null) {
return;
} else {
String authorization = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
ServerHttpResponse response = exchange.getResponse();
if (authorization == null) {
writeForbiddenCode(response);
} else {
String[] parts = authorization.split(" ");
if (parts.length != 2 || !parts[0].equals("Basic")) {
writeForbiddenCode(response);
} else {
String credentials = new String(Base64.getDecoder().decode(parts[1]));
String[] usernameAndPassword = credentials.split(":");
if (usernameAndPassword.length != 2 || !usernameAndPassword[0].equals(userName) || !usernameAndPassword[1].equals(password)) {
writeForbiddenCode(response);
} else {
exchange.getSession().doOnNext(session1 -> {
session1.getAttributes().put(GlobalConstants.KNIFE4J_BASIC_AUTH_SESSION, userName);
}).subscribe();
}
}
}
}
}).then(chain.filter(exchange));
}
}
return chain.filter(exchange);
}

private void writeForbiddenCode(ServerHttpResponse serverHttpResponse) {
serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
serverHttpResponse.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Area\"");
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}

@Setter
@Getter
public class WebFluxSecurityBasicAuthFilter extends AbstractBasicAuthFilter implements WebFilter {

/***
* 是否开启basic验证,默认不开启
*/
private boolean enableBasicAuth = false;

private String userName;

private String password;

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 只拦截Knife4J资源
if (this.enableBasicAuth && this.match(exchange.getRequest().getURI().toString())) {
return exchange.getSession().doOnNext(session -> this.doFilter(exchange, session))
.then(chain.filter(exchange));
}
return chain.filter(exchange);
}

/**
* 过滤处理
*
* @param exchange
* @param session
*/
private void doFilter(ServerWebExchange exchange, WebSession session) {
Object attribute = session.getAttribute(GlobalConstants.KNIFE4J_BASIC_AUTH_SESSION);
if (attribute != null) {
return;
}
String authorization = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
ServerHttpResponse response = exchange.getResponse();
if (authorization == null) {
writeForbiddenCode(response);
}

String[] parts = authorization.split(" ");
// 验证是否符合规则
if (parts.length != 2 || !parts[0].equals(BASIC)) {
writeForbiddenCode(response);
}

String credentials = new String(Base64.getDecoder().decode(parts[1]));
String[] usernameAndPassword = credentials.split(":");
// 验证用户名密码是否匹配
if (usernameAndPassword.length != 2 || !usernameAndPassword[0].equals(this.userName)
|| !usernameAndPassword[1].equals(this.password)) {
writeForbiddenCode(response);
} else {
exchange.getSession().doOnNext(
session1 -> session1.getAttributes().put(GlobalConstants.KNIFE4J_BASIC_AUTH_SESSION, this.userName))
.subscribe();
}
}

private void writeForbiddenCode(ServerHttpResponse serverHttpResponse) {
serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
serverHttpResponse.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Area\"");
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}

}

0 comments on commit 445ec65

Please sign in to comment.