mirror of
https://gitee.com/log4j/pig.git
synced 2024-12-22 20:54:25 +08:00
⚡ 优化。 优化SAS 客户端认证过程中的异常输出
This commit is contained in:
parent
825403ee3a
commit
812a6016ed
@ -69,8 +69,10 @@ public class AuthorizationServerConfiguration {
|
||||
tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
|
||||
.accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler()) // 登录成功处理器
|
||||
.errorResponseHandler(new PigAuthenticationFailureEventHandler());// 登录失败处理器
|
||||
}).authorizationEndpoint( // 授权码端点个性化confirm页面
|
||||
authorizationEndpoint -> authorizationEndpoint.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));
|
||||
}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
|
||||
oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))// 处理客户端认证异常
|
||||
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面
|
||||
.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));
|
||||
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
||||
DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
|
||||
|
@ -22,6 +22,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
|
||||
import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;
|
||||
import com.pig4cloud.pig.admin.api.vo.TokenVo;
|
||||
import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
@ -44,18 +45,19 @@ import org.springframework.security.authentication.event.LogoutSuccessEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
@ -75,7 +77,7 @@ public class PigTokenEndpoint {
|
||||
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
private final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler();
|
||||
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
|
||||
@ -135,21 +137,20 @@ public class PigTokenEndpoint {
|
||||
*/
|
||||
@SneakyThrows
|
||||
@GetMapping("/check_token")
|
||||
public void checkToken(String token, HttpServletResponse response) {
|
||||
public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) {
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
|
||||
if (StrUtil.isBlank(token)) {
|
||||
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
this.errorHttpResponseConverter.write(new OAuth2Error(OAuth2ErrorCodesExpand.TOKEN_MISSING), null,
|
||||
httpResponse);
|
||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
|
||||
new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING));
|
||||
}
|
||||
OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
|
||||
// 如果令牌不存在 返回401
|
||||
if (authorization == null) {
|
||||
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
this.errorHttpResponseConverter.write(new OAuth2Error(OAuth2ErrorCodesExpand.TOKEN_MISSING), null,
|
||||
httpResponse);
|
||||
if (authorization == null || authorization.getAccessToken() == null) {
|
||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response,
|
||||
new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN));
|
||||
}
|
||||
|
||||
Map<String, Object> claims = authorization.getAccessToken().getClaims();
|
||||
|
@ -29,8 +29,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
@ -73,15 +71,13 @@ public class PigAuthenticationFailureEventHandler implements AuthenticationFailu
|
||||
logVo.setUpdateBy(username);
|
||||
SpringContextHolder.publishEvent(new SysLogEvent(logVo));
|
||||
// 写出错误信息
|
||||
sendErrorResponse(request, response, exception);
|
||||
sendErrorResponse(response, exception);
|
||||
}
|
||||
|
||||
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException {
|
||||
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
|
||||
private void sendErrorResponse(HttpServletResponse response, AuthenticationException exception) throws IOException {
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
this.errorHttpResponseConverter.write(R.failed(error.getDescription()), MediaType.APPLICATION_JSON,
|
||||
httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||
this.errorHttpResponseConverter.write(R.failed(exception.getLocalizedMessage()), MediaType.APPLICATION_JSON,
|
||||
httpResponse);
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class R<T> implements Serializable {
|
||||
return restResult(data, CommonConstants.FAIL, msg);
|
||||
}
|
||||
|
||||
private static <T> R<T> restResult(T data, int code, String msg) {
|
||||
public static <T> R<T> restResult(T data, int code, String msg) {
|
||||
R<T> apiResult = new R<>();
|
||||
apiResult.setCode(code);
|
||||
apiResult.setData(data);
|
||||
|
@ -0,0 +1,289 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2018-2025, lengleng All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the pig4cloud.com developer nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
* Author: lengleng (wangiegie@gmail.com)
|
||||
*
|
||||
*/
|
||||
|
||||
package com.pig4cloud.pig.common.core.util;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.pig4cloud.pig.common.core.constant.CommonConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 简化{@code R<T>} 的访问操作,例子 <pre>
|
||||
* R<Integer> result = R.ok(0);
|
||||
* // 使用场景1: 链式操作: 断言然后消费
|
||||
* RetOps.of(result)
|
||||
* .assertCode(-1,r -> new RuntimeException("error "+r.getCode()))
|
||||
* .assertDataNotEmpty(r -> new IllegalStateException("oops!"))
|
||||
* .useData(System.out::println);
|
||||
*
|
||||
* // 使用场景2: 读取原始值(data),这里返回的是Optional
|
||||
* RetOps.of(result).getData().orElse(null);
|
||||
*
|
||||
* // 使用场景3: 类型转换
|
||||
* R<String> s = RetOps.of(result)
|
||||
* .assertDataNotNull(r -> new IllegalStateException("nani??"))
|
||||
* .map(i -> Integer.toHexString(i))
|
||||
* .peek();
|
||||
* </pre>
|
||||
*
|
||||
* @author CJ (power4j@outlook.com)
|
||||
* @date 2022/5/12
|
||||
* @since 4.4
|
||||
*/
|
||||
public class RetOps<T> {
|
||||
|
||||
/** 状态码为成功 */
|
||||
public static final Predicate<R<?>> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode();
|
||||
|
||||
/** 数据有值 */
|
||||
public static final Predicate<R<?>> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData());
|
||||
|
||||
/** 数据有值,并且包含元素 */
|
||||
public static final Predicate<R<?>> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData());
|
||||
|
||||
/** 状态码为成功并且有值 */
|
||||
public static final Predicate<R<?>> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA);
|
||||
|
||||
private final R<T> original;
|
||||
|
||||
// ~ 初始化
|
||||
// ===================================================================================================
|
||||
|
||||
RetOps(R<T> original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
public static <T> RetOps<T> of(R<T> original) {
|
||||
return new RetOps<>(Objects.requireNonNull(original));
|
||||
}
|
||||
|
||||
// ~ 杂项方法
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 观察原始值
|
||||
* @return R
|
||||
*/
|
||||
public R<T> peek() {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code code}的值
|
||||
* @return 返回code的值
|
||||
*/
|
||||
public int getCode() {
|
||||
return original.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code data}的值
|
||||
* @return 返回 Optional 包装的data
|
||||
*/
|
||||
public Optional<T> getData() {
|
||||
return Optional.of(original.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 有条件地读取{@code data}的值
|
||||
* @param predicate 断言函数
|
||||
* @return 返回 Optional 包装的data,如果断言失败返回empty
|
||||
*/
|
||||
public Optional<T> getDataIf(Predicate<? super R<?>> predicate) {
|
||||
return predicate.test(original) ? getData() : Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取{@code msg}的值
|
||||
* @return 返回Optional包装的 msg
|
||||
*/
|
||||
public Optional<String> getMsg() {
|
||||
return Optional.of(original.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对{@code code}的值进行相等性测试
|
||||
* @param value 基准值
|
||||
* @return 返回ture表示相等
|
||||
*/
|
||||
public boolean codeEquals(int value) {
|
||||
return original.getCode() == value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对{@code code}的值进行相等性测试
|
||||
* @param value 基准值
|
||||
* @return 返回ture表示不相等
|
||||
*/
|
||||
public boolean codeNotEquals(int value) {
|
||||
return !codeEquals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
* @return 返回ture表示成功
|
||||
* @see CommonConstants#SUCCESS
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return codeEquals(CommonConstants.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否失败
|
||||
* @return 返回ture表示失败
|
||||
*/
|
||||
public boolean notSuccess() {
|
||||
return !isSuccess();
|
||||
}
|
||||
|
||||
// ~ 链式操作
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 断言{@code code}的值
|
||||
* @param expect 预期的值
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertCode(int expect, Function<? super R<T>, ? extends Ex> func)
|
||||
throws Ex {
|
||||
if (codeNotEquals(expect)) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言成功
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertSuccess(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
return assertCode(CommonConstants.SUCCESS, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言业务数据有值
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertDataNotNull(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
if (Objects.isNull(original.getData())) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言业务数据有值,并且包含元素
|
||||
* @param func 用户函数,负责创建异常对象
|
||||
* @param <Ex> 异常类型
|
||||
* @return 返回实例,以便于继续进行链式操作
|
||||
* @throws Ex 断言失败时抛出
|
||||
*/
|
||||
public <Ex extends Exception> RetOps<T> assertDataNotEmpty(Function<? super R<T>, ? extends Ex> func) throws Ex {
|
||||
if (ObjectUtil.isNotEmpty(original.getData())) {
|
||||
throw func.apply(original);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对业务数据(data)转换
|
||||
* @param mapper 业务数据转换函数
|
||||
* @param <U> 数据类型
|
||||
* @return 返回新实例,以便于继续进行链式操作
|
||||
*/
|
||||
public <U> RetOps<U> map(Function<? super T, ? extends U> mapper) {
|
||||
R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
|
||||
return of(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对业务数据(data)转换
|
||||
* @param predicate 断言函数
|
||||
* @param mapper 业务数据转换函数
|
||||
* @param <U> 数据类型
|
||||
* @return 返回新实例,以便于继续进行链式操作
|
||||
* @see RetOps#CODE_SUCCESS
|
||||
* @see RetOps#HAS_DATA
|
||||
* @see RetOps#HAS_ELEMENT
|
||||
* @see RetOps#DATA_AVAILABLE
|
||||
*/
|
||||
public <U> RetOps<U> mapIf(Predicate<? super R<T>> predicate, Function<? super T, ? extends U> mapper) {
|
||||
R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
|
||||
return of(result);
|
||||
}
|
||||
|
||||
// ~ 数据消费
|
||||
// ===================================================================================================
|
||||
|
||||
/**
|
||||
* 消费数据,注意此方法保证数据可用
|
||||
* @param consumer 消费函数
|
||||
*/
|
||||
public void useData(Consumer<? super T> consumer) {
|
||||
consumer.accept(original.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费(错误代码匹配某个值)
|
||||
* @param consumer 消费函数
|
||||
* @param codes 错误代码集合,匹配任意一个则调用消费函数
|
||||
*/
|
||||
public void useDataOnCode(Consumer<? super T> consumer, int... codes) {
|
||||
useDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode() == c).findFirst().isPresent(), consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费(错误代码表示成功)
|
||||
* @param consumer 消费函数
|
||||
*/
|
||||
public void useDataIfSuccess(Consumer<? super T> consumer) {
|
||||
useDataIf(CODE_SUCCESS, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 条件消费
|
||||
* @param predicate 断言函数
|
||||
* @param consumer 消费函数,断言函数返回{@code true}时被调用
|
||||
* @see RetOps#CODE_SUCCESS
|
||||
* @see RetOps#HAS_DATA
|
||||
* @see RetOps#HAS_ELEMENT
|
||||
* @see RetOps#DATA_AVAILABLE
|
||||
*/
|
||||
public void useDataIf(Predicate<? super R<T>> predicate, Consumer<? super T> consumer) {
|
||||
if (predicate.test(original)) {
|
||||
consumer.accept(original.getData());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,8 @@ import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
|
||||
import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;
|
||||
import com.pig4cloud.pig.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pig.common.core.util.R;
|
||||
import com.pig4cloud.pig.common.core.util.RetOps;
|
||||
import com.pig4cloud.pig.common.security.util.OAuthClientException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@ -82,9 +83,10 @@ public class PigRemoteRegisteredClientRepository implements RegisteredClientRepo
|
||||
@SneakyThrows
|
||||
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
|
||||
public RegisteredClient findByClientId(String clientId) {
|
||||
R<SysOauthClientDetails> detailsR = clientDetailsService.getClientDetailsById(clientId,
|
||||
SecurityConstants.FROM_IN);
|
||||
SysOauthClientDetails clientDetails = detailsR.getData();
|
||||
|
||||
SysOauthClientDetails clientDetails = RetOps
|
||||
.of(clientDetailsService.getClientDetailsById(clientId, SecurityConstants.FROM_IN))
|
||||
.assertDataNotNull(result -> new OAuthClientException("clientId 不合法")).getData().get();
|
||||
|
||||
RegisteredClient.Builder builder = RegisteredClient.withId(clientDetails.getClientId())
|
||||
.clientId(clientDetails.getClientId())
|
||||
|
@ -35,4 +35,9 @@ public interface OAuth2ErrorCodesExpand {
|
||||
/** 未知的登录异常 */
|
||||
String UN_KNOW_LOGIN_ERROR = "un_know_login_error";
|
||||
|
||||
/**
|
||||
* 不合法的Token
|
||||
*/
|
||||
String INVALID_BEARER_TOKEN = "invalid_bearer_token";
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package com.pig4cloud.pig.common.security.util;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
* @description OAuthClientException 异常信息
|
||||
*/
|
||||
public class OAuthClientException extends OAuth2AuthenticationException {
|
||||
|
||||
/**
|
||||
* Constructs a <code>ScopeException</code> with the specified message.
|
||||
* @param msg the detail message.
|
||||
*/
|
||||
public OAuthClientException(String msg) {
|
||||
super(new OAuth2Error(msg), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ScopeException} with the specified message and root cause.
|
||||
* @param msg the detail message.
|
||||
* @param cause root cause
|
||||
*/
|
||||
public OAuthClientException(String msg, Throwable cause) {
|
||||
super(new OAuth2Error(msg), cause);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user