java
复制代码
package com.kongjs.oauth.component.authentication;
import com.kongjs.common.session.authentication.RestAuthentication;
import com.kongjs.oauth.component.authentication.dto.LoginInfo;
import com.kongjs.oauth.component.password.SMO2FRawEncryptor;
import com.kongjs.oauth.exception.AuthParamException;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import java.util.*;
//@Component
public class RestAuthenticationConverter implements AuthenticationConverter {
private final JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter = new JacksonJsonHttpMessageConverter();
@Resource
private SMO2FRawEncryptor smo2FRawEncryptor;
@Override
public Authentication convert(HttpServletRequest request) {
try {
MultiValueMap<String, String> param = param(request);
Map<String, Object> additional = additional(param);
ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);
LoginInfo loginInfo = (LoginInfo) jacksonJsonHttpMessageConverter.read(LoginInfo.class, httpRequest);
if (!StringUtils.hasText(loginInfo.getTenant())) {
throw new AuthParamException("error.auth.param.tenant");
}
if (!StringUtils.hasText(loginInfo.getUsername())) {
throw new AuthParamException("error.auth.param.username");
}
if (!StringUtils.hasText(loginInfo.getPassword())) {
throw new AuthParamException("error.auth.param.password");
}
try {
String decryptPassword = smo2FRawEncryptor.decrypt(loginInfo.getPassword());
loginInfo.setPassword(decryptPassword);
} catch (Exception e) {
throw new AuthParamException("error.auth.param.password.decrypt");
}
if (!StringUtils.hasText(loginInfo.getPassword())) {
throw new AuthParamException("error.auth.param.password.decrypt");
}
//loginInfo.setDeviceType(deviceType);
//loginInfo.setDeviceName(deviceName);
loginInfo.setParams(param);
loginInfo.setAdditional(additional);
return RestAuthentication.unauthenticated(loginInfo, null);
} catch (AuthParamException e) {
throw e;
} catch (Exception e) {
throw new AuthParamException("error.auth.param.unknown");
}
}
private MultiValueMap<String, String> param(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameterMap.forEach((key, values) -> {
for (String value : values) {
parameters.add(key, value);
}
});
// response_type (REQUIRED)
String responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE);
if (!StringUtils.hasText(responseType) || parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
}
// client_id (REQUIRED)
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
}
// redirect_uri (OPTIONAL)
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
if (StringUtils.hasText(redirectUri) && parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
}
// scope (OPTIONAL)
Set<String> scopes = null;
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
// state (RECOMMENDED)
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(state) && parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
if (StringUtils.hasText(codeChallenge) && parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE);
}
// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
if (StringUtils.hasText(codeChallengeMethod)
&& parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD);
}
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) {
String prompt = parameters.getFirst("prompt");
if (StringUtils.hasText(prompt) && parameters.get("prompt").size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt");
}
}
return parameters;
}
private static Map<String, Object> additional(MultiValueMap<String, String> parameters) {
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.RESPONSE_TYPE) && !key.equals(OAuth2ParameterNames.CLIENT_ID)
&& !key.equals(OAuth2ParameterNames.REDIRECT_URI) && !key.equals(OAuth2ParameterNames.SCOPE)
&& !key.equals(OAuth2ParameterNames.STATE)) {
additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0]));
}
});
return additionalParameters;
}
private void throwError(String errorCode, String paramName) {
throw new AuthParamException(paramName);
}
}
java
复制代码
package com.kongjs.oauth.component.authentication;
import com.kongjs.common.session.authentication.RestAuthentication;
import com.kongjs.common.session.dto.AccountInfo;
import com.kongjs.oauth.component.authentication.dto.LoginInfo;
import com.kongjs.oauth.component.password.SMO2FPasswordEncoder;
import com.kongjs.oauth.exception.AuthParamException;
import com.kongjs.system.entity.SystemUser;
import com.kongjs.system.service.SystemUserService;
import jakarta.annotation.Resource;
import org.springframework.boot.security.oauth2.server.authorization.autoconfigure.servlet.OAuth2AuthorizationServerProperties;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
//@Component
public class RestAuthenticationManager implements AuthenticationManager {
@Resource
private OAuth2AuthorizationServerProperties oAuth2AuthorizationServerProperties;
@Resource
private SMO2FPasswordEncoder smo2FPasswordEncoder;
@Resource
private SystemUserService systemUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof RestAuthentication)) {
return authentication;
}
LoginInfo loginInfo = (LoginInfo) authentication.getPrincipal();
if (loginInfo == null) {
throw new AuthParamException("error.auth.param.user");
}
SystemUser user = systemUserService.getSystemUserByTenantCodeAndUserName(loginInfo.getTenant(), loginInfo.getUsername());
if (user == null) {
throw new AuthParamException("error.auth.param.user");
}
if (!smo2FPasswordEncoder.matches(loginInfo.getPassword(), user.getPassword())) {
throw new AuthParamException("error.auth.param.password.matches");
}
OAuth2AuthorizationServerProperties.Endpoint endpoint = oAuth2AuthorizationServerProperties.getEndpoint();
String authorizationEndpoint = endpoint.getAuthorizationUri();
String redirectUri = UriComponentsBuilder.fromUriString(authorizationEndpoint).queryParams(loginInfo.getParams()).build().toString();
AccountInfo accountInfo = new AccountInfo();
accountInfo.setLoginId(user.getId());
accountInfo.setTenantId(user.getTenantId());
accountInfo.setRedirectUri(redirectUri);
return RestAuthentication.authenticated(accountInfo, null, null);
}
}
java
复制代码
package com.kongjs.oauth.component.authentication.handler;
import com.kongjs.common.api.response.ApiResponse;
import com.kongjs.common.session.authentication.RestAuthentication;
import com.kongjs.common.session.dto.AccountInfo;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import tools.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@Component
public class RestAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Consumer<OAuth2User> oauth2UserHandler = (user) -> {
};
private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user);
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Resource
private JsonMapper jsonMapper;
public Consumer<OAuth2User> getOauth2UserHandler() {
return oauth2UserHandler;
}
public void setOauth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
this.oauth2UserHandler = oauth2UserHandler;
}
public Consumer<OidcUser> getOidcUserHandler() {
return oidcUserHandler;
}
public void setOidcUserHandler(Consumer<OidcUser> oidcUserHandler) {
this.oidcUserHandler = oidcUserHandler;
}
public RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
/*
* if (authentication instanceof OAuth2AuthenticationToken) {
* if (authentication.getPrincipal() instanceof OidcUser oidcUser) {
* this.oidcUserHandler.accept(oidcUser);
* } else if (authentication.getPrincipal() instanceof OAuth2User oauth2User) {
* this.oauth2UserHandler.accept(oauth2User);
* }
* }
*/
// http://localhost:3000/login?client_id=oidc-client&client_secret=secret&redirect_uri=http://127.0.0.1:3000/login/oauth/code&response_type=code&scope=openid+profile&state=xyzABC123&nonce=defGHI456&code_challenge=YHI7Jk3hF8sW3qL2pQ9mN6rT5vX8zB1cF4dG7jK2lM9nP0sR3uV6wY9zA4&code_challenge_method=S256
Map<String, Object> map = new HashMap<>();
if (authentication instanceof RestAuthentication restAuthentication) {
AccountInfo accountInfo = (AccountInfo) restAuthentication.getPrincipal();
if (accountInfo != null && StringUtils.hasText(accountInfo.getRedirectUri())) {
map.put("redirectUri", accountInfo.getRedirectUri());
}
}
map.put("sessionId", session.getId());
map.put("principal", authentication.getPrincipal());
ApiResponse apiResponse = ApiResponse.success(map);
response.setStatus(200);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8);
jsonMapper.writeValue(response.getOutputStream(), apiResponse);
}
}
java
复制代码
package com.kongjs.oauth.component.authentication.handler;
import com.kongjs.common.api.response.ApiResponse;
import com.kongjs.oauth.exception.AuthParamException;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import tools.jackson.databind.json.JsonMapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Resource
private JsonMapper jsonMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String detailMessage = "";
if (exception instanceof AuthParamException AuthParamException) {
detailMessage = AuthParamException.getMessage();
}
ApiResponse apiResponse = ApiResponse.notAuthentication();
response.setStatus(200);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8);
jsonMapper.writeValue(response.getOutputStream(), apiResponse);
}
}
java
复制代码
package com.kongjs.oauth.component.authentication.filter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
public class RestLoginFilter extends AbstractAuthenticationProcessingFilter {
public RestLoginFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {
super(requiresAuthenticationRequestMatcher, authenticationManager);
}
}
java
复制代码
package com.kongjs.oauth.config.security;
import com.kongjs.oauth.component.authentication.RestAuthenticationConverter;
import com.kongjs.oauth.component.authentication.RestAuthenticationManager;
import com.kongjs.oauth.component.authentication.filter.RestLoginFilter;
import com.kongjs.oauth.component.authentication.filter.SecurityFilter;
import com.kongjs.oauth.component.authentication.handler.RestAuthenticationFailureHandler;
import com.kongjs.oauth.component.authentication.handler.RestAuthenticationSuccessHandler;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Map;
//@Configuration(proxyBeanMethods = false)
public class RestLoginConfig {
private static final Logger log = LoggerFactory.getLogger(RestLoginConfig.class);
private static final RequestMatcher DEFAULT_PATH_REQUEST_MATCHER = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.POST, "/rest/login");
@Resource
private WebSecurityProperties webSecurityProperties;
@Resource
private RestAuthenticationConverter restAuthenticationConverter;
@Resource
private RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
@Resource
private RestAuthenticationFailureHandler restAuthenticationFailureHandler;
@Bean
public RestLoginFilter restLoginFilter(RestAuthenticationManager restAuthenticationManager) {
String restLoginApi = webSecurityProperties.getRestLoginApi();
PathPatternRequestMatcher requestMatcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.POST,
restLoginApi);
RestLoginFilter restLoginFilter = new RestLoginFilter(requestMatcher, restAuthenticationManager);
restLoginFilter.setAuthenticationConverter(restAuthenticationConverter);
restLoginFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
restLoginFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
restLoginFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
return restLoginFilter;
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
for (Map.Entry<String, CorsPathProperties> entry : webSecurityProperties.getCorsMappings().entrySet()) {
CorsPathProperties corsPathProperties = entry.getValue();
log.info("cors: path: {} config: {}", entry.getKey(), corsPathProperties);
source.registerCorsConfiguration(entry.getKey(), corsPathProperties.toCorsConfiguration());
}
return source;
}
}