diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java index 9e77f15f3..74e5d3f9c 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java @@ -1,20 +1,17 @@ /* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * - * Copyright (c) 2018-2025, lengleng All rights reserved. + * 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 * - * 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) + * 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.pig4cloud.pigx.auth; @@ -26,7 +23,7 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author lengleng - * @date 2020-02-08 认证授权中心 + * @date 2018年06月21日 认证授权中心 */ @EnablePigxFeignClients @EnableDiscoveryClient diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java index fde86b55f..b13b7cff8 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java @@ -1,123 +1,159 @@ /* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * - * Copyright (c) 2018-2025, lengleng All rights reserved. + * 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 * - * 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) + * 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.pig4cloud.pigx.auth.config; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint; -import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator; -import com.pig4cloud.pigx.common.security.grant.ResourceOwnerCustomeAppTokenGranter; -import com.pig4cloud.pigx.common.security.service.PigxCustomTokenServices; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; +import com.pig4cloud.pigx.auth.support.CustomeOAuth2AccessTokenGenerator; +import com.pig4cloud.pigx.auth.support.core.CustomeOAuth2TokenCustomizer; +import com.pig4cloud.pigx.auth.support.core.FormIdentityLoginConfigurer; +import com.pig4cloud.pigx.auth.support.core.PigDaoAuthenticationProvider; +import com.pig4cloud.pigx.auth.support.handler.PigAuthenticationFailureEventHandler; +import com.pig4cloud.pigx.auth.support.handler.PigAuthenticationSuccessEventHandler; +import com.pig4cloud.pigx.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter; +import com.pig4cloud.pigx.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider; +import com.pig4cloud.pigx.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter; +import com.pig4cloud.pigx.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.handler.FormAuthenticationFailureHandler; +import com.pig4cloud.pigx.common.security.handler.SsoLogoutSuccessHandler; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.CompositeTokenGranter; -import org.springframework.security.oauth2.provider.TokenGranter; -import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +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.token.DelegatingOAuth2TokenGenerator; +import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; +import org.springframework.security.oauth2.server.authorization.web.authentication.*; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import java.util.ArrayList; import java.util.Arrays; /** * @author lengleng - * @date 2018/6/22 认证服务器配置 + * @date 2022/5/27 + * + * 认证服务器配置 */ @Configuration -@AllArgsConstructor -@EnableAuthorizationServer -public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { +@RequiredArgsConstructor +public class AuthorizationServerConfiguration { - private final ClientDetailsService pigxClientDetailsServiceImpl; + private final OAuth2AuthorizationService authorizationService; - private final AuthenticationManager authenticationManagerBean; + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, + PigAuthenticationSuccessEventHandler successEventHandler, + PigAuthenticationFailureEventHandler failureEventHandler) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); - private final AuthorizationCodeServices authorizationCodeServices; + http.apply(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 + tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter + .accessTokenResponseHandler(successEventHandler) // 登录成功处理器 + .errorResponseHandler(failureEventHandler);// 登录失败处理器 + }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 + oAuth2ClientAuthenticationConfigurer.errorResponseHandler(failureEventHandler))// 处理客户端认证异常 + .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面 + .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI))); - private final AuthenticationManager authenticationManager; + DefaultSecurityFilterChain securityFilterChain = http.authorizeHttpRequests(authorizeRequests -> { + // 自定义接口、端点暴露 + authorizeRequests.antMatchers("/token/**", "/actuator/**", "/css/**", "/error").permitAll(); + authorizeRequests.anyRequest().authenticated(); + }).apply(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 + .authorizationServerSettings( + AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build())) + // 授权码登录的登录页个性化 + .and().apply(new FormIdentityLoginConfigurer()).and().build(); - private final TokenStore redisTokenStore; - - private final TokenEnhancer tokenEnhancer; - - private final ObjectMapper objectMapper; - - @Override - @SneakyThrows - public void configure(ClientDetailsServiceConfigurer clients) { - clients.withClientDetails(pigxClientDetailsServiceImpl); - } - - @Override - public void configure(AuthorizationServerSecurityConfigurer oauthServer) { - oauthServer.allowFormAuthenticationForClients() - .authenticationEntryPoint(new PigxCommenceAuthExceptionEntryPoint(objectMapper)) - .checkTokenAccess("isAuthenticated()"); - } - - @Override - public void configure(AuthorizationServerEndpointsConfigurer endpoints) { - endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenServices(tokenServices()) - .tokenStore(redisTokenStore).tokenEnhancer(tokenEnhancer) - .authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManagerBean) - .reuseRefreshTokens(false).pathMapping("/oauth/confirm_access", "/token/confirm_access") - .exceptionTranslator(new PigxWebResponseExceptionTranslator()); - - // 注入自定义认证类型 - setTokenGranter(endpoints); + // 注入自定义授权模式实现 + addCustomOAuth2GrantAuthenticationProvider(http); + return securityFilterChain; } /** - * 自定义 APP 认证类型 - * @param endpoints AuthorizationServerEndpointsConfigurer + * 令牌生成规则实现
+ * client:username:uuid + * @return OAuth2TokenGenerator */ - private void setTokenGranter(AuthorizationServerEndpointsConfigurer endpoints) { - // 获取默认授权类型 - TokenGranter tokenGranter = endpoints.getTokenGranter(); - ArrayList tokenGranters = new ArrayList<>(Arrays.asList(tokenGranter)); - ResourceOwnerCustomeAppTokenGranter resourceOwnerCustomeAppTokenGranter = new ResourceOwnerCustomeAppTokenGranter( - authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), - endpoints.getOAuth2RequestFactory()); - tokenGranters.add(resourceOwnerCustomeAppTokenGranter); - CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(tokenGranters); - endpoints.tokenGranter(compositeTokenGranter); + @Bean + public OAuth2TokenGenerator oAuth2TokenGenerator() { + CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator(); + // 注入Token 增加关联用户信息 + accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer()); + return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator()); } @Bean - public PigxCustomTokenServices tokenServices() { - PigxCustomTokenServices tokenServices = new PigxCustomTokenServices(); - tokenServices.setTokenStore(redisTokenStore); - tokenServices.setSupportRefreshToken(true); - tokenServices.setReuseRefreshToken(false); - tokenServices.setClientDetailsService(pigxClientDetailsServiceImpl); - tokenServices.setTokenEnhancer(tokenEnhancer); - return tokenServices; + public AuthenticationFailureHandler authenticationFailureHandler() { + return new FormAuthenticationFailureHandler(); + } + + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return new SsoLogoutSuccessHandler(); + } + + /** + * request -> xToken 注入请求转换器 + * @return DelegatingAuthenticationConverter + */ + private AuthenticationConverter accessTokenRequestConverter() { + return new DelegatingAuthenticationConverter(Arrays.asList( + new OAuth2ResourceOwnerPasswordAuthenticationConverter(), + new OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(), + new OAuth2ClientCredentialsAuthenticationConverter(), + new OAuth2AuthorizationCodeAuthenticationConverter(), + new OAuth2AuthorizationCodeRequestAuthenticationConverter())); + } + + /** + * 注入授权模式实现提供方 + * + * 1. 密码模式
+ * 2. 短信登录
+ * + */ + @SuppressWarnings("unchecked") + private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { + AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); + OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); + + OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator()); + + OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator()); + + // 处理 UsernamePasswordAuthenticationToken + http.authenticationProvider(new PigDaoAuthenticationProvider()); + // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken + http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); + // 处理 OAuth2ResourceOwnerSmsAuthenticationToken + http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); } } diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/WebSecurityConfigurer.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/WebSecurityConfigurer.java deleted file mode 100644 index 0483fae23..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/WebSecurityConfigurer.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * - * 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.pigx.auth.config; - -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import com.pig4cloud.pigx.common.security.component.PigxDaoAuthenticationProvider; -import com.pig4cloud.pigx.common.security.grant.CustomAppAuthenticationProvider; -import com.pig4cloud.pigx.common.security.handler.FormAuthenticationFailureHandler; -import com.pig4cloud.pigx.common.security.handler.MobileLoginSuccessHandler; -import com.pig4cloud.pigx.common.security.handler.SsoLogoutSuccessHandler; -import com.pig4cloud.pigx.common.security.handler.TenantSavedRequestAwareAuthenticationSuccessHandler; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.springframework.context.MessageSource; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.annotation.Order; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; - -/** - * @author lengleng - * @date 2018/6/22 认证相关配置 - */ -@Primary -@Order(90) -@Configuration -@RequiredArgsConstructor -public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { - - @Override - @SneakyThrows - protected void configure(HttpSecurity http) { - http.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form") - .successHandler(tenantSavedRequestAwareAuthenticationSuccessHandler()) - .failureHandler(authenticationFailureHandler()).and().logout() - .logoutSuccessHandler(logoutSuccessHandler()).deleteCookies("JSESSIONID").invalidateHttpSession(true) - .and().authorizeRequests().antMatchers("/token/**", "/actuator/**", "/mobile/**").permitAll() - .anyRequest().authenticated().and().csrf().disable(); - } - - /** - * 自定义 provider 列表注入 - * @param auth AuthenticationManagerBuilder - */ - @Override - protected void configure(AuthenticationManagerBuilder auth) { - PigxDaoAuthenticationProvider daoAuthenticationProvider = new PigxDaoAuthenticationProvider(); - daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); - // 注入自定义异常国际化文件处理器 - MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource"); - daoAuthenticationProvider.setMessageSource(messageSource); - - // 处理默认的密码模式认证 - auth.authenticationProvider(daoAuthenticationProvider); - // 自定义的认证模式 - CustomAppAuthenticationProvider customAppAuthenticationProvider = new CustomAppAuthenticationProvider(); - customAppAuthenticationProvider.setMessageSource(messageSource); - auth.authenticationProvider(customAppAuthenticationProvider); - } - - /** - * 不拦截静态资源 - * @param web - */ - @Override - public void configure(WebSecurity web) { - web.ignoring().antMatchers("/favicon.ico", "/css/**", "/error"); - } - - @Bean - @Override - @SneakyThrows - public AuthenticationManager authenticationManagerBean() { - return super.authenticationManagerBean(); - } - - @Bean - public AuthenticationFailureHandler authenticationFailureHandler() { - return new FormAuthenticationFailureHandler(); - } - - @Bean - public LogoutSuccessHandler logoutSuccessHandler() { - return new SsoLogoutSuccessHandler(); - } - - @Bean - public AuthenticationSuccessHandler mobileLoginSuccessHandler() { - return new MobileLoginSuccessHandler(); - } - - /** - * 具备租户传递能力 - * @return AuthenticationSuccessHandler - */ - @Bean - public AuthenticationSuccessHandler tenantSavedRequestAwareAuthenticationSuccessHandler() { - return new TenantSavedRequestAwareAuthenticationSuccessHandler(); - } - - /** - * 密码处理器 - * @return 动态密码处理器 {类型}密文 - */ - @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigTokenEndpoint.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigTokenEndpoint.java new file mode 100644 index 000000000..527a5197d --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigTokenEndpoint.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.auth.endpoint; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.TemporalAccessorUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; +import com.pig4cloud.pigx.admin.api.entity.SysTenant; +import com.pig4cloud.pigx.admin.api.feign.RemoteClientDetailsService; +import com.pig4cloud.pigx.admin.api.feign.RemoteTenantService; +import com.pig4cloud.pigx.admin.api.vo.TokenVo; +import com.pig4cloud.pigx.auth.support.handler.PigAuthenticationFailureEventHandler; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.core.util.RetOps; +import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import com.pig4cloud.pigx.common.security.annotation.Inner; +import com.pig4cloud.pigx.common.security.util.OAuth2ErrorCodesExpand; +import com.pig4cloud.pigx.common.security.util.OAuthClientException; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.CacheManager; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.security.authentication.event.LogoutSuccessEvent; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +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.resource.InvalidBearerTokenException; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +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; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author lengleng + * @date 2019/2/1 删除token端点 + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/token") +public class PigTokenEndpoint { + + private final PigAuthenticationFailureEventHandler authenticationFailureHandler; + + private final OAuth2AuthorizationService authorizationService; + + private final RemoteClientDetailsService clientDetailsService; + + private final RedisTemplate redisTemplate; + + private final RemoteTenantService tenantService; + + private final CacheManager cacheManager; + + /** + * 认证页面 + * @param modelAndView + * @param error 表单登录失败处理回调的错误信息 + * @return ModelAndView + */ + @GetMapping("/login") + public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) { + modelAndView.setViewName("ftl/login"); + modelAndView.addObject("error", error); + + R> tenantList = tenantService.list(SecurityConstants.FROM_IN); + modelAndView.addObject("tenantList", tenantList.getData()); + return modelAndView; + } + + @GetMapping("/confirm_access") + public ModelAndView confirm(Principal principal, ModelAndView modelAndView, + @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId, + @RequestParam(OAuth2ParameterNames.SCOPE) String scope, + @RequestParam(OAuth2ParameterNames.STATE) String state) { + SysOauthClientDetails clientDetails = RetOps + .of(clientDetailsService.getClientDetailsById(clientId, SecurityConstants.FROM_IN)).getData() + .orElseThrow(() -> new OAuthClientException("clientId 不合法")); + + Set authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope()); + modelAndView.addObject("clientId", clientId); + modelAndView.addObject("state", state); + modelAndView.addObject("scopeList", authorizedScopes); + modelAndView.addObject("principalName", principal.getName()); + modelAndView.setViewName("ftl/confirm"); + return modelAndView; + } + + /** + * 退出并删除token + * @param authHeader Authorization + */ + @DeleteMapping("/logout") + public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) { + if (StrUtil.isBlank(authHeader)) { + return R.ok(); + } + + String tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim(); + return removeToken(tokenValue); + } + + /** + * 校验token + * @param token 令牌 + */ + @SneakyThrows + @GetMapping("/check_token") + public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) { + ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + + if (StrUtil.isBlank(token)) { + httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); + this.authenticationFailureHandler.onAuthenticationFailure(request, response, + new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING)); + return; + } + OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); + + // 如果令牌不存在 返回401 + if (authorization == null || authorization.getAccessToken() == null) { + this.authenticationFailureHandler.onAuthenticationFailure(request, response, + new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN)); + } + + } + + /** + * 令牌管理调用 + * @param token token + */ + @Inner + @DeleteMapping("/{token}") + public R removeToken(@PathVariable("token") String token) { + OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); + if (authorization == null) { + return R.ok(); + } + + OAuth2Authorization.Token accessToken = authorization.getAccessToken(); + if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) { + return R.ok(); + } + // 清空用户信息 + cacheManager.getCache(CacheConstants.USER_DETAILS).evict(authorization.getPrincipalName()); + // 清空access token + authorizationService.remove(authorization); + // 处理自定义退出事件,保存相关日志 + SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken( + authorization.getPrincipalName(), authorization.getRegisteredClientId()))); + return R.ok(); + } + + /** + * 查询token + * @param params 分页参数 + * @return + */ + @Inner + @PostMapping("/page") + public R tokenList(@RequestBody Map params) { + // 根据分页参数获取对应数据 + String key = String.format("%s::*", CacheConstants.PROJECT_OAUTH_ACCESS); + int current = MapUtil.getInt(params, CommonConstants.CURRENT); + int size = MapUtil.getInt(params, CommonConstants.SIZE); + Set keys = redisTemplate.keys(key); + List pages = keys.stream().skip((current - 1) * size).limit(size).collect(Collectors.toList()); + Page result = new Page(current, size); + + List tokenVoList = redisTemplate.opsForValue().multiGet(pages).stream().map(obj -> { + OAuth2Authorization authorization = (OAuth2Authorization) obj; + TokenVo tokenVo = new TokenVo(); + tokenVo.setClientId(authorization.getRegisteredClientId()); + tokenVo.setId(authorization.getId()); + tokenVo.setUsername(authorization.getPrincipalName()); + OAuth2Authorization.Token accessToken = authorization.getAccessToken(); + tokenVo.setAccessToken(accessToken.getToken().getTokenValue()); + + String expiresAt = TemporalAccessorUtil.format(accessToken.getToken().getExpiresAt(), + DatePattern.NORM_DATETIME_PATTERN); + tokenVo.setExpiresAt(expiresAt); + + String issuedAt = TemporalAccessorUtil.format(accessToken.getToken().getIssuedAt(), + DatePattern.NORM_DATETIME_PATTERN); + tokenVo.setIssuedAt(issuedAt); + return tokenVo; + }).collect(Collectors.toList()); + result.setRecords(tokenVoList); + result.setTotal(keys.size()); + return R.ok(result); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java deleted file mode 100644 index 64cf631b5..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * - * 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.pigx.auth.endpoint; - -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.pig4cloud.pigx.admin.api.entity.SysTenant; -import com.pig4cloud.pigx.admin.api.feign.RemoteTenantService; -import com.pig4cloud.pigx.auth.service.PigxTokenDealServiceImpl; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.security.annotation.Inner; -import com.pig4cloud.pigx.common.security.util.SecurityUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.util.List; -import java.util.Map; - -/** - * @author lengleng - * @date 2018/6/24 删除token端点 - */ -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/token") -public class PigxTokenEndpoint { - - private final ClientDetailsService clientDetailsService; - - private final PigxTokenDealServiceImpl dealService; - - private final RemoteTenantService tenantService; - - /** - * 认证页面 - * @param modelAndView - * @param error 表单登录失败处理回调的错误信息 - * @return ModelAndView - */ - @GetMapping("/login") - public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) { - R> tenantList = tenantService.list(SecurityConstants.FROM_IN); - modelAndView.setViewName("ftl/login"); - modelAndView.addObject("error", error); - modelAndView.addObject("tenantList", tenantList.getData()); - return modelAndView; - } - - /** - * 确认授权页面 - * @param request - * @param session - * @param modelAndView - * @return - */ - @GetMapping("/confirm_access") - public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) { - Map scopeList = (Map) request.getAttribute("scopes"); - modelAndView.addObject("scopeList", scopeList.keySet()); - - Object auth = session.getAttribute("authorizationRequest"); - if (auth != null) { - AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth; - ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); - modelAndView.addObject("app", clientDetails.getAdditionalInformation()); - modelAndView.addObject("user", SecurityUtils.getUser()); - } - - modelAndView.setViewName("ftl/confirm"); - return modelAndView; - } - - /** - * 退出token - * @param authHeader Authorization - */ - @DeleteMapping("/logout") - public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) { - if (StrUtil.isBlank(authHeader)) { - return R.ok(Boolean.FALSE, "退出失败,token 为空"); - } - - String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim(); - return delToken(tokenValue); - } - - /** - * 令牌管理调用 - * @param token token - * @return - */ - @Inner - @DeleteMapping("/{token}") - public R delToken(@PathVariable("token") String token) { - return dealService.removeToken(token); - } - - /** - * 查询token - * @param page 分页参数 - * @param username 用户名 - * @return - */ - @Inner - @GetMapping("/page") - public R tokenList(Page page, String username) { - // 根据username 查询 token 列表 - if (StrUtil.isNotBlank(username)) { - return dealService.queryTokenByUsername(page, username); - } - - return dealService.queryToken(page); - } - - /** - * 查询token - * @param token - * @return - */ - @Inner - @GetMapping("/query-token") - public R queryToken(String token) { - return dealService.queryTokenInfo(token); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLockEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLockEventHandler.java deleted file mode 100644 index c6336f901..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLockEventHandler.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.pigx.auth.handler; - -import com.pig4cloud.pigx.admin.api.feign.RemoteUserService; -import com.pig4cloud.pigx.common.core.constant.CacheConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import com.pig4cloud.pigx.common.data.resolver.ParamResolver; -import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.concurrent.TimeUnit; - -/** - * @author lengleng - * @date 2022-04-11 - * - * 登录操作次数锁定功能 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxAuthenticationFailureLockEventHandler implements AuthenticationFailureHandler { - - private final RedisTemplate redisTemplate; - - private final KeyStrResolver tenantKeyStrResolver; - - private final RemoteUserService userService; - - /** - * 密码错误超过 5 此自动锁定 - *

- * @param authenticationException 登录的authentication 对象 - * @param authentication 登录的authenticationException 对象 - * @param request 请求 - * @param response 响应 - */ - @Async - @Override - @SneakyThrows - public void handle(AuthenticationException authenticationException, Authentication authentication, - HttpServletRequest request, HttpServletResponse response) { - // 只处理账号密码错误异常 - if (!(authenticationException instanceof BadCredentialsException)) { - return; - } - - String username = authentication.getName(); - String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username); - Long deltaTimes = ParamResolver.getLong("LOGIN_ERROR_TIMES", 5L); - Long times = redisTemplate.opsForValue().increment(key); - - // 自动过期时间 - Long deltaTime = ParamResolver.getLong("DELTA_TIME", 1L); - redisTemplate.expire(key, deltaTime, TimeUnit.HOURS); - - if (deltaTimes <= times) { - userService.lockUser(username, SecurityConstants.FROM_IN); - } - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLogEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLogEventHandler.java deleted file mode 100644 index 3f9c5ffce..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureLogEventHandler.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.pigx.auth.handler; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; -import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import com.pig4cloud.pigx.common.core.util.WebUtils; -import com.pig4cloud.pigx.common.log.util.LogTypeEnum; -import com.pig4cloud.pigx.common.log.util.SysLogUtils; -import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author lengleng - * @date 2018/10/8 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxAuthenticationFailureLogEventHandler implements AuthenticationFailureHandler { - - private final RemoteLogService logService; - - private final KeyStrResolver tenantKeyStrResolver; - - /** - * 异步处理,登录失败方法 - *

- * @param authenticationException 登录的authentication 对象 - * @param authentication 登录的authenticationException 对象 - * @param request 请求 - * @param response 响应 - */ - @Async - @Override - @SneakyThrows - public void handle(AuthenticationException authenticationException, Authentication authentication, - HttpServletRequest request, HttpServletResponse response) { - String username = authentication.getName(); - SysLogDTO sysLog = SysLogUtils.getSysLog(request, username); - - String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); - if (StrUtil.isNotBlank(startTimeStr)) { - Long startTime = Long.parseLong(startTimeStr); - Long endTime = System.currentTimeMillis(); - sysLog.setTime(endTime - startTime); - } - sysLog.setTitle(username + "用户登录"); - sysLog.setLogType(LogTypeEnum.ERROR.getType()); - sysLog.setParams(username); - sysLog.setException(authenticationException.getLocalizedMessage()); - String header = request.getHeader(HttpHeaders.AUTHORIZATION); - sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A")); - sysLog.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); - - logService.saveLog(sysLog, SecurityConstants.FROM_IN); - - log.info("用户:{} 登录失败,异常:{}", username, authenticationException.getLocalizedMessage()); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationLogoutEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationLogoutEventHandler.java deleted file mode 100644 index ac7dc7243..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationLogoutEventHandler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.pigx.auth.handler; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; -import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.log.util.SysLogUtils; -import com.pig4cloud.pigx.common.security.handler.AuthenticationLogoutHandler; -import com.pig4cloud.pigx.common.security.service.PigxUser; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 退出事件处理 - * - * @author lengleng - * @date 2021/06/22 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxAuthenticationLogoutEventHandler implements AuthenticationLogoutHandler { - - private final RemoteLogService logService; - - /** - * 处理登录成功方法 - *

- * 获取到登录的authentication 对象 - * @param authentication 登录对象 - * @param request 请求 - * @param response 返回 - */ - @Async - @Override - public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { - - PigxUser pigxUser = (PigxUser) authentication.getPrincipal(); - SysLogDTO sysLog = SysLogUtils.getSysLog(request, pigxUser.getUsername()); - sysLog.setTitle(pigxUser.getUsername() + "用户退出登录"); - sysLog.setParams(pigxUser.getUsername()); - - String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); - if (StrUtil.isNotBlank(startTimeStr)) { - Long startTime = Long.parseLong(startTimeStr); - Long endTime = System.currentTimeMillis(); - sysLog.setTime(endTime - startTime); - } - - // 获取clientId 信息 - OAuth2Authentication auth2Authentication = (OAuth2Authentication) authentication; - sysLog.setServiceId(auth2Authentication.getOAuth2Request().getClientId()); - sysLog.setTenantId(pigxUser.getTenantId()); - // 保存退出的token - String token = request.getHeader(HttpHeaders.AUTHORIZATION); - sysLog.setParams(token); - - logService.saveLog(sysLog, SecurityConstants.FROM_IN); - log.info("用户:{} 退出成功, token:{} 已注销", pigxUser.getTenantId(), token); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLockEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLockEventHandler.java deleted file mode 100644 index df9f86743..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLockEventHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.pigx.auth.handler; - -import com.pig4cloud.pigx.common.core.constant.CacheConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author lengleng - * @date 2022-04-11 - * - * 登录操作次数锁定清楚功能 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxAuthenticationSuccessLockEventHandler implements AuthenticationSuccessHandler { - - private final RedisTemplate redisTemplate; - - private final KeyStrResolver tenantKeyStrResolver; - - /** - * 处理登录成功方法 - *

- * 获取到登录的authentication 对象 - * @param authentication 登录对象 - * @param request 请求 - * @param response 返回 - */ - @Async - @Override - public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { - String username = authentication.getName(); - String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username); - redisTemplate.delete(key); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLogEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLogEventHandler.java deleted file mode 100644 index b7706fecd..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessLogEventHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.pigx.auth.handler; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; -import com.pig4cloud.pigx.admin.api.feign.RemoteLogService; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import com.pig4cloud.pigx.common.core.util.WebUtils; -import com.pig4cloud.pigx.common.log.util.SysLogUtils; -import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.scheduling.annotation.Async; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author lengleng - * @date 2022-04-11 - * - * 登录成功日志记录 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxAuthenticationSuccessLogEventHandler implements AuthenticationSuccessHandler { - - private final RemoteLogService logService; - - private final KeyStrResolver tenantKeyStrResolver; - - /** - * 处理登录成功方法 - *

- * 获取到登录的authentication 对象 - * @param authentication 登录对象 - * @param request 请求 - * @param response 返回 - */ - @Async - @Override - public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { - String username = authentication.getName(); - SysLogDTO sysLog = SysLogUtils.getSysLog(request, username); - sysLog.setTitle(username + "用户登录"); - sysLog.setParams(username); - - String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); - if (StrUtil.isNotBlank(startTimeStr)) { - Long startTime = Long.parseLong(startTimeStr); - Long endTime = System.currentTimeMillis(); - sysLog.setTime(endTime - startTime); - } - - String header = request.getHeader(HttpHeaders.AUTHORIZATION); - sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A")); - sysLog.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); - - logService.saveLog(sysLog, SecurityConstants.FROM_IN); - log.info("用户:{} 登录成功", username); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxAuthorizationCodeServicesImpl.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxAuthorizationCodeServicesImpl.java deleted file mode 100644 index 3c299916a..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxAuthorizationCodeServicesImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.pig4cloud.pigx.auth.service; - -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import lombok.Cleanup; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisStringCommands; -import org.springframework.data.redis.core.types.Expiration; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices; -import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; -import org.springframework.stereotype.Service; - -/** - * @author lengleng - * @date 2020/9/3 - *

- * 授权码模式支持集群 - */ -@Service -@RequiredArgsConstructor -public class PigxAuthorizationCodeServicesImpl extends RandomValueAuthorizationCodeServices { - - /** - * 授权码模式时: 验证码有效期设置, 默认有效期为5分钟, 单位秒 - */ - @Value("${pigx.authorizationCode.expirationTime:300}") - private Long expirationTime; - - private final RedisConnectionFactory connectionFactory; - - @Setter - private String prefix = SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_CODE_PREFIX; - - @Setter - private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); - - /** - * 保存 code 和 认证信息 - * @param code 授权码模式: code - * @param authentication 认证信息 - */ - @Override - protected void store(String code, OAuth2Authentication authentication) { - @Cleanup - RedisConnection connection = connectionFactory.getConnection(); - connection.set(serializationStrategy.serialize(prefix + code), serializationStrategy.serialize(authentication), - Expiration.seconds(expirationTime), RedisStringCommands.SetOption.UPSERT); - } - - /** - * 删除code 并返回认证信息 - * @param code 授权码模式: code - * @return - */ - @Override - protected OAuth2Authentication remove(String code) { - @Cleanup - RedisConnection connection = connectionFactory.getConnection(); - - byte[] key = serializationStrategy.serialize(prefix + code); - byte[] value = connection.get(key); - - if (value == null) { - return null; - } - - OAuth2Authentication oAuth2Authentication = serializationStrategy.deserialize(value, - OAuth2Authentication.class); - - connection.del(key); - return oAuth2Authentication; - } - -} \ No newline at end of file diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java deleted file mode 100644 index e379d8ac1..000000000 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxTokenDealServiceImpl.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.pig4cloud.pigx.auth.service; - -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; -import com.pig4cloud.pigx.admin.api.feign.RemoteClientDetailsService; -import com.pig4cloud.pigx.common.core.constant.CacheConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.core.util.RetOps; -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.CacheManager; -import org.springframework.data.redis.core.ConvertingCursor; -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; -import org.springframework.security.authentication.event.LogoutSuccessEvent; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * @author lengleng - * @date 2020/9/3 - *

- * token 端点相关的业务处理 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class PigxTokenDealServiceImpl { - - private final RedisTemplate redisTemplate; - - private final CacheManager cacheManager; - - private final TokenStore tokenStore; - - private final KeyStrResolver keyStrResolver; - - private final RemoteClientDetailsService clientDetailsService; - - /** - * 删除 请求令牌 和 刷新令牌 - * @param token 请求令牌 - * @return R - */ - public R removeToken(String token) { - - OAuth2AccessToken accessToken = tokenStore.readAccessToken(token); - if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) { - return R.ok(Boolean.TRUE, "退出失败,token 无效"); - } - - OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken); - // 清空用户信息 - cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName()); - - // 清空access token - tokenStore.removeAccessToken(accessToken); - - // 清空 refresh token - OAuth2RefreshToken refreshToken = accessToken.getRefreshToken(); - tokenStore.removeRefreshToken(refreshToken); - - // 发送退出成功事件 - SpringContextHolder.publishEvent(new LogoutSuccessEvent(auth2Authentication)); - return R.ok(); - } - - /** - * 根据用户名查询 令牌相关信息 - * @param page 分页信息 - * @param username 用户名 - * @return R - */ - public R queryTokenByUsername(Page page, String username) { - // @formatter:off - List oAuth2AccessTokenList = - RetOps.of(clientDetailsService.listClientDetails(SecurityConstants.FROM_IN)) - .getData() - .orElseGet(Collections::emptyList) - .stream().map(SysOauthClientDetails::getClientId) - .flatMap(clientId -> tokenStore.findTokensByClientIdAndUserName(clientId, username).stream()).distinct() - .collect(Collectors.toList()); - // @formatter:on - - page.setRecords(oAuth2AccessTokenList); - page.setTotal(oAuth2AccessTokenList.size()); - return R.ok(page); - } - - /** - * 分页查询token 列表 - * @param page page - * @return - */ - public R queryToken(Page page) { - // 根据分页参数获取对应数据 - String key = keyStrResolver.extract(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX + "access:*", - StrUtil.COLON); - List pages = findKeysForPage(key, page.getCurrent(), page.getSize()); - redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); - page.setRecords(redisTemplate.opsForValue().multiGet(pages)); - page.setTotal(redisTemplate.keys(key).size()); - return R.ok(page); - } - - /** - * 使用游标 分页查询key - * @param patternKey key 通配符 - * @param pageNum 当前页 - * @param pageSize 每页大小 - * @return 符合的 value列表 - */ - private List findKeysForPage(String patternKey, long pageNum, long pageSize) { - ScanOptions options = ScanOptions.scanOptions().count(1000L).match(patternKey).build(); - RedisSerializer redisSerializer = (RedisSerializer) redisTemplate.getKeySerializer(); - Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection( - redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize)); - List result = new ArrayList<>(); - int tmpIndex = 0; - long startIndex = (pageNum - 1) * pageSize; - long end = pageNum * pageSize; - - assert cursor != null; - while (cursor.hasNext()) { - if (tmpIndex >= startIndex && tmpIndex < end) { - result.add(cursor.next().toString()); - tmpIndex++; - continue; - } - if (tmpIndex >= end) { - break; - } - tmpIndex++; - cursor.next(); - } - - try { - cursor.close(); - } - catch (Exception e) { - log.error("关闭cursor 失败"); - } - return result; - } - - public R queryTokenInfo(String token) { - OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token); - return R.ok(oAuth2AccessToken); - } - -} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/CustomeOAuth2AccessTokenGenerator.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/CustomeOAuth2AccessTokenGenerator.java new file mode 100644 index 000000000..54f6f26e6 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/CustomeOAuth2AccessTokenGenerator.java @@ -0,0 +1,119 @@ +package com.pig4cloud.pigx.auth.support; + +import org.springframework.lang.Nullable; +import org.springframework.security.oauth2.core.ClaimAccessor; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.token.*; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * @author lengleng + * @date 2022/5/29 + */ +public class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator { + + private OAuth2TokenCustomizer accessTokenCustomizer; + + @Nullable + @Override + public OAuth2AccessToken generate(OAuth2TokenContext context) { + if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE + .equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { + return null; + } + + String issuer = null; + if (context.getAuthorizationServerContext() != null) { + issuer = context.getAuthorizationServerContext().getIssuer(); + } + RegisteredClient registeredClient = context.getRegisteredClient(); + + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); + + // @formatter:off + OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder(); + if (StringUtils.hasText(issuer)) { + claimsBuilder.issuer(issuer); + } + claimsBuilder + .subject(context.getPrincipal().getName()) + .audience(Collections.singletonList(registeredClient.getClientId())) + .issuedAt(issuedAt) + .expiresAt(expiresAt) + .notBefore(issuedAt) + .id(UUID.randomUUID().toString()); + if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) { + claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes()); + } + // @formatter:on + + if (this.accessTokenCustomizer != null) { + // @formatter:off + OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder) + .registeredClient(context.getRegisteredClient()) + .principal(context.getPrincipal()) + .authorizationServerContext(context.getAuthorizationServerContext()) + .authorizedScopes(context.getAuthorizedScopes()) + .tokenType(context.getTokenType()) + .authorizationGrantType(context.getAuthorizationGrantType()); + if (context.getAuthorization() != null) { + accessTokenContextBuilder.authorization(context.getAuthorization()); + } + if (context.getAuthorizationGrant() != null) { + accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant()); + } + // @formatter:on + + OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build(); + this.accessTokenCustomizer.customize(accessTokenContext); + } + + OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build(); + return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, UUID.randomUUID().toString(), + accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), + accessTokenClaimsSet.getClaims()); + } + + /** + * Sets the {@link OAuth2TokenCustomizer} that customizes the + * {@link OAuth2TokenClaimsContext#getClaims() claims} for the + * {@link OAuth2AccessToken}. + * @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the + * claims for the {@code OAuth2AccessToken} + */ + public void setAccessTokenCustomizer(OAuth2TokenCustomizer accessTokenCustomizer) { + Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null"); + this.accessTokenCustomizer = accessTokenCustomizer; + } + + private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor { + + private final Map claims; + + private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, + Set scopes, Map claims) { + super(tokenType, tokenValue, issuedAt, expiresAt, scopes); + this.claims = claims; + } + + @Override + public Map getClaims() { + return this.claims; + } + + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java new file mode 100644 index 000000000..f5ec7b9ff --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java @@ -0,0 +1,96 @@ +package com.pig4cloud.pigx.auth.support.base; + +import com.pig4cloud.pigx.common.security.util.OAuth2EndpointUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author jumuning + * @date 2022-06-02 + * + * 自定义模式认证转换器 + */ +public abstract class OAuth2ResourceOwnerBaseAuthenticationConverter + implements AuthenticationConverter { + + /** + * 是否支持此convert + * @param grantType 授权类型 + * @return + */ + public abstract boolean support(String grantType); + + /** + * 校验参数 + * @param request 请求 + */ + public void checkParams(HttpServletRequest request) { + + } + + /** + * 构建具体类型的token + * @param clientPrincipal + * @param requestedScopes + * @param additionalParameters + * @return + */ + public abstract T buildToken(Authentication clientPrincipal, Set requestedScopes, + Map additionalParameters); + + @Override + public Authentication convert(HttpServletRequest request) { + + // grant_type (REQUIRED) + String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); + if (!support(grantType)) { + return null; + } + + MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); + // scope (OPTIONAL) + String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + + Set requestedScopes = null; + if (StringUtils.hasText(scope)) { + requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); + } + + // 校验个性化参数 + checkParams(request); + + // 获取当前已经认证的客户端信息 + Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); + if (clientPrincipal == null) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + + // 扩展信息 + Map additionalParameters = parameters.entrySet().stream() + .filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) + && !e.getKey().equals(OAuth2ParameterNames.SCOPE)) + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))); + + // 创建token + return buildToken(clientPrincipal, requestedScopes, additionalParameters); + + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java new file mode 100644 index 000000000..0c106af67 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java @@ -0,0 +1,285 @@ +package com.pig4cloud.pigx.auth.support.base; + +import cn.hutool.extra.spring.SpringUtil; +import com.pig4cloud.pigx.common.security.util.OAuth2ErrorCodesExpand; +import com.pig4cloud.pigx.common.security.util.ScopeException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.core.*; +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 org.springframework.util.CollectionUtils; + +import java.security.Principal; +import java.time.Instant; +import java.util.*; +import java.util.function.Supplier; + +/** + * @author jumuning + * @description + * + * 处理自定义授权 + */ +public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider + implements AuthenticationProvider { + + private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerBaseAuthenticationProvider.class); + + private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; + + private final OAuth2AuthorizationService authorizationService; + + private final OAuth2TokenGenerator tokenGenerator; + + private final AuthenticationManager authenticationManager; + + private final MessageSourceAccessor messages; + + @Deprecated + private Supplier refreshTokenGenerator; + + /** + * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the + * provided parameters. + * @param authorizationService the authorization service + * @param tokenGenerator the token generator + * @since 0.2.3 + */ + public OAuth2ResourceOwnerBaseAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator) { + Assert.notNull(authorizationService, "authorizationService cannot be null"); + Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); + this.authenticationManager = authenticationManager; + this.authorizationService = authorizationService; + this.tokenGenerator = tokenGenerator; + + // 国际化配置 + this.messages = new MessageSourceAccessor(SpringUtil.getBean("securityMessageSource"), Locale.CHINA); + } + + @Deprecated + public void setRefreshTokenGenerator(Supplier refreshTokenGenerator) { + Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null"); + this.refreshTokenGenerator = refreshTokenGenerator; + } + + public abstract UsernamePasswordAuthenticationToken buildToken(Map reqParameters); + + /** + * 当前provider是否支持此令牌类型 + * @param authentication + * @return + */ + @Override + public abstract boolean supports(Class authentication); + + /** + * 当前的请求客户端是否支持此模式 + * @param registeredClient + */ + public abstract void checkClient(RegisteredClient registeredClient); + + /** + * Performs authentication with the same contract as + * {@link AuthenticationManager#authenticate(Authentication)} . + * @param authentication the authentication request object. + * @return a fully authenticated object including credentials. May return + * null if the AuthenticationProvider is unable to support + * authentication of the passed Authentication object. In such a case, + * the next AuthenticationProvider that supports the presented + * Authentication class will be tried. + * @throws AuthenticationException if authentication fails. + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + + T resouceOwnerBaseAuthentication = (T) authentication; + + OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient( + resouceOwnerBaseAuthentication); + + RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); + checkClient(registeredClient); + + Set authorizedScopes; + // Default to configured scopes + if (!CollectionUtils.isEmpty(resouceOwnerBaseAuthentication.getScopes())) { + for (String requestedScope : resouceOwnerBaseAuthentication.getScopes()) { + if (!registeredClient.getScopes().contains(requestedScope)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); + } + } + authorizedScopes = new LinkedHashSet<>(resouceOwnerBaseAuthentication.getScopes()); + } + else { + throw new ScopeException(OAuth2ErrorCodesExpand.SCOPE_IS_EMPTY); + } + + Map reqParameters = resouceOwnerBaseAuthentication.getAdditionalParameters(); + try { + + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters); + + LOGGER.debug("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken); + + Authentication usernamePasswordAuthentication = authenticationManager + .authenticate(usernamePasswordAuthenticationToken); + + // @formatter:off + DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() + .registeredClient(registeredClient) + .principal(usernamePasswordAuthentication) + .authorizationServerContext(AuthorizationServerContextHolder.getContext()) + .authorizedScopes(authorizedScopes) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + .authorizationGrant(resouceOwnerBaseAuthentication); + // @formatter:on + + OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization + .withRegisteredClient(registeredClient).principalName(usernamePasswordAuthentication.getName()) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + // 0.4.0 新增的方法 + .authorizedScopes(authorizedScopes); + + // ----- Access token ----- + 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.id(accessToken.getTokenValue()) + .token(accessToken, + (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, + ((ClaimAccessor) generatedAccessToken).getClaims())) + // 0.4.0 新增的方法 + .authorizedScopes(authorizedScopes) + .attribute(Principal.class.getName(), usernamePasswordAuthentication); + } + else { + authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken); + } + + // ----- Refresh token ----- + OAuth2RefreshToken refreshToken = null; + if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && + // Do not issue refresh token to public client + !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { + + if (this.refreshTokenGenerator != null) { + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getRefreshTokenTimeToLive()); + refreshToken = new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt); + } + else { + 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); + } + refreshToken = (OAuth2RefreshToken) generatedRefreshToken; + } + authorizationBuilder.refreshToken(refreshToken); + } + + OAuth2Authorization authorization = authorizationBuilder.build(); + + this.authorizationService.save(authorization); + + LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken"); + + return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, + refreshToken, Objects.requireNonNull(authorization.getAccessToken().getClaims())); + + } + catch (Exception ex) { + throw oAuth2AuthenticationException(authentication, (AuthenticationException) ex); + } + + } + + /** + * 登录异常转换为oauth2异常 + * @param authentication 身份验证 + * @param authenticationException 身份验证异常 + * @return {@link OAuth2AuthenticationException} + */ + private OAuth2AuthenticationException oAuth2AuthenticationException(Authentication authentication, + AuthenticationException authenticationException) { + if (authenticationException instanceof UsernameNotFoundException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USERNAME_NOT_FOUND, + this.messages.getMessage("JdbcDaoImpl.notFound", new Object[] { authentication.getName() }, + "Username {0} not found"), + "")); + } + if (authenticationException instanceof BadCredentialsException) { + return new OAuth2AuthenticationException( + new OAuth2Error(OAuth2ErrorCodesExpand.BAD_CREDENTIALS, this.messages.getMessage( + "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), "")); + } + if (authenticationException instanceof LockedException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_LOCKED, this.messages + .getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), "")); + } + if (authenticationException instanceof DisabledException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_DISABLE, + this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), + "")); + } + if (authenticationException instanceof AccountExpiredException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_EXPIRED, this.messages + .getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), "")); + } + if (authenticationException instanceof CredentialsExpiredException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.CREDENTIALS_EXPIRED, + this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", + "User credentials have expired"), + "")); + } + if (authenticationException instanceof ScopeException) { + return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE, + this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "invalid_scope"), "")); + } + return new OAuth2AuthenticationException(OAuth2ErrorCodesExpand.UN_KNOW_LOGIN_ERROR); + } + + private 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); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java new file mode 100644 index 000000000..8c0c29b5a --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java @@ -0,0 +1,61 @@ +package com.pig4cloud.pigx.auth.support.base; + +import lombok.Getter; +import org.springframework.lang.Nullable; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.util.Assert; + +import java.util.*; + +/** + * @author lengleng + * @date 2022/6/2 + * + * 自定义授权模式抽象 + */ +public abstract class OAuth2ResourceOwnerBaseAuthenticationToken extends AbstractAuthenticationToken { + + @Getter + private final AuthorizationGrantType authorizationGrantType; + + @Getter + private final Authentication clientPrincipal; + + @Getter + private final Set scopes; + + @Getter + private final Map additionalParameters; + + public OAuth2ResourceOwnerBaseAuthenticationToken(AuthorizationGrantType authorizationGrantType, + Authentication clientPrincipal, @Nullable Set scopes, + @Nullable Map additionalParameters) { + super(Collections.emptyList()); + Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); + Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); + this.authorizationGrantType = authorizationGrantType; + this.clientPrincipal = clientPrincipal; + this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet()); + this.additionalParameters = Collections.unmodifiableMap( + additionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap()); + } + + /** + * 扩展模式一般不需要密码 + */ + @Override + public Object getCredentials() { + return ""; + } + + /** + * 获取用户名 + */ + @Override + public Object getPrincipal() { + return this.clientPrincipal; + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/package-info.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/package-info.java new file mode 100644 index 000000000..692d98464 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 自定义认证模式接入的抽象实现 + */ +package com.pig4cloud.pigx.auth.support.base; diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/CustomeOAuth2TokenCustomizer.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/CustomeOAuth2TokenCustomizer.java new file mode 100644 index 000000000..b2506358f --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/CustomeOAuth2TokenCustomizer.java @@ -0,0 +1,39 @@ +package com.pig4cloud.pigx.auth.support.core; + +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.service.PigxUser; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; + +/** + * token 输出增强 + * + * @author lengleng + * @date 2022/6/3 + */ +public class CustomeOAuth2TokenCustomizer implements OAuth2TokenCustomizer { + + /** + * Customize the OAuth 2.0 Token attributes. + * @param context the context containing the OAuth 2.0 Token attributes + */ + @Override + public void customize(OAuth2TokenClaimsContext context) { + OAuth2TokenClaimsSet.Builder claims = context.getClaims(); + claims.claim(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PIGX_LICENSE); + String clientId = context.getAuthorizationGrant().getName(); + claims.claim(SecurityConstants.CLIENT_ID, clientId); + claims.claim(SecurityConstants.ACTIVE, Boolean.TRUE); + + // 客户端模式不返回具体用户信息 + if (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) { + return; + } + + PigxUser pigxUser = (PigxUser) context.getPrincipal().getPrincipal(); + claims.claim(SecurityConstants.DETAILS_USER_ID, pigxUser.getId()); + claims.claim(SecurityConstants.DETAILS_USERNAME, pigxUser.getUsername()); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java new file mode 100644 index 000000000..88965f096 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/FormIdentityLoginConfigurer.java @@ -0,0 +1,31 @@ +package com.pig4cloud.pigx.auth.support.core; + +import com.pig4cloud.pigx.auth.support.handler.FormAuthenticationFailureHandler; +import com.pig4cloud.pigx.auth.support.handler.SsoLogoutSuccessHandler; +import com.pig4cloud.pigx.auth.support.handler.TenantSavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; + +/** + * @author lengleng + * @data 2022-06-04 + * + * 基于授权码模式 统一认证登录 spring security & sas 都可以使用 所以抽取成 HttpConfigurer + */ +public final class FormIdentityLoginConfigurer + extends AbstractHttpConfigurer { + + @Override + public void init(HttpSecurity http) throws Exception { + http.formLogin(formLogin -> { + formLogin.loginPage("/token/login"); + formLogin.loginProcessingUrl("/token/form"); + formLogin.failureHandler(new FormAuthenticationFailureHandler()); + formLogin.successHandler(new TenantSavedRequestAwareAuthenticationSuccessHandler()); + + }).logout() // SSO登出成功处理 + .logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID") + .invalidateHttpSession(true).and().csrf().disable(); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxDaoAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigDaoAuthenticationProvider.java similarity index 81% rename from pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxDaoAuthenticationProvider.java rename to pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigDaoAuthenticationProvider.java index 0dcaa196c..77a980ce1 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxDaoAuthenticationProvider.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/core/PigDaoAuthenticationProvider.java @@ -1,8 +1,11 @@ -package com.pig4cloud.pigx.common.security.component; +package com.pig4cloud.pigx.auth.support.core; -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.core.util.WebUtils; import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService; +import lombok.SneakyThrows; import org.springframework.core.Ordered; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; @@ -10,13 +13,14 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.authentication.www.BasicAuthenticationConverter; import org.springframework.util.Assert; import java.util.Comparator; @@ -25,9 +29,9 @@ import java.util.Optional; /** * @author lengleng - * @date 2022/1/15 + * @date 2022-06-04 */ -public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { +public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { /** * The plaintext password used to perform PasswordEncoder#matches(CharSequence, @@ -35,6 +39,8 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica */ private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; + private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter(); + private PasswordEncoder passwordEncoder; /** @@ -49,7 +55,8 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica private UserDetailsPasswordService userDetailsPasswordService; - public PigxDaoAuthenticationProvider() { + public PigDaoAuthenticationProvider() { + setMessageSource(SpringUtil.getBean("securityMessageSource")); setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); } @@ -57,6 +64,13 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica @SuppressWarnings("deprecation") protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { + + // app 模式不用校验密码 + String grantType = WebUtils.getRequest().getParameter(OAuth2ParameterNames.GRANT_TYPE); + if (StrUtil.equals(SecurityConstants.APP, grantType)) { + return; + } + if (authentication.getCredentials() == null) { this.logger.debug("Failed to authenticate since no credentials provided"); throw new BadCredentialsException(this.messages @@ -70,27 +84,24 @@ public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthentica } } + @SneakyThrows @Override - protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { + protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) { prepareTimingAttackProtection(); + String grantType = WebUtils.getRequest().getParameter(OAuth2ParameterNames.GRANT_TYPE); + String clientId = WebUtils.getRequest().getParameter(OAuth2ParameterNames.CLIENT_ID); - // 此处已获得 客户端认证 获取对应 userDetailsService - Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication(); - - // SSO NPE 处理 - String clientId; - if (clientAuthentication == null) { - clientId = WebUtils.getRequest().getParameter("clientId"); - } - else { - clientId = clientAuthentication.getName(); + if (StrUtil.isBlank(clientId)) { + clientId = basicConvert.convert(WebUtils.getRequest()).getName(); } - Map userDetailsServiceMap = SpringContextHolder + Map userDetailsServiceMap = SpringUtil .getBeansOfType(PigxUserDetailsService.class); + + String finalClientId = clientId; Optional optional = userDetailsServiceMap.values().stream() - .filter(service -> service.support(clientId, null)).max(Comparator.comparingInt(Ordered::getOrder)); + .filter(service -> service.support(finalClientId, grantType)) + .max(Comparator.comparingInt(Ordered::getOrder)); if (!optional.isPresent()) { throw new InternalAuthenticationServiceException("UserDetailsService error , not register"); diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java new file mode 100644 index 000000000..8c8475379 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/FormAuthenticationFailureHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.auth.support.handler; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.HttpUtil; +import com.pig4cloud.pigx.common.core.util.WebUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author lengleng + * @date 2022-06-02 + *

+ * 表单登录失败处理逻辑 + */ +@Slf4j +public class FormAuthenticationFailureHandler implements AuthenticationFailureHandler { + + /** + * Called when an authentication attempt fails. + * @param request the request during which the authentication attempt occurred. + * @param response the response. + * @param exception the exception which was thrown to reject the authentication + */ + @Override + @SneakyThrows + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) { + log.debug("表单登录失败:{}", exception.getLocalizedMessage()); + String url = HttpUtil.encodeParams(String.format("/token/login?error=%s", exception.getMessage()), + CharsetUtil.CHARSET_UTF_8); + WebUtils.getResponse().sendRedirect(url); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationFailureEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationFailureEventHandler.java new file mode 100644 index 000000000..a80886dc7 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationFailureEventHandler.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.auth.support.handler; + +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; +import com.pig4cloud.pigx.admin.api.feign.RemoteUserService; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; +import com.pig4cloud.pigx.common.core.util.MsgUtils; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.data.resolver.ParamResolver; +import com.pig4cloud.pigx.common.log.event.SysLogEvent; +import com.pig4cloud.pigx.common.log.util.LogTypeEnum; +import com.pig4cloud.pigx.common.log.util.SysLogUtils; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpStatus; +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.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * @author lengleng + * @date 2022-06-02 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PigAuthenticationFailureEventHandler implements AuthenticationFailureHandler { + + private static final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter(); + + private final RedisTemplate redisTemplate; + + private final ApplicationEventPublisher publisher; + + private final KeyStrResolver tenantKeyStrResolver; + + private final RemoteUserService userService; + + /** + * Called when an authentication attempt fails. + * @param request the request during which the authentication attempt occurred. + * @param response the response. + * @param exception the exception which was thrown to reject the authentication + * request. + */ + @Override + @SneakyThrows + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) { + String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); + log.info("登录失败,异常:{}", exception.getLocalizedMessage()); + + // 密码模式记录错误信息 + if (OAuth2ParameterNames.PASSWORD.equals(grantType)) { + String username = request.getParameter(OAuth2ParameterNames.USERNAME); + + // 密码错误记录错误次数 + if (exception instanceof OAuth2AuthenticationException) { + recordLoginFailureTimes(username); + } + + // 记录登录失败错误信息 + sendFailureEventLog(request, exception, username); + } + + // 写出错误信息 + sendErrorResponse(request, response, exception); + } + + /** + * 记录失败日志 + * @param request HttpServletRequest + * @param exception 错误日志 + * @param username 用户名 + */ + private void sendFailureEventLog(HttpServletRequest request, AuthenticationException exception, String username) { + SysLogDTO logVo = SysLogUtils.getSysLog(); + logVo.setTitle("登录失败"); + logVo.setLogType(LogTypeEnum.ERROR.getType()); + logVo.setException(exception.getLocalizedMessage()); + // 发送异步日志事件 + String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); + if (StrUtil.isNotBlank(startTimeStr)) { + Long startTime = Long.parseLong(startTimeStr); + Long endTime = System.currentTimeMillis(); + logVo.setTime(endTime - startTime); + } + logVo.setCreateBy(username); + logVo.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); + publisher.publishEvent(new SysLogEvent(logVo)); + } + + /** + * 记录登录失败此处,如果操作阈值 调用接口锁定用户 + * @param username username + */ + private void recordLoginFailureTimes(String username) { + String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username); + Long deltaTimes = ParamResolver.getLong("LOGIN_ERROR_TIMES", 5L); + Long times = redisTemplate.opsForValue().increment(key); + + // 自动过期时间 + Long deltaTime = ParamResolver.getLong("DELTA_TIME", 1L); + redisTemplate.expire(key, deltaTime, TimeUnit.HOURS); + + if (deltaTimes <= times) { + userService.lockUser(username, SecurityConstants.FROM_IN); + } + } + + private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException { + ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); + String errorMessage; + + if (exception instanceof OAuth2AuthenticationException) { + OAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception; + errorMessage = StrUtil.isBlank(authorizationException.getError().getDescription()) + ? authorizationException.getError().getErrorCode() + : authorizationException.getError().getDescription(); + } + else { + errorMessage = exception.getLocalizedMessage(); + } + + // 手机号登录 + String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); + if (SecurityConstants.APP.equals(grantType)) { + errorMessage = MsgUtils.getSecurityMessage("AbstractUserDetailsAuthenticationProvider.smsBadCredentials"); + } + + this.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationSuccessEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationSuccessEventHandler.java new file mode 100644 index 000000000..2729c96de --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigAuthenticationSuccessEventHandler.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.auth.support.handler; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.KeyStrResolver; +import com.pig4cloud.pigx.common.log.event.SysLogEvent; +import com.pig4cloud.pigx.common.log.util.LogTypeEnum; +import com.pig4cloud.pigx.common.log.util.SysLogUtils; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServletServerHttpResponse; +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.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.Map; + +/** + * @author lengleng + * @date 2022-06-02 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PigAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler { + + private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); + + private final RedisTemplate redisTemplate; + + private final ApplicationEventPublisher publisher; + + private final KeyStrResolver tenantKeyStrResolver; + + /** + * Called when a user has been successfully authenticated. + * @param request the request which caused the successful authentication + * @param response the response + * @param authentication the Authentication object which was created during + * the authentication process. + */ + @SneakyThrows + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + + // 清除账号历史锁定次数 + clearLoginFailureTimes(authentication); + + // 写入登录成功的日志 + OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication; + Map map = accessTokenAuthentication.getAdditionalParameters(); + if (MapUtil.isNotEmpty(map)) { + sendSuccessEventLog(request, accessTokenAuthentication, map); + } + + // 输出token + sendAccessTokenResponse(response, authentication); + } + + /** + * 记录登录成功事件 + * @param request HttpServletRequest + * @param accessTokenAuthentication Authentication + * @param map 请求参数 + */ + private void sendSuccessEventLog(HttpServletRequest request, + OAuth2AccessTokenAuthenticationToken accessTokenAuthentication, Map map) { + // 发送异步日志事件 + SecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication); + SysLogDTO logVo = SysLogUtils.getSysLog(); + logVo.setTitle("登录成功"); + logVo.setLogType(LogTypeEnum.NORMAL.getType()); + String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); + if (StrUtil.isNotBlank(startTimeStr)) { + Long startTime = Long.parseLong(startTimeStr); + Long endTime = System.currentTimeMillis(); + logVo.setTime(endTime - startTime); + } + logVo.setCreateBy(MapUtil.getStr(map, SecurityConstants.DETAILS_USERNAME)); + logVo.setTenantId(Long.parseLong(tenantKeyStrResolver.key())); + + publisher.publishEvent(new SysLogEvent(logVo)); + } + + /** + * 清空登录失败的记录 + * @param authentication + */ + private void clearLoginFailureTimes(Authentication authentication) { + String username = authentication.getName(); + String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username); + redisTemplate.delete(key); + } + + private void sendAccessTokenResponse(HttpServletResponse response, Authentication authentication) + throws IOException { + + OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication; + + OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken(); + OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken(); + Map additionalParameters = accessTokenAuthentication.getAdditionalParameters(); + + OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) + .tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes()); + if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) { + builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt())); + } + if (refreshToken != null) { + builder.refreshToken(refreshToken.getTokenValue()); + } + if (!CollectionUtils.isEmpty(additionalParameters)) { + builder.additionalParameters(additionalParameters); + } + OAuth2AccessTokenResponse accessTokenResponse = builder.build(); + ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); + + // 无状态 注意删除 context 上下文的信息 + SecurityContextHolder.clearContext(); + this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigLogoutSuccessEventHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigLogoutSuccessEventHandler.java new file mode 100644 index 000000000..df6de0522 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/PigLogoutSuccessEventHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.auth.support.handler; + +import com.pig4cloud.pigx.admin.api.dto.SysLogDTO; +import com.pig4cloud.pigx.common.core.util.WebUtils; +import com.pig4cloud.pigx.common.log.util.SysLogUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.event.LogoutSuccessEvent; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.stereotype.Component; + +/** + * @author zhangran + * @date 2022-06-02 + * + * 事件机制处理退出相关 + */ +@Slf4j +@Component +public class PigLogoutSuccessEventHandler implements ApplicationListener { + + @Override + public void onApplicationEvent(LogoutSuccessEvent event) { + Authentication authentication = (Authentication) event.getSource(); + if (authentication instanceof PreAuthenticatedAuthenticationToken) { + handle(authentication); + } + } + + /** + * 处理退出成功方法 + *

+ * 获取到登录的authentication 对象 + * @param authentication 登录对象 + */ + public void handle(Authentication authentication) { + log.info("用户:{} 退出成功", authentication.getPrincipal()); + SysLogDTO logVo = SysLogUtils.getSysLog(); + logVo.setTitle("退出成功"); + // 发送异步日志事件 + Long startTime = System.currentTimeMillis(); + Long endTime = System.currentTimeMillis(); + logVo.setTime(endTime - startTime); + + // 设置对应的token + logVo.setParams(WebUtils.getRequest().getHeader(HttpHeaders.AUTHORIZATION)); + + // 这边设置ServiceId + if (authentication instanceof PreAuthenticatedAuthenticationToken) { + logVo.setServiceId(authentication.getCredentials().toString()); + } + logVo.setCreateBy(authentication.getName()); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/SsoLogoutSuccessHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/SsoLogoutSuccessHandler.java new file mode 100644 index 000000000..2a75807a5 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/SsoLogoutSuccessHandler.java @@ -0,0 +1,41 @@ +package com.pig4cloud.pigx.auth.support.handler; + +import cn.hutool.core.util.StrUtil; +import org.springframework.http.HttpHeaders; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author lengleng + * @date 2022-06-02 + *

+ * sso 退出功能 ,根据客户端传入跳转 + */ +public class SsoLogoutSuccessHandler implements LogoutSuccessHandler { + + private static final String REDIRECT_URL = "redirect_url"; + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException { + if (response == null) { + return; + } + + // 获取请求参数中是否包含 回调地址 + String redirectUrl = request.getParameter(REDIRECT_URL); + if (StrUtil.isNotBlank(redirectUrl)) { + response.sendRedirect(redirectUrl); + } + else if (StrUtil.isNotBlank(request.getHeader(HttpHeaders.REFERER))) { + // 默认跳转referer 地址 + String referer = request.getHeader(HttpHeaders.REFERER); + response.sendRedirect(referer); + } + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java similarity index 97% rename from pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java rename to pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java index 8f961d8f4..37acec37e 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/handler/TenantSavedRequestAwareAuthenticationSuccessHandler.java @@ -1,4 +1,4 @@ -package com.pig4cloud.pigx.common.security.handler; +package com.pig4cloud.pigx.auth.support.handler; import com.pig4cloud.pigx.common.core.util.KeyStrResolver; import com.pig4cloud.pigx.common.core.util.SpringContextHolder; diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java new file mode 100644 index 000000000..9c8aceafa --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java @@ -0,0 +1,63 @@ +package com.pig4cloud.pigx.auth.support.password; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter; +import com.pig4cloud.pigx.common.security.util.OAuth2EndpointUtils; +import javax.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.Set; + +/** + * @author jumuning + * @date 2022-06-02 + * + * 密码认证转换器 + */ +public class OAuth2ResourceOwnerPasswordAuthenticationConverter + extends OAuth2ResourceOwnerBaseAuthenticationConverter { + + /** + * 支持密码模式 + * @param grantType 授权类型 + */ + @Override + public boolean support(String grantType) { + return AuthorizationGrantType.PASSWORD.getValue().equals(grantType); + } + + @Override + public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal, + Set requestedScopes, Map additionalParameters) { + return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal, + requestedScopes, additionalParameters); + } + + /** + * 校验扩展参数 密码模式密码必须不为空 + * @param request 参数列表 + */ + @Override + public void checkParams(HttpServletRequest request) { + MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); + // username (REQUIRED) + String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); + if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + + // password (REQUIRED) + String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); + if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java new file mode 100644 index 000000000..3eace7f8d --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java @@ -0,0 +1,64 @@ +package com.pig4cloud.pigx.auth.support.password; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.OAuth2Token; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; + +import java.util.Map; + +/** + * @author jumuning + * @description 处理用户名密码授权 + */ +public class OAuth2ResourceOwnerPasswordAuthenticationProvider + extends OAuth2ResourceOwnerBaseAuthenticationProvider { + + private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class); + + /** + * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the + * provided parameters. + * @param authenticationManager + * @param authorizationService the authorization service + * @param tokenGenerator the token generator + * @since 0.2.3 + */ + public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator) { + super(authenticationManager, authorizationService, tokenGenerator); + } + + @Override + public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { + String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME); + String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD); + return new UsernamePasswordAuthenticationToken(username, password); + } + + @Override + public boolean supports(Class authentication) { + boolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication); + LOGGER.debug("supports authentication=" + authentication + " returning " + supports); + return supports; + } + + @Override + public void checkClient(RegisteredClient registeredClient) { + assert registeredClient != null; + if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); + } + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java new file mode 100644 index 000000000..709030215 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java @@ -0,0 +1,21 @@ +package com.pig4cloud.pigx.auth.support.password; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +import java.util.Map; +import java.util.Set; + +/** + * @author jumuning + * @description 密码授权token信息 + */ +public class OAuth2ResourceOwnerPasswordAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken { + + public OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType, + Authentication clientPrincipal, Set scopes, Map additionalParameters) { + super(authorizationGrantType, clientPrincipal, scopes, additionalParameters); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/package-info.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/package-info.java new file mode 100644 index 000000000..b2255c6fc --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/password/package-info.java @@ -0,0 +1,4 @@ +/** + * 密码模式 + */ +package com.pig4cloud.pigx.auth.support.password; diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java new file mode 100644 index 000000000..2a16976be --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java @@ -0,0 +1,57 @@ +package com.pig4cloud.pigx.auth.support.sms; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.util.OAuth2EndpointUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.Set; + +/** + * @author lengleng + * @date 2022-05-31 + * + * 短信登录转换器 + */ +public class OAuth2ResourceOwnerSmsAuthenticationConverter + extends OAuth2ResourceOwnerBaseAuthenticationConverter { + + /** + * 是否支持此convert + * @param grantType 授权类型 + * @return + */ + @Override + public boolean support(String grantType) { + return SecurityConstants.APP.equals(grantType); + } + + @Override + public OAuth2ResourceOwnerSmsAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes, + Map additionalParameters) { + return new OAuth2ResourceOwnerSmsAuthenticationToken(new AuthorizationGrantType(SecurityConstants.APP), + clientPrincipal, requestedScopes, additionalParameters); + } + + /** + * 校验扩展参数 密码模式密码必须不为空 + * @param request 参数列表 + */ + @Override + public void checkParams(HttpServletRequest request) { + MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); + // PHONE (REQUIRED) + String phone = parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME); + if (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() != 1) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME, + OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); + } + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java new file mode 100644 index 000000000..961f1e9e1 --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java @@ -0,0 +1,66 @@ +package com.pig4cloud.pigx.auth.support.sms; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.OAuth2Token; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; + +import java.util.Map; + +/** + * @author lengleng + * @date date + * + * 短信登录的核心处理 + */ +public class OAuth2ResourceOwnerSmsAuthenticationProvider + extends OAuth2ResourceOwnerBaseAuthenticationProvider { + + private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerSmsAuthenticationProvider.class); + + /** + * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the + * provided parameters. + * @param authenticationManager + * @param authorizationService the authorization service + * @param tokenGenerator the token generator + * @since 0.2.3 + */ + public OAuth2ResourceOwnerSmsAuthenticationProvider(AuthenticationManager authenticationManager, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator) { + super(authenticationManager, authorizationService, tokenGenerator); + } + + @Override + public boolean supports(Class authentication) { + boolean supports = OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication); + LOGGER.debug("supports authentication=" + authentication + " returning " + supports); + return supports; + } + + @Override + public void checkClient(RegisteredClient registeredClient) { + assert registeredClient != null; + if (!registeredClient.getAuthorizationGrantTypes() + .contains(new AuthorizationGrantType(SecurityConstants.APP))) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); + } + } + + @Override + public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { + String phone = (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME); + return new UsernamePasswordAuthenticationToken(phone, null); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java new file mode 100644 index 000000000..dd9b1f12c --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java @@ -0,0 +1,21 @@ +package com.pig4cloud.pigx.auth.support.sms; + +import com.pig4cloud.pigx.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +import java.util.Map; +import java.util.Set; + +/** + * @author lengleng + * @description 短信登录token信息 + */ +public class OAuth2ResourceOwnerSmsAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken { + + public OAuth2ResourceOwnerSmsAuthenticationToken(AuthorizationGrantType authorizationGrantType, + Authentication clientPrincipal, Set scopes, Map additionalParameters) { + super(authorizationGrantType, clientPrincipal, scopes, additionalParameters); + } + +} diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/package-info.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/package-info.java new file mode 100644 index 000000000..d5481f48e --- /dev/null +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/support/sms/package-info.java @@ -0,0 +1,4 @@ +/** + * 短信模式 + */ +package com.pig4cloud.pigx.auth.support.sms; diff --git a/pigx-auth/src/main/resources/templates/ftl/confirm.ftl b/pigx-auth/src/main/resources/templates/ftl/confirm.ftl index 7059cc1cf..820b644bb 100644 --- a/pigx-auth/src/main/resources/templates/ftl/confirm.ftl +++ b/pigx-auth/src/main/resources/templates/ftl/confirm.ftl @@ -5,7 +5,7 @@ - PigX第三方授权 + PigX 第三方授权 @@ -21,21 +21,26 @@ 技术支持

-
- + + + +

- ${app.appName!'未定义应用名称'} 将获得以下权限:

+ 将获得以下权限:

  • <#list scopeList as scope> - - +

授权后表明你已同意 服务协议

diff --git a/pigx-auth/src/main/resources/templates/ftl/login.ftl b/pigx-auth/src/main/resources/templates/ftl/login.ftl index 5bf8c09a1..ad1c33e12 100755 --- a/pigx-auth/src/main/resources/templates/ftl/login.ftl +++ b/pigx-auth/src/main/resources/templates/ftl/login.ftl @@ -27,7 +27,7 @@ - + diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CacheConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CacheConstants.java index bb617df6d..fbcb4d9a0 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CacheConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CacheConstants.java @@ -105,4 +105,9 @@ public interface CacheConstants { */ String LOGIN_ERROR_TIMES = "login_error_times"; + /** + * oauth 缓存前缀 + */ + String PROJECT_OAUTH_ACCESS = "token::access_token"; + } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java index febfe9178..49dafb451 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/CommonConstants.java @@ -125,4 +125,14 @@ public interface CommonConstants { */ String REQUEST_START_TIME = "REQUEST-START-TIME"; + /** + * 当前页 + */ + String CURRENT = "current"; + + /** + * size + */ + String SIZE = "size"; + } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java index e841678b8..61178516b 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java @@ -73,7 +73,7 @@ public interface SecurityConstants { /** * 项目的license */ - String PIGX_LICENSE = "made by pigx"; + String PIGX_LICENSE = "https://pig4cloud.com"; /** * 内部 @@ -88,7 +88,7 @@ public interface SecurityConstants { /** * OAUTH URL */ - String OAUTH_TOKEN_URL = "/oauth/token"; + String OAUTH_TOKEN_URL = "/oauth2/token"; /** * 移动端授权 @@ -96,9 +96,14 @@ public interface SecurityConstants { String GRANT_MOBILE = "mobile"; /** - * 小程序授权 + * TOC 客户端 */ - String CLIENT_MINI = "mini"; + String HEADER_TOC = "CLIENT_TOC"; + + /** + * TOC 客户端 + */ + String HEADER_TOC_YES = "Y"; /** * QQ获取token @@ -256,4 +261,29 @@ public interface SecurityConstants { */ String AES = "aes"; + /** + * 授权码模式confirm + */ + String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access"; + + /** + * {noop} 加密的特征码 + */ + String NOOP = "{noop}"; + + /** + * 短信登录 参数名称 + */ + String SMS_PARAMETER_NAME = "mobile"; + + /** + * 手机号登录 + */ + String APP = "mobile"; + + /** + * 用户信息 + */ + String DETAILS_USER = "user_info"; + } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/enums/LoginTypeEnum.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/enums/LoginTypeEnum.java index 60b604054..fed98e14f 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/enums/LoginTypeEnum.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/enums/LoginTypeEnum.java @@ -72,6 +72,7 @@ public enum LoginTypeEnum { * 企业微信 */ WEIXIN_CP("WEIXIN_CP", "企业微信"), + /** * CAS 登录 */ diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/MsgUtils.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/MsgUtils.java index 9ee192ce3..5c2870cc8 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/MsgUtils.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/util/MsgUtils.java @@ -34,4 +34,14 @@ public class MsgUtils { return messageSource.getMessage(code, objects, Locale.CHINA); } + /** + * security 通过code 和参数获取中文错误信息 + * @param code + * @return + */ + public String getSecurityMessage(String code, Object... objects) { + MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource"); + return messageSource.getMessage(code, objects, Locale.CHINA); + } + } diff --git a/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/PigxDefaultDatascopeHandle.java b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/PigxDefaultDatascopeHandle.java index ad5ade7eb..f48e9fd48 100644 --- a/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/PigxDefaultDatascopeHandle.java +++ b/pigx-common/pigx-common-data/src/main/java/com/pig4cloud/pigx/common/data/datascope/PigxDefaultDatascopeHandle.java @@ -55,9 +55,11 @@ public class PigxDefaultDatascopeHandle implements DataScopeHandle { @Override public Boolean calcScope(DataScope dataScope) { PigxUser user = SecurityUtils.getUser(); + // toc 客户端不进行数据权限 if (UserTypeEnum.TOC.getStatus().equals(user.getUserType())) { return true; } + List roleIdList = user.getAuthorities().stream().map(GrantedAuthority::getAuthority) .filter(authority -> authority.startsWith(SecurityConstants.ROLE)) .map(authority -> authority.split(StrUtil.UNDERLINE)[1]).collect(Collectors.toList()); diff --git a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/SysLogUtils.java b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/SysLogUtils.java index 76f82e683..180afd53b 100644 --- a/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/SysLogUtils.java +++ b/pigx-common/pigx-common-log/src/main/java/com/pig4cloud/pigx/common/log/util/SysLogUtils.java @@ -51,7 +51,7 @@ public class SysLogUtils { HttpServletRequest request = ((ServletRequestAttributes) Objects .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); SysLogDTO sysLog = new SysLogDTO(); - sysLog.setCreateBy(Objects.requireNonNull(getUsername())); + sysLog.setCreateBy(getUsername()); sysLog.setLogType(LogTypeEnum.NORMAL.getType()); sysLog.setRemoteAddr(ServletUtil.getClientIP(request)); sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI())); diff --git a/pigx-common/pigx-common-security/pom.xml b/pigx-common/pigx-common-security/pom.xml index dae0d9695..1044e77bd 100644 --- a/pigx-common/pigx-common-security/pom.xml +++ b/pigx-common/pigx-common-security/pom.xml @@ -45,8 +45,13 @@ - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure + org.springframework.security + spring-security-oauth2-jose + + + org.springframework.security + spring-security-oauth2-authorization-server + ${spring.authorization.version} @@ -73,5 +78,9 @@ com.pig4cloud pigx-app-server-api + + org.springframework + spring-webmvc + diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java index d951467b8..1e01b5c52 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/EnablePigxResourceServer.java @@ -1,43 +1,42 @@ /* - * Copyright (c) 2018-2025, lengleng All rights reserved. + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * 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 * - * 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) + * 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.pig4cloud.pigx.common.security.annotation; -import com.pig4cloud.pigx.common.security.component.PigxResourceServerAutoConfiguration; -import com.pig4cloud.pigx.common.security.component.PigxSecurityBeanDefinitionRegistrar; +import com.pig4cloud.pigx.common.security.component.PigResourceServerAutoConfiguration; +import com.pig4cloud.pigx.common.security.component.PigResourceServerConfiguration; +import com.pig4cloud.pigx.common.security.feign.PigFeignClientConfiguration; import org.springframework.context.annotation.Import; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import java.lang.annotation.*; /** * @author lengleng - * @date 2018/11/10 + * @date 2022-06-04 *

* 资源服务注解 */ @Documented @Inherited -@EnableResourceServer +@EnableMethodSecurity @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) -@EnableGlobalMethodSecurity(prePostEnabled = true) -@Import({ PigxResourceServerAutoConfiguration.class, PigxSecurityBeanDefinitionRegistrar.class }) +@Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class, + PigFeignClientConfiguration.class }) public @interface EnablePigxResourceServer { } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/Inner.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/Inner.java index b90b8a634..fd9654018 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/Inner.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/annotation/Inner.java @@ -1,18 +1,17 @@ /* - * Copyright (c) 2018-2025, lengleng All rights reserved. + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * 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 * - * 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) + * 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.pig4cloud.pigx.common.security.annotation; @@ -20,10 +19,10 @@ package com.pig4cloud.pigx.common.security.annotation; import java.lang.annotation.*; /** + * 服务调用不鉴权注解 + * * @author lengleng - * @date 2019/4/13 - *

- * 服务调用鉴权注解 + * @date 2020-06-14 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermissionService.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermissionService.java index cb0591870..3dee9caa3 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermissionService.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermissionService.java @@ -1,28 +1,26 @@ + /* - * Copyright (c) 2018-2025, lengleng All rights reserved. + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * 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 * - * 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) + * 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.pig4cloud.pigx.common.security.component; import cn.hutool.core.util.ArrayUtil; -import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; @@ -30,10 +28,8 @@ import java.util.Collection; /** * @author lengleng - * @date 2018/6/22 接口权限判断工具 + * @date 2019/2/1 接口权限判断工具 */ -@Slf4j -@Component("pms") public class PermissionService { /** diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java new file mode 100755 index 000000000..76a9d8503 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import cn.hutool.core.util.ReUtil; +import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import com.pig4cloud.pigx.common.security.annotation.Inner; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * @author lengleng + * @date 2020-03-11 + *

+ * 资源服务器对外直接暴露URL,如果设置contex-path 要特殊处理 + */ +@Slf4j +@ConfigurationProperties(prefix = "security.oauth2.ignore") +public class PermitAllUrlProperties implements InitializingBean { + + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private static final String[] DEFAULT_IGNORE_URLS = new String[] { "/actuator/**", "/error", "/v3/api-docs" }; + + @Getter + @Setter + private List urls = new ArrayList<>(); + + @Override + public void afterPropertiesSet() { + urls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS)); + RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping"); + Map map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class); + Optional.ofNullable(method).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition()) + .getPatternValues().forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*")))); + + // 获取类上边的注解, 替代path variable 为 * + Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class); + Optional.ofNullable(controller).ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition()) + .getPatternValues().forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*")))); + }); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlResolver.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlResolver.java deleted file mode 100644 index b02196342..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlResolver.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ReUtil; -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.security.annotation.Inner; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import java.lang.reflect.Method; -import java.util.*; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * @author lengleng - * @date 2019/4/13 - *

- * 资源暴露处理器 - */ -@Slf4j -@Configuration -@RequiredArgsConstructor -@ConditionalOnExpression("!'${security.oauth2.client.ignore-urls}'.isEmpty()") -@ConfigurationProperties(prefix = "security.oauth2.client") -public class PermitAllUrlResolver implements InitializingBean { - - private static final PathMatcher PATHMATCHER = new AntPathMatcher(); - - private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); - - private static final String[] DEFAULT_IGNORE_URLS = new String[] { "/error", "/v2/api-docs" }; - - private final WebApplicationContext applicationContext; - - @Getter - @Setter - private List ignoreUrls = new ArrayList<>(); - - @Override - public void afterPropertiesSet() { - RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", - RequestMappingHandlerMapping.class); - Map map = mapping.getHandlerMethods(); - - for (RequestMappingInfo info : map.keySet()) { - HandlerMethod handlerMethod = map.get(info); - - // 1. 首先获取类上边 @Inner 注解 - Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class); - - // 2. 当类上不包含 @Inner 注解则获取该方法的注解 - if (controller == null) { - Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class); - Optional.ofNullable(method) - .ifPresent(inner -> Objects.requireNonNull(info.getPatternsCondition().getPatterns()) - .forEach(url -> this.filterPath(url, info, map))); - continue; - } - - // 3. 当类上包含 @Inner 注解 判断handlerMethod 是否包含在 inner 类中 - Class beanType = handlerMethod.getBeanType(); - Method[] methods = beanType.getDeclaredMethods(); - Method method = handlerMethod.getMethod(); - if (ArrayUtil.contains(methods, method)) { - Objects.requireNonNull(info.getPatternsCondition().getPatterns()) - .forEach(url -> filterPath(url, info, map)); - } - } - - } - - /** - * 过滤 Inner 设置 - *

- * 0. 暴露安全检查 1. 路径转换: 如果为restful(/xx/{xx}) --> /xx/* ant 表达式 2. - * 构建表达式:允许暴露的接口|允许暴露的方法类型,允许暴露的方法类型 URL|GET,POST,DELETE,PUT - *

- * @param url mapping路径 - * @param info 请求犯法 - * @param map 路由映射信息 - */ - private void filterPath(String url, RequestMappingInfo info, Map map) { - // 安全检查 - if (SecurityConstants.INNER_CHECK) { - security(url, info, map); - } - - List methodList = info.getMethodsCondition().getMethods().stream().map(RequestMethod::name) - .collect(Collectors.toList()); - String resultUrl = ReUtil.replaceAll(url, PATTERN, "*"); - if (CollUtil.isEmpty(methodList)) { - ignoreUrls.add(resultUrl); - } - else { - ignoreUrls.add(String.format("%s|%s", resultUrl, CollUtil.join(methodList, StrUtil.COMMA))); - } - - // 增加默认暴露规则 - for (String ignoreUrl : DEFAULT_IGNORE_URLS) { - ignoreUrls.add(ignoreUrl); - } - } - - /** - * 针对Pathvariable 请求安全检查。增加启动好使影响启动效率 请注意 - * @param url 接口路径 - * @param rq 当前请求的元信息 - * @param map springmvc 接口列表 - */ - private void security(String url, RequestMappingInfo rq, Map map) { - // 判断 URL 是否是 rest path 形式 - if (!StrUtil.containsAny(url, StrUtil.DELIM_START, StrUtil.DELIM_END)) { - return; - } - - for (RequestMappingInfo info : map.keySet()) { - Set methods = info.getMethodsCondition().getMethods(); - // 如果请求方法不匹配跳过 - if (!CollUtil.containsAny(methods, rq.getMethodsCondition().getMethods())) { - continue; - } - - // 如果请求方法路径匹配 - - Set patterns = Objects.requireNonNull(info.getPatternsCondition().getPatterns()); - for (String pattern : patterns) { - // 跳过自身 - if (StrUtil.equals(url, pattern)) { - continue; - } - - if (PATHMATCHER.match(url, pattern)) { - HandlerMethod rqMethod = map.get(rq); - HandlerMethod infoMethod = map.get(info); - log.error("@Inner 标记接口 ==> {}.{} 使用不当,会额外暴露接口 ==> {}.{} 请知悉", rqMethod.getBeanType().getName(), - rqMethod.getMethod().getName(), infoMethod.getBeanType().getName(), - infoMethod.getMethod().getName()); - } - } - } - } - - /** - * 获取对外暴露的URL,注册到 spring security - * @param registry spring security context - */ - public void registry(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { - for (String url : getIgnoreUrls()) { - List strings = StrUtil.split(url, "|"); - - // 仅配置对外暴露的URL ,则注册到 spring security的为全部方法 - if (strings.size() == 1) { - registry.antMatchers(strings.get(0)).permitAll(); - continue; - } - - // 当配置对外的URL|GET,POST 这种形式,则获取方法列表 并注册到 spring security - if (strings.size() == 2) { - for (String method : StrUtil.split(strings.get(1), StrUtil.COMMA)) { - registry.antMatchers(HttpMethod.valueOf(method), strings.get(0)).permitAll(); - } - continue; - } - - log.warn("{} 配置无效,无法配置对外暴露", url); - } - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigBearerTokenExtractor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigBearerTokenExtractor.java new file mode 100644 index 000000000..aa4ea85a0 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigBearerTokenExtractor.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.server.resource.BearerTokenError; +import org.springframework.security.oauth2.server.resource.BearerTokenErrors; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author caiqy + * @date 2020.05.15 + */ +public class PigBearerTokenExtractor implements BearerTokenResolver { + + private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-:._~+/]+=*)$", + Pattern.CASE_INSENSITIVE); + + private boolean allowFormEncodedBodyParameter = false; + + private boolean allowUriQueryParameter = true; + + private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION; + + private final PathMatcher pathMatcher = new AntPathMatcher(); + + private final PermitAllUrlProperties urlProperties; + + public PigBearerTokenExtractor(PermitAllUrlProperties urlProperties) { + this.urlProperties = urlProperties; + } + + @Override + public String resolve(HttpServletRequest request) { + boolean match = urlProperties.getUrls().stream() + .anyMatch(url -> pathMatcher.match(url, request.getRequestURI())); + + if (match) { + return null; + } + + final String authorizationHeaderToken = resolveFromAuthorizationHeader(request); + final String parameterToken = isParameterTokenSupportedForRequest(request) + ? resolveFromRequestParameters(request) : null; + if (authorizationHeaderToken != null) { + if (parameterToken != null) { + final BearerTokenError error = BearerTokenErrors + .invalidRequest("Found multiple bearer tokens in the request"); + throw new OAuth2AuthenticationException(error); + } + return authorizationHeaderToken; + } + if (parameterToken != null && isParameterTokenEnabledForRequest(request)) { + return parameterToken; + } + return null; + } + + private String resolveFromAuthorizationHeader(HttpServletRequest request) { + String authorization = request.getHeader(this.bearerTokenHeaderName); + if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { + return null; + } + Matcher matcher = authorizationPattern.matcher(authorization); + if (!matcher.matches()) { + BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed"); + throw new OAuth2AuthenticationException(error); + } + return matcher.group("token"); + } + + private static String resolveFromRequestParameters(HttpServletRequest request) { + String[] values = request.getParameterValues("access_token"); + if (values == null || values.length == 0) { + return null; + } + if (values.length == 1) { + return values[0]; + } + BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request"); + throw new OAuth2AuthenticationException(error); + } + + private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) { + return (("POST".equals(request.getMethod()) + && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) + || "GET".equals(request.getMethod())); + } + + private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) { + return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod()) + && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) + || (this.allowUriQueryParameter && "GET".equals(request.getMethod()))); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigClientCredentialsOAuth2AuthenticatedPrincipal.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigClientCredentialsOAuth2AuthenticatedPrincipal.java new file mode 100644 index 000000000..bc4481d02 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigClientCredentialsOAuth2AuthenticatedPrincipal.java @@ -0,0 +1,40 @@ +package com.pig4cloud.pigx.common.security.component; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; + +import java.util.Collection; +import java.util.Map; + +/** + * @author lengleng + * @date 2022/7/6 + * + * credential 支持客户端模式的用户存储 + */ +@RequiredArgsConstructor +public class PigClientCredentialsOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal { + + private final Map attributes; + + private final Collection authorities; + + private final String name; + + @Override + public Map getAttributes() { + return this.attributes; + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigCustomOpaqueTokenIntrospector.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigCustomOpaqueTokenIntrospector.java new file mode 100644 index 000000000..2cc6d1e99 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigCustomOpaqueTokenIntrospector.java @@ -0,0 +1,75 @@ +package com.pig4cloud.pigx.common.security.component; + +import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import com.pig4cloud.pigx.common.security.service.PigxUser; +import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.Ordered; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +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.resource.InvalidBearerTokenException; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; + +import java.security.Principal; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * @author lengleng + * @date 2022/5/28 + */ +@Slf4j +@RequiredArgsConstructor +public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector { + + private final OAuth2AuthorizationService authorizationService; + + @Override + public OAuth2AuthenticatedPrincipal introspect(String token) { + OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); + if (Objects.isNull(oldAuthorization)) { + throw new InvalidBearerTokenException(token); + } + + // 客户端模式默认返回 + if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) { + return new PigClientCredentialsOAuth2AuthenticatedPrincipal(oldAuthorization.getAttributes(), + AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName()); + } + + Map userDetailsServiceMap = SpringContextHolder + .getBeansOfType(PigxUserDetailsService.class); + + Optional optional = userDetailsServiceMap.values().stream() + .filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(), + oldAuthorization.getAuthorizationGrantType().getValue())) + .max(Comparator.comparingInt(Ordered::getOrder)); + + UserDetails userDetails = null; + try { + Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName()); + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal; + Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal(); + userDetails = optional.get().loadUserByUser((PigxUser) tokenPrincipal); + } + catch (UsernameNotFoundException notFoundException) { + log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage()); + throw notFoundException; + } + catch (Exception ex) { + log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage()); + } + return (PigxUser) userDetails; + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerAutoConfiguration.java new file mode 100644 index 000000000..342b137f0 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerAutoConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; + +/** + * @author lengleng + * @date 2022-06-02 + */ +@RequiredArgsConstructor +@EnableConfigurationProperties(PermitAllUrlProperties.class) +public class PigResourceServerAutoConfiguration { + + /** + * 鉴权具体的实现逻辑 + * @return (#pms.xxx) + */ + @Bean("pms") + public PermissionService permissionService() { + return new PermissionService(); + } + + /** + * 请求令牌的抽取逻辑 + * @param urlProperties 对外暴露的接口列表 + * @return BearerTokenExtractor + */ + @Bean + public PigBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) { + return new PigBearerTokenExtractor(urlProperties); + } + + /** + * 资源服务器异常处理 + * @param objectMapper jackson 输出对象 + * @param securityMessageSource 自定义国际化处理器 + * @return ResourceAuthExceptionEntryPoint + */ + @Bean + public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper, + MessageSource securityMessageSource) { + return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource); + } + + /** + * 资源服务器toke内省处理器 + * @param authorizationService token 存储实现 + * @return TokenIntrospector + */ + @Bean + public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) { + return new PigCustomOpaqueTokenIntrospector(authorizationService); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerConfiguration.java new file mode 100644 index 000000000..0f3d21a0d --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigResourceServerConfiguration.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import cn.hutool.core.util.ArrayUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.web.SecurityFilterChain; + +/** + * @author lengleng + * @date 2022-06-04 + * + * 资源服务器认证授权配置 + */ +@Slf4j +@EnableWebSecurity +@RequiredArgsConstructor +public class PigResourceServerConfiguration { + + protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; + + private final PermitAllUrlProperties permitAllUrl; + + private final PigBearerTokenExtractor pigBearerTokenExtractor; + + private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; + + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + http.authorizeHttpRequests(authorizeRequests -> authorizeRequests + .antMatchers(ArrayUtil.toArray(permitAllUrl.getUrls(), String.class)).permitAll().anyRequest() + .authenticated()) + .oauth2ResourceServer( + oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) + .authenticationEntryPoint(resourceAuthExceptionEntryPoint) + .bearerTokenResolver(pigBearerTokenExtractor)) + .headers().frameOptions().disable().and().csrf().disable(); + + return http.build(); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityInnerAspect.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityInnerAspect.java new file mode 100644 index 000000000..ea2beab31 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityInnerAspect.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.annotation.Inner; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.security.access.AccessDeniedException; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author lengleng + * @date 2022-06-04 + * + * 服务间接口不鉴权处理逻辑 + */ +@Slf4j +@Aspect +@RequiredArgsConstructor +public class PigSecurityInnerAspect implements Ordered { + + private final HttpServletRequest request; + + @SneakyThrows + @Around("@within(inner) || @annotation(inner)") + public Object around(ProceedingJoinPoint point, Inner inner) { + // 实际注入的inner实体由表达式后一个注解决定,即是方法上的@Inner注解实体,若方法上无@Inner注解,则获取类上的 + if (inner == null) { + Class clazz = point.getTarget().getClass(); + inner = AnnotationUtils.findAnnotation(clazz, Inner.class); + } + String header = request.getHeader(SecurityConstants.FROM); + if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) { + log.warn("访问接口 {} 没有权限", point.getSignature().getName()); + throw new AccessDeniedException("Access is denied"); + } + return point.proceed(); + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE + 1; + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityMessageSourceConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityMessageSourceConfiguration.java similarity index 84% rename from pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityMessageSourceConfiguration.java rename to pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityMessageSourceConfiguration.java index d2bf1df49..a80aa91ee 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityMessageSourceConfiguration.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigSecurityMessageSourceConfiguration.java @@ -22,21 +22,24 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.Locale; + import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET; /** * @author lengleng - * @date 2022年01月06日 + * @date 2022-06-04 *

- * 注入自定义错误处理 + * 注入自定义错误处理,覆盖 org/springframework/security/messages 内置异常 */ @ConditionalOnWebApplication(type = SERVLET) -public class PigxSecurityMessageSourceConfiguration implements WebMvcConfigurer { +public class PigSecurityMessageSourceConfiguration implements WebMvcConfigurer { @Bean public MessageSource securityMessageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.addBasenames("classpath:errors/messages"); + messageSource.setDefaultLocale(Locale.CHINA); return messageSource; } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxAuth2ExceptionSerializer.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxAuth2ExceptionSerializer.java deleted file mode 100644 index 0a2a12b66..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxAuth2ExceptionSerializer.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import com.pig4cloud.pigx.common.security.exception.PigxAuth2Exception; -import lombok.SneakyThrows; - -/** - * @author lengleng - * @date 2018/11/16 - *

- * OAuth2 异常格式化 - */ -public class PigxAuth2ExceptionSerializer extends StdSerializer { - - public PigxAuth2ExceptionSerializer() { - super(PigxAuth2Exception.class); - } - - @Override - @SneakyThrows - public void serialize(PigxAuth2Exception value, JsonGenerator gen, SerializerProvider provider) { - gen.writeStartObject(); - gen.writeObjectField("code", CommonConstants.FAIL); - gen.writeStringField("msg", value.getMessage()); - gen.writeStringField("data", value.getErrorCode()); - gen.writeEndObject(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java deleted file mode 100644 index f1443060c..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com). - * *

- * * Licensed under the GNU Lesser General Public License 3.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * *

- * * https://www.gnu.org/licenses/lgpl.html - * *

- * * 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.pig4cloud.pigx.common.security.component; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor; -import org.springframework.stereotype.Component; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; - -import javax.servlet.http.HttpServletRequest; -import java.util.List; - -/** - * 改造 {@link BearerTokenExtractor} 对公开权限的请求不进行校验 - * - * @author caiqy - * @date 2020.05.15 - */ -@Component -@RequiredArgsConstructor -public class PigxBearerTokenExtractor extends BearerTokenExtractor { - - private final PathMatcher pathMatcher = new AntPathMatcher(); - - private final PermitAllUrlResolver permitAllUrlResolver; - - @Override - public Authentication extract(HttpServletRequest request) { - - // 2. 判断请求方法是否匹配 - boolean result = permitAllUrlResolver.getIgnoreUrls().stream().anyMatch(url -> { - List strings = StrUtil.split(url, "|"); - // 1. 判断路径是否匹配 - boolean match = pathMatcher.match(strings.get(0), request.getRequestURI()); - // 2. 判断方法是否匹配 - if (strings.size() == 2) { - List methods = StrUtil.split(strings.get(1), StrUtil.COMMA); - return CollUtil.contains(methods, request.getMethod()) && match; - } - return match; - }); - return result ? null : super.extract(request); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxCommenceAuthExceptionEntryPoint.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxCommenceAuthExceptionEntryPoint.java deleted file mode 100644 index 3e2fb8cb9..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxCommenceAuthExceptionEntryPoint.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.CredentialsExpiredException; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.PrintWriter; - -/** - * 针对资源服务器的异常处理 {@link OAuth2AuthenticationProcessingFilter}不同细化异常处理 - * - * @author lengleng - * @date 2020-06-14 - */ -@Slf4j -@Component -@AllArgsConstructor -public class PigxCommenceAuthExceptionEntryPoint implements AuthenticationEntryPoint { - - private final ObjectMapper objectMapper; - - @Override - @SneakyThrows - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) { - response.setCharacterEncoding(CommonConstants.UTF8); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - R result = new R<>(); - result.setMsg(authException.getMessage()); - result.setData(authException.getMessage()); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - result.setCode(CommonConstants.FAIL); - - if (authException instanceof CredentialsExpiredException) { - String msg = PigxSecurityMessageSourceUtil.getAccessor().getMessage( - "AbstractUserDetailsAuthenticationProvider.credentialsExpired", authException.getMessage()); - result.setMsg(msg); - } - - if (authException instanceof UsernameNotFoundException) { - String msg = PigxSecurityMessageSourceUtil.getAccessor().getMessage( - "AbstractUserDetailsAuthenticationProvider.noopBindAccount", authException.getMessage()); - result.setMsg(msg); - } - - if (authException instanceof BadCredentialsException) { - String msg = PigxSecurityMessageSourceUtil.getAccessor().getMessage( - "AbstractUserDetailsAuthenticationProvider.badClientCredentials", authException.getMessage()); - result.setMsg(msg); - } - - if (authException instanceof InsufficientAuthenticationException) { - String msg = PigxSecurityMessageSourceUtil.getAccessor() - .getMessage("AbstractAccessDecisionManager.expireToken", authException.getMessage()); - response.setStatus(HttpStatus.FAILED_DEPENDENCY.value()); - result.setMsg(msg); - } - - PrintWriter printWriter = response.getWriter(); - printWriter.append(objectMapper.writeValueAsString(result)); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java deleted file mode 100644 index 891a851a4..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerConfigurerAdapter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -import org.springframework.security.oauth2.provider.authentication.TokenExtractor; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.web.AuthenticationEntryPoint; - -/** - * @author lengleng - * @date 2020/9/30 - *

- * 支持本地模式 [不进过认证中心 CheckToken]的的资源服务器配置 - */ -@Slf4j -public class PigxLocalResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter { - - @Autowired - protected AuthenticationEntryPoint resourceAuthExceptionEntryPoint; - - @Autowired - private PermitAllUrlResolver permitAllUrlResolver; - - @Autowired - private TokenExtractor tokenExtractor; - - @Autowired - private ResourceServerTokenServices resourceServerTokenServices; - - /** - * 默认的配置,对外暴露 - * @param httpSecurity - */ - @Override - @SneakyThrows - public void configure(HttpSecurity httpSecurity) { - // 允许使用iframe 嵌套,避免swagger-ui 不被加载的问题 - httpSecurity.headers().frameOptions().disable(); - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity - .authorizeRequests(); - // 配置对外暴露接口 - permitAllUrlResolver.registry(registry); - registry.anyRequest().authenticated().and().csrf().disable(); - } - - @Override - public void configure(ResourceServerSecurityConfigurer resources) { - resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint).tokenExtractor(tokenExtractor) - .tokenServices(resourceServerTokenServices); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java deleted file mode 100644 index cd69341de..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxLocalResourceServerTokenServices.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.pig4cloud.pigx.common.security.component; - -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import com.pig4cloud.pigx.common.security.exception.UnauthorizedException; -import com.pig4cloud.pigx.common.security.service.PigxUser; -import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService; -import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Primary; -import org.springframework.core.Ordered; -import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.authentication.InternalAuthenticationServiceException; -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.UserDetailsChecker; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.stereotype.Service; - -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; - -/** - * @author lengleng - * @date 2020/9/29 - */ -@Primary -@Service -@RequiredArgsConstructor -public class PigxLocalResourceServerTokenServices implements ResourceServerTokenServices { - - private final TokenStore tokenStore; - - private final UserDetailsChecker preAuthenticationChecks; - - @Override - public OAuth2Authentication loadAuthentication(String accessToken) - throws AuthenticationException, InvalidTokenException { - OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken); - if (oAuth2Authentication == null) { - return null; - } - - OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request(); - if (!(oAuth2Authentication.getPrincipal() instanceof PigxUser)) { - return oAuth2Authentication; - } - - String clientId = oAuth2Request.getClientId(); - - Map userDetailsServiceMap = SpringContextHolder - .getBeansOfType(PigxUserDetailsService.class); - - Optional optional = userDetailsServiceMap.values().stream() - .filter(service -> service.support(clientId, oAuth2Request.getGrantType())) - .max(Comparator.comparingInt(Ordered::getOrder)); - - if (!optional.isPresent()) { - throw new InternalAuthenticationServiceException("UserDetailsService error , not register"); - } - - // 根据 username 查询 spring cache 最新的值 并返回 - PigxUser pigUser = (PigxUser) oAuth2Authentication.getPrincipal(); - - UserDetails userDetails; - try { - userDetails = optional.get().loadUserByUser(pigUser); - - preAuthenticationChecks.check(userDetails); - } - catch (UsernameNotFoundException notFoundException) { - throw new UnauthorizedException(String.format("%s username not found", pigUser.getUsername()), - notFoundException); - } - catch (AccountStatusException accountStatusException) { - throw new UnauthorizedException( - PigxSecurityMessageSourceUtil.getAccessor().getMessage( - "AbstractUserDetailsAuthenticationProvider.locked", accountStatusException.getMessage()), - accountStatusException); - } - Authentication userAuthentication = new UsernamePasswordAuthenticationToken(userDetails, "N/A", - userDetails.getAuthorities()); - OAuth2Authentication authentication = new OAuth2Authentication(oAuth2Request, userAuthentication); - authentication.setAuthenticated(true); - return authentication; - } - - @Override - public OAuth2AccessToken readAccessToken(String accessToken) { - throw new UnsupportedOperationException("Not supported: read access token"); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxPreAuthenticationChecks.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxPreAuthenticationChecks.java deleted file mode 100644 index be30c7c73..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxPreAuthenticationChecks.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.authentication.AccountExpiredException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsChecker; - -/** - * @author lengleng - * @date 2019-01-02 - */ -@Slf4j -public class PigxPreAuthenticationChecks implements UserDetailsChecker { - - private MessageSourceAccessor messages = PigxSecurityMessageSourceUtil.getAccessor(); - - @Override - public void check(UserDetails user) { - if (!user.isAccountNonLocked()) { - log.debug("User account is locked"); - - throw new LockedException( - messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked")); - } - - if (!user.isEnabled()) { - log.debug("User account is disabled"); - - throw new DisabledException( - messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled")); - } - - if (!user.isAccountNonExpired()) { - log.debug("User account is expired"); - - throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", - "User account has expired")); - } - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxRedisTokenStore.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxRedisTokenStore.java deleted file mode 100644 index ce42a655b..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxRedisTokenStore.java +++ /dev/null @@ -1,483 +0,0 @@ -package com.pig4cloud.pigx.common.security.component; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.RedisZSetCommands; -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; -import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; -import org.springframework.security.oauth2.provider.token.TokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; -import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -import java.lang.reflect.Method; -import java.util.*; - -/** - * @author efenderbosch - * @date 2020/9/30 - *

- * @link https://github.com/spring-projects/spring-security-oauth/pull/1660 - * 重写RedisTokenStore ,主要解决 #1814 oauth2中client_id_to_access数据膨胀问题 - */ -public class PigxRedisTokenStore implements TokenStore { - - private static final String ACCESS = "access:"; - - private static final String AUTH_TO_ACCESS = "auth_to_access:"; - - private static final String AUTH = "auth:"; - - private static final String REFRESH_AUTH = "refresh_auth:"; - - private static final String REFRESH = "refresh:"; - - private static final String REFRESH_TO_ACCESS = "refresh_to_access:"; - - private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access_z:"; - - private static final String UNAME_TO_ACCESS = "uname_to_access_z:"; - - private static final boolean springDataRedis_2_0 = ClassUtils.isPresent( - "org.springframework.data.redis.connection.RedisStandaloneConfiguration", - RedisTokenStore.class.getClassLoader()); - - private final RedisConnectionFactory connectionFactory; - - private final KeyStrResolver keyStrResolver; - - private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator(); - - private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy(); - - private String prefix = ""; - - private Method redisConnectionSet_2_0; - - public PigxRedisTokenStore(RedisConnectionFactory connectionFactory, KeyStrResolver resolver) { - this.keyStrResolver = resolver; - this.connectionFactory = connectionFactory; - if (springDataRedis_2_0) { - this.loadRedisConnectionMethods_2_0(); - } - } - - public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) { - this.authenticationKeyGenerator = authenticationKeyGenerator; - } - - public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) { - this.serializationStrategy = serializationStrategy; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - private void loadRedisConnectionMethods_2_0() { - this.redisConnectionSet_2_0 = ReflectionUtils.findMethod(RedisConnection.class, "set", byte[].class, - byte[].class); - } - - private RedisConnection getConnection() { - return connectionFactory.getConnection(); - } - - private byte[] serialize(Object object) { - return serializationStrategy.serialize(object); - } - - private byte[] serializeKey(String object) { - return serialize(keyStrResolver.extract((prefix + object), StrUtil.COLON)); - } - - private OAuth2AccessToken deserializeAccessToken(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class); - } - - private OAuth2Authentication deserializeAuthentication(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2Authentication.class); - } - - private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) { - return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class); - } - - private byte[] serialize(String string) { - return serializationStrategy.serialize(string); - } - - private String deserializeString(byte[] bytes) { - return serializationStrategy.deserializeString(bytes); - } - - @Override - public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - String key = authenticationKeyGenerator.extractKey(authentication); - byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key); - byte[] bytes; - try (RedisConnection conn = getConnection()) { - bytes = conn.get(serializedKey); - } - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - if (accessToken != null) { - OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue()); - if ((storedAuthentication == null - || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) { - // Keep the stores consistent (maybe the same user is - // represented by this authentication but the details have - // changed) - storeAccessToken(accessToken, authentication); - } - - } - return accessToken; - } - - @Override - public OAuth2Authentication readAuthentication(OAuth2AccessToken token) { - return readAuthentication(token.getValue()); - } - - @Override - public OAuth2Authentication readAuthentication(String token) { - byte[] bytes; - try (RedisConnection conn = getConnection()) { - bytes = conn.get(serializeKey(AUTH + token)); - } - return deserializeAuthentication(bytes); - } - - @Override - public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) { - return readAuthenticationForRefreshToken(token.getValue()); - } - - public OAuth2Authentication readAuthenticationForRefreshToken(String token) { - try (RedisConnection conn = getConnection()) { - byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token)); - return deserializeAuthentication(bytes); - } - } - - @Override - public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) { - byte[] serializedAccessToken = serialize(token); - byte[] serializedAuth = serialize(authentication); - byte[] accessKey = serializeKey(ACCESS + token.getValue()); - byte[] authKey = serializeKey(AUTH + token.getValue()); - byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication)); - byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); - byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); - - try (RedisConnection conn = getConnection()) { - conn.openPipeline(); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, accessKey, serializedAccessToken); - this.redisConnectionSet_2_0.invoke(conn, authKey, serializedAuth); - this.redisConnectionSet_2_0.invoke(conn, authToAccessKey, serializedAccessToken); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(accessKey, serializedAccessToken); - conn.set(authKey, serializedAuth); - conn.set(authToAccessKey, serializedAccessToken); - } - - if (token.getExpiration() != null) { - int seconds = token.getExpiresIn(); - long expirationTime = token.getExpiration().getTime(); - - if (!authentication.isClientOnly()) { - conn.zAdd(approvalKey, expirationTime, serializedAccessToken); - } - conn.zAdd(clientId, expirationTime, serializedAccessToken); - - conn.expire(accessKey, seconds); - conn.expire(authKey, seconds); - conn.expire(authToAccessKey, seconds); - conn.expire(clientId, seconds); - conn.expire(approvalKey, seconds); - } - else { - conn.zAdd(clientId, -1, serializedAccessToken); - if (!authentication.isClientOnly()) { - conn.zAdd(approvalKey, -1, serializedAccessToken); - } - } - OAuth2RefreshToken refreshToken = token.getRefreshToken(); - if (refreshToken != null && refreshToken.getValue() != null) { - byte[] auth = serialize(token.getValue()); - byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue()); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, refreshToAccessKey, auth); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(refreshToAccessKey, auth); - } - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; - Date expiration = expiringRefreshToken.getExpiration(); - if (expiration != null) { - int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L) - .intValue(); - conn.expire(refreshToAccessKey, seconds); - } - } - } - conn.closePipeline(); - } - } - - private static String getApprovalKey(OAuth2Authentication authentication) { - String userName = authentication.getUserAuthentication() == null ? "" - : authentication.getUserAuthentication().getName(); - return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName); - } - - private static String getApprovalKey(String clientId, String userName) { - return clientId + (userName == null ? "" : ":" + userName); - } - - @Override - public void removeAccessToken(OAuth2AccessToken accessToken) { - removeAccessToken(accessToken.getValue()); - } - - @Override - public OAuth2AccessToken readAccessToken(String tokenValue) { - byte[] key = serializeKey(ACCESS + tokenValue); - byte[] bytes; - try (RedisConnection conn = getConnection()) { - bytes = conn.get(key); - } - return deserializeAccessToken(bytes); - } - - public void removeAccessToken(String tokenValue) { - byte[] accessKey = serializeKey(ACCESS + tokenValue); - byte[] authKey = serializeKey(AUTH + tokenValue); - try (RedisConnection conn = getConnection()) { - conn.openPipeline(); - conn.get(accessKey); - conn.get(authKey); - conn.del(accessKey); - // Don't remove the refresh token - it's up to the caller to do that - conn.del(authKey); - List results = conn.closePipeline(); - byte[] access = (byte[]) results.get(0); - byte[] auth = (byte[]) results.get(1); - - OAuth2Authentication authentication = deserializeAuthentication(auth); - if (authentication != null) { - String key = authenticationKeyGenerator.extractKey(authentication); - byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key); - byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication)); - byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId()); - conn.openPipeline(); - conn.del(authToAccessKey); - conn.zRem(unameKey, access); - conn.zRem(clientId, access); - conn.del(serialize(ACCESS + key)); - conn.closePipeline(); - } - } - } - - @Override - public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) { - byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue()); - byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue()); - byte[] serializedRefreshToken = serialize(refreshToken); - try (RedisConnection conn = getConnection()) { - conn.openPipeline(); - if (springDataRedis_2_0) { - try { - this.redisConnectionSet_2_0.invoke(conn, refreshKey, serializedRefreshToken); - this.redisConnectionSet_2_0.invoke(conn, refreshAuthKey, serialize(authentication)); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - else { - conn.set(refreshKey, serializedRefreshToken); - conn.set(refreshAuthKey, serialize(authentication)); - } - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken; - Date expiration = expiringRefreshToken.getExpiration(); - if (expiration != null) { - int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L).intValue(); - conn.expire(refreshKey, seconds); - conn.expire(refreshAuthKey, seconds); - } - } - conn.closePipeline(); - } - } - - @Override - public OAuth2RefreshToken readRefreshToken(String tokenValue) { - byte[] key = serializeKey(REFRESH + tokenValue); - byte[] bytes; - try (RedisConnection conn = getConnection()) { - bytes = conn.get(key); - } - return deserializeRefreshToken(bytes); - } - - @Override - public void removeRefreshToken(OAuth2RefreshToken refreshToken) { - removeRefreshToken(refreshToken.getValue()); - } - - public void removeRefreshToken(String tokenValue) { - byte[] refreshKey = serializeKey(REFRESH + tokenValue); - byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue); - byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue); - try (RedisConnection conn = getConnection()) { - conn.openPipeline(); - conn.del(refreshKey); - conn.del(refreshAuthKey); - conn.del(refresh2AccessKey); - conn.closePipeline(); - } - } - - @Override - public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) { - removeAccessTokenUsingRefreshToken(refreshToken.getValue()); - } - - private void removeAccessTokenUsingRefreshToken(String refreshToken) { - byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken); - List results; - try (RedisConnection conn = getConnection()) { - conn.openPipeline(); - conn.get(key); - conn.del(key); - results = conn.closePipeline(); - } - byte[] bytes = (byte[]) results.get(0); - String accessToken = deserializeString(bytes); - if (accessToken != null) { - removeAccessToken(accessToken); - } - } - - private List getZByteLists(byte[] key, RedisConnection conn) { - // Sorted Set expiration maintenance - long currentTime = System.currentTimeMillis(); - conn.zRemRangeByScore(key, 0, currentTime); - - List byteList; - Long size = conn.zCard(key); - assert size != null; - byteList = new ArrayList<>(size.intValue()); - Cursor cursor = conn.zScan(key, ScanOptions.NONE); - - while (cursor.hasNext()) { - RedisZSetCommands.Tuple t = cursor.next(); - - // Probably not necessary because of the maintenance at the beginning but why - // not... - if (t.getScore() == -1 || t.getScore() > currentTime) { - byteList.add(t.getValue()); - } - } - return byteList; - } - - /** - * Runs a maintenance of the RedisTokenStore. - *

- * SortedSets UNAME_TO_ACCESS and CLIENT_ID_TO_ACCESS contains access tokens that can - * expire. This expiration is set as a score of the Redis SortedSet data structure. - * Redis does not support expiration of items in a container data structure. It - * supports only expiration of whole key. In case there is still new access tokens - * being stored into the RedisTokenStore before whole key gets expired, the expiration - * is prolonged and the key is not effectively deleted. To do "garbage collection" - * this method should be called once upon a time. - * @return how many items were removed - */ - public long doMaintenance() { - long removed = 0; - try (RedisConnection conn = getConnection()) { - // client_id_to_acccess maintenance - Cursor clientToAccessKeys = conn.scan(ScanOptions.scanOptions() - .match(keyStrResolver.extract(prefix + CLIENT_ID_TO_ACCESS + "*", StrUtil.COLON)).build()); - while (clientToAccessKeys.hasNext()) { - byte[] clientIdToAccessKey = clientToAccessKeys.next(); - - removed += conn.zRemRangeByScore(clientIdToAccessKey, 0, System.currentTimeMillis()); - } - - // uname_to_access maintenance - Cursor unameToAccessKeys = conn.scan(ScanOptions.scanOptions() - .match(keyStrResolver.extract(prefix + UNAME_TO_ACCESS + "*", StrUtil.COLON)).build()); - while (unameToAccessKeys.hasNext()) { - byte[] unameToAccessKey = unameToAccessKeys.next(); - - removed += conn.zRemRangeByScore(unameToAccessKey, 0, System.currentTimeMillis()); - } - } - return removed; - } - - @Override - public Collection findTokensByClientIdAndUserName(String clientId, String userName) { - byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName)); - List byteList; - try (RedisConnection conn = getConnection()) { - byteList = getZByteLists(approvalKey, conn); - } - if (byteList.size() == 0) { - return Collections.emptySet(); - } - List accessTokens = new ArrayList<>(byteList.size()); - for (byte[] bytes : byteList) { - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - accessTokens.add(accessToken); - } - return Collections.unmodifiableCollection(accessTokens); - } - - @Override - public Collection findTokensByClientId(String clientId) { - byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId); - List byteList; - try (RedisConnection conn = getConnection()) { - byteList = getZByteLists(key, conn); - } - if (byteList.size() == 0) { - return Collections.emptySet(); - } - List accessTokens = new ArrayList<>(byteList.size()); - for (byte[] bytes : byteList) { - OAuth2AccessToken accessToken = deserializeAccessToken(bytes); - accessTokens.add(accessToken); - } - return Collections.unmodifiableCollection(accessTokens); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java deleted file mode 100644 index 8fc399fcb..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.pig4cloud.pigx.common.security.handler.RestResponseErrorHandler; -import org.springframework.beans.BeansException; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Primary; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; -import org.springframework.web.client.RestTemplate; - -import java.util.ArrayList; -import java.util.Map; - -/** - * @author lengleng - * @date 2018/11/10 - */ -@ComponentScan("com.pig4cloud.pigx.common.security") -public class PigxResourceServerAutoConfiguration implements ApplicationContextAware { - - private ApplicationContext applicationContext; - - @Bean - @Primary - @LoadBalanced - public RestTemplate lbRestTemplate() { - // 获取上下文配置的ClientHttpRequestInterceptor 实现 - Map beansOfType = applicationContext - .getBeansOfType(ClientHttpRequestInterceptor.class); - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setInterceptors(new ArrayList<>(beansOfType.values())); - restTemplate.setErrorHandler(new RestResponseErrorHandler()); - return restTemplate; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * 支持 #oauth2.xxx 校验注解 - * @return OAuth2MethodSecurityExpressionHandler - */ - @Bean - public OAuth2MethodSecurityExpressionHandler expressionHandler() { - OAuth2MethodSecurityExpressionHandler oAuth2MethodSecurityExpressionHandler = new OAuth2MethodSecurityExpressionHandler(); - oAuth2MethodSecurityExpressionHandler.setApplicationContext(applicationContext); - return oAuth2MethodSecurityExpressionHandler; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerTokenRelayAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerTokenRelayAutoConfiguration.java deleted file mode 100644 index e39582e0a..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerTokenRelayAutoConfiguration.java +++ /dev/null @@ -1,78 +0,0 @@ - -/* - * 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.pigx.common.security.component; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.AllNestedConditions; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration; -import org.springframework.cloud.commons.security.AccessTokenContextRelay; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration; - -import java.lang.annotation.*; - -/** - * @author lengleng - * @date 2019/4/14 注入AccessTokenContextRelay 解决feign 传递token 为空问题 - */ -@Configuration -@AutoConfigureAfter(OAuth2AutoConfiguration.class) -@ConditionalOnWebApplication -@ConditionalOnProperty("security.oauth2.client.client-id") -public class PigxResourceServerTokenRelayAutoConfiguration { - - @Bean - public AccessTokenContextRelay accessTokenContextRelay(OAuth2ClientContext context) { - return new AccessTokenContextRelay(context); - } - - @Target({ ElementType.TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @Conditional(OAuth2OnClientInResourceServerCondition.class) - @interface ConditionalOnOAuth2ClientInResourceServer { - - } - - private static class OAuth2OnClientInResourceServerCondition extends AllNestedConditions { - - public OAuth2OnClientInResourceServerCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnBean(ResourceServerConfiguration.class) - static class Server { - - } - - @ConditionalOnBean(OAuth2ClientConfiguration.class) - static class Client { - - } - - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java deleted file mode 100644 index 9c71506e8..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityBeanDefinitionRegistrar.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.type.AnnotationMetadata; - -/** - * @author lengleng - * @date 2018-11-24 - */ -@Slf4j -public class PigxSecurityBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { - - /** - * 根据注解值动态注入资源服务器的相关属性 - * @param metadata 注解信息 - * @param registry 注册器 - */ - @Override - public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { - if (registry.isBeanNameInUse(SecurityConstants.RESOURCE_SERVER_CONFIGURER)) { - log.warn("本地存在资源服务器配置,覆盖默认配置:" + SecurityConstants.RESOURCE_SERVER_CONFIGURER); - return; - } - - GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(PigxLocalResourceServerConfigurerAdapter.class); - registry.registerBeanDefinition(SecurityConstants.RESOURCE_SERVER_CONFIGURER, beanDefinition); - - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityCommonAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityCommonAutoConfiguration.java deleted file mode 100644 index d44f78133..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityCommonAutoConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.pig4cloud.pigx.common.security.component; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.KeyStrResolver; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.security.core.userdetails.UserDetailsChecker; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator; -import org.springframework.security.oauth2.provider.token.TokenStore; - -/** - * @author lengleng - * @date 2022-04-28 - *

- * 资源服务器 认证服务器 公用配置 - */ -@RequiredArgsConstructor -@Configuration(proxyBeanMethods = false) -public class PigxSecurityCommonAutoConfiguration { - - private final KeyStrResolver resolver; - - private final RedisConnectionFactory connectionFactory; - - @Bean - public TokenStore tokenStore() { - PigxRedisTokenStore tokenStore = new PigxRedisTokenStore(connectionFactory, resolver); - tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX); - tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() { - @Override - public String extractKey(OAuth2Authentication authentication) { - // 增加租户隔离部分 租户ID:原生计算值 - return resolver.extract(super.extractKey(authentication), StrUtil.COLON); - } - }); - return tokenStore; - } - - /** - * 认证状态检查 - * @return UserDetailsChecker - */ - @Bean - public UserDetailsChecker preAuthenticationChecks() { - return new PigxPreAuthenticationChecks(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityInnerAspect.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityInnerAspect.java deleted file mode 100644 index 956e94220..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxSecurityInnerAspect.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import cn.hutool.core.util.StrUtil; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.security.annotation.Inner; -import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.access.AccessDeniedException; - -import javax.servlet.http.HttpServletRequest; - -/** - * @author lengleng - * @date 2018/11/26 - *

- * 服务间接口不鉴权处理逻辑 - */ -@Slf4j -@Aspect -@AllArgsConstructor -public class PigxSecurityInnerAspect { - - private final HttpServletRequest request; - - @SneakyThrows - @Around("@within(inner) || @annotation(inner)") - public Object around(ProceedingJoinPoint point, Inner inner) { - // 先判断 inner 是否为空, 为空则获取类上注解 - if (inner == null) { - Class aClass = point.getTarget().getClass(); - inner = AnnotationUtils.findAnnotation(aClass, Inner.class); - } - - String header = request.getHeader(SecurityConstants.FROM); - if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) { - log.warn("访问接口 {} 没有权限", inner.value()); - throw new AccessDeniedException(PigxSecurityMessageSourceUtil.getAccessor().getMessage( - "AbstractAccessDecisionManager.accessDenied", new Object[] { inner.value() }, "access denied")); - } - return point.proceed(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenEnhancer.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenEnhancer.java deleted file mode 100644 index 269ddb9c5..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenEnhancer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.security.service.PigxUser; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.TokenEnhancer; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author lengleng - * @date 2019-09-17 - *

- * token增强,客户端模式不增强。 - */ -public class PigxTokenEnhancer implements TokenEnhancer { - - /** - * Provides an opportunity for customization of an access token (e.g. through its - * additional information map) during the process of creating a new token for use by a - * client. - * @param accessToken the current access token with its expiration and refresh token - * @param authentication the current authentication including client and user details - * @return a new token enhanced with additional information - */ - @Override - public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { - - final Map additionalInfo = new HashMap<>(8); - String clientId = authentication.getOAuth2Request().getClientId(); - additionalInfo.put(SecurityConstants.CLIENT_ID, clientId); - additionalInfo.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PIGX_LICENSE); - additionalInfo.put(SecurityConstants.ACTIVE, Boolean.TRUE); - - // 客户端模式不返回具体用户信息 - if (SecurityConstants.CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) { - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); - return accessToken; - } - - PigxUser pigxUser = (PigxUser) authentication.getUserAuthentication().getPrincipal(); - additionalInfo.put(SecurityConstants.DETAILS_USER_ID, pigxUser.getId()); - additionalInfo.put(SecurityConstants.DETAILS_USERNAME, pigxUser.getUsername()); - ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); - return accessToken; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreCleanSchedule.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreCleanSchedule.java deleted file mode 100644 index aee2a0354..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxTokenStoreCleanSchedule.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.pig4cloud.pigx.common.security.component; - -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; - -/** - * @author lengleng - * @date 2021/10/3 - */ -@Slf4j -@EnableScheduling -@ConditionalOnBean(AuthorizationServerConfigurerAdapter.class) -public class PigxTokenStoreCleanSchedule { - - @Scheduled(cron = "@hourly") - public void doMaintenance() { - PigxRedisTokenStore tokenStore = SpringContextHolder.getBean(PigxRedisTokenStore.class); - long maintenance = tokenStore.doMaintenance(); - log.debug("清理Redis ZADD 过期 token 数量: {}", maintenance); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxWebResponseExceptionTranslator.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxWebResponseExceptionTranslator.java deleted file mode 100644 index 3b878e08c..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxWebResponseExceptionTranslator.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.pigx.common.security.component; - -import com.pig4cloud.pigx.common.security.exception.*; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer; -import org.springframework.security.oauth2.common.exceptions.ClientAuthenticationException; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; -import org.springframework.security.web.util.ThrowableAnalyzer; -import org.springframework.web.HttpRequestMethodNotSupportedException; - -/** - * @author lengleng - * @date 2020-06-20 OAutH Server 异常处理,重写oauth 默认实现 - */ -@Slf4j -public class PigxWebResponseExceptionTranslator implements WebResponseExceptionTranslator { - - private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); - - @Override - public ResponseEntity translate(Exception e) { - - // Try to extract a SpringSecurityException from the stacktrace - Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e); - - Exception ase = (AuthenticationException) throwableAnalyzer - .getFirstThrowableOfType(AuthenticationException.class, causeChain); - if (ase != null) { - return handleOAuth2Exception(new UnauthorizedException(e.getMessage(), e)); - } - - ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, - causeChain); - if (ase != null) { - return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase)); - } - - ase = (InvalidGrantException) throwableAnalyzer.getFirstThrowableOfType(InvalidGrantException.class, - causeChain); - if (ase != null) { - return handleOAuth2Exception(new InvalidException(ase.getLocalizedMessage(), ase)); - } - - ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer - .getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain); - if (ase != null) { - return handleOAuth2Exception(new MethodNotAllowedException(ase.getMessage(), ase)); - } - - // 处理不合法的令牌错误 424 返回 - ase = (InvalidTokenException) throwableAnalyzer.getFirstThrowableOfType(InvalidTokenException.class, - causeChain); - if (ase != null) { - return handleOAuth2Exception(new TokenInvalidException(ase.getMessage(), ase)); - } - - ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain); - - if (ase != null) { - return handleOAuth2Exception((OAuth2Exception) ase); - } - - return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e)); - - } - - private ResponseEntity handleOAuth2Exception(OAuth2Exception e) { - int status = e.getHttpErrorCode(); - // 客户端异常直接返回客户端,不然无法解析 - if (e instanceof ClientAuthenticationException) { - return new ResponseEntity<>(e, HttpStatus.valueOf(status)); - } - return new ResponseEntity<>(new PigxAuth2Exception(e.getMessage(), e.getOAuth2ErrorCode()), - HttpStatus.valueOf(status)); - - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/ResourceAuthExceptionEntryPoint.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/ResourceAuthExceptionEntryPoint.java new file mode 100755 index 000000000..9f3cd0f0e --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/ResourceAuthExceptionEntryPoint.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.component; + +import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.util.R; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +/** + * @author lengleng + * @date 2019/2/1 + * + * 客户端异常处理 AuthenticationException 不同细化异常处理 + */ +@RequiredArgsConstructor +public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + + private final MessageSource messageSource; + + @Override + @SneakyThrows + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) { + response.setCharacterEncoding(CommonConstants.UTF8); + response.setContentType(ContentType.JSON.getValue()); + R result = new R<>(); + result.setCode(CommonConstants.FAIL); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + if (authException != null) { + result.setMsg("error"); + result.setData(authException.getMessage()); + } + + // 针对令牌过期返回特殊的 424 + if (authException instanceof InvalidBearerTokenException + || authException instanceof InsufficientAuthenticationException) { + response.setStatus(HttpStatus.FAILED_DEPENDENCY.value()); + result.setMsg(this.messageSource.getMessage("OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired", + null, LocaleContextHolder.getLocale())); + } + PrintWriter printWriter = response.getWriter(); + printWriter.append(objectMapper.writeValueAsString(result)); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ForbiddenException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ForbiddenException.java deleted file mode 100644 index 1d3e87ba9..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ForbiddenException.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import org.springframework.http.HttpStatus; - -/** - * @author lengleng - * @date 2018/7/8 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class ForbiddenException extends PigxAuth2Exception { - - public ForbiddenException(String msg) { - super(msg); - } - - public ForbiddenException(String msg, Throwable t) { - super(msg, t); - } - - @Override - public String getOAuth2ErrorCode() { - return "access_denied"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.FORBIDDEN.value(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/InvalidException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/InvalidException.java deleted file mode 100644 index 80aaa00a5..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/InvalidException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; - -/** - * @author lengleng - * @date 2018/7/8 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class InvalidException extends PigxAuth2Exception { - - public InvalidException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "invalid_exception"; - } - - @Override - public int getHttpErrorCode() { - return 426; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/MethodNotAllowedException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/MethodNotAllowedException.java deleted file mode 100644 index 489a9cb0f..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/MethodNotAllowedException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import org.springframework.http.HttpStatus; - -/** - * @author lengleng - * @date 2018/7/8 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class MethodNotAllowedException extends PigxAuth2Exception { - - public MethodNotAllowedException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "method_not_allowed"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.METHOD_NOT_ALLOWED.value(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/PigxAuth2Exception.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/PigxAuth2Exception.java deleted file mode 100644 index c83c1aa47..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/PigxAuth2Exception.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import lombok.Getter; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; - -/** - * @author lengleng - * @date 2018/7/8 自定义OAuth2Exception - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class PigxAuth2Exception extends OAuth2Exception { - - @Getter - private String errorCode; - - public PigxAuth2Exception(String msg) { - super(msg); - } - - public PigxAuth2Exception(String msg, Throwable t) { - super(msg, t); - } - - public PigxAuth2Exception(String msg, String errorCode) { - super(msg); - this.errorCode = errorCode; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ServerErrorException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ServerErrorException.java deleted file mode 100644 index ba3d91a43..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/ServerErrorException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import org.springframework.http.HttpStatus; - -/** - * @author lengleng - * @date 2018/7/8 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class ServerErrorException extends PigxAuth2Exception { - - public ServerErrorException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "server_error"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.INTERNAL_SERVER_ERROR.value(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/TokenInvalidException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/TokenInvalidException.java deleted file mode 100644 index 57b3d2fcf..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/TokenInvalidException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import org.springframework.http.HttpStatus; - -/** - * @author lengleng - * @date 2021-08-05 - *

- * 令牌不合法 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class TokenInvalidException extends PigxAuth2Exception { - - public TokenInvalidException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "invalid_token"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.FAILED_DEPENDENCY.value(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/UnauthorizedException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/UnauthorizedException.java deleted file mode 100644 index 35e0191de..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/exception/UnauthorizedException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.pigx.common.security.exception; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.pig4cloud.pigx.common.security.component.PigxAuth2ExceptionSerializer; -import org.springframework.http.HttpStatus; - -/** - * @author lengleng - * @date 2018/7/8 - */ -@JsonSerialize(using = PigxAuth2ExceptionSerializer.class) -public class UnauthorizedException extends PigxAuth2Exception { - - public UnauthorizedException(String msg, Throwable t) { - super(msg); - } - - @Override - public String getOAuth2ErrorCode() { - return "unauthorized"; - } - - @Override - public int getHttpErrorCode() { - return HttpStatus.UNAUTHORIZED.value(); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigFeignClientConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigFeignClientConfiguration.java new file mode 100755 index 000000000..191d53581 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigFeignClientConfiguration.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * 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.pig4cloud.pigx.common.security.feign; + +import feign.RequestInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; + +public class PigFeignClientConfiguration { + + /** + * 注入 oauth2 feign token 增强 + * @param tokenResolver token获取处理器 + * @return 拦截器 + */ + @Bean + public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) { + return new PigOAuthRequestInterceptor(tokenResolver); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigOAuthRequestInterceptor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigOAuthRequestInterceptor.java new file mode 100644 index 000000000..4ac2eb16e --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/feign/PigOAuthRequestInterceptor.java @@ -0,0 +1,63 @@ +package com.pig4cloud.pigx.common.security.feign; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.WebUtils; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; + +/** + * oauth2 feign token传递 + * + * 重新 OAuth2FeignRequestInterceptor ,官方实现部分常见不适用 + * + * @author lengleng + * @date 2022/5/29 + */ +@Slf4j +@RequiredArgsConstructor +public class PigOAuthRequestInterceptor implements RequestInterceptor { + + private final BearerTokenResolver tokenResolver; + + /** + * Create a template with the header of provided name and extracted extract
+ * + * 1. 如果使用 非web 请求,header 区别
+ * + * 2. 根据authentication 还原请求token + * @param template + */ + @Override + public void apply(RequestTemplate template) { + Collection fromHeader = template.headers().get(SecurityConstants.FROM); + // 带from 请求直接跳过 + if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) { + return; + } + + // 非web 请求直接跳过 + if (WebUtils.getRequest() == null) { + return; + } + HttpServletRequest request = WebUtils.getRequest(); + // 避免请求参数的 query token 无法传递 + String token = tokenResolver.resolve(request); + if (StrUtil.isBlank(token)) { + return; + } + template.header(HttpHeaders.AUTHORIZATION, + String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), token)); + + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationProvider.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationProvider.java deleted file mode 100644 index d1d732f79..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.pig4cloud.pigx.common.security.grant; - -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.Ordered; -import org.springframework.security.authentication.AccountStatusUserDetailsChecker; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.InternalAuthenticationServiceException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsChecker; - -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; - -/** - * @author hzq - * @since 2021-09-14 - */ -@Slf4j -public class CustomAppAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { - - /** - * user 属性校验 - */ - @Setter - private UserDetailsChecker preAuthenticationChecks = new AccountStatusUserDetailsChecker(); - - /** - * 校验 请求信息userDetails - * @param userDetails 用户信息 - * @param authentication 认证信息 - * @throws AuthenticationException - */ - @Override - protected void additionalAuthenticationChecks(UserDetails userDetails, - UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { - - if (authentication.getCredentials() == null) { - this.logger.debug("Failed to authenticate since no credentials provided"); - throw new BadCredentialsException(this.messages - .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); - } - - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - - if (authentication.getCredentials() == null) { - log.debug("Failed to authenticate since no credentials provided"); - throw new BadCredentialsException("Bad credentials"); - } - - CustomAppAuthenticationToken requestToken = (CustomAppAuthenticationToken) authentication; - // 此处已获得 客户端认证 获取对应 userDetailsService - Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication(); - String clientId = clientAuthentication.getName(); - - Map userDetailsServiceMap = SpringContextHolder - .getBeansOfType(PigxUserDetailsService.class); - Optional optional = userDetailsServiceMap.values().stream() - .filter(service -> service.support(clientId, requestToken.getGrantType())) - .max(Comparator.comparingInt(Ordered::getOrder)); - - if (!optional.isPresent()) { - throw new InternalAuthenticationServiceException("UserDetailsService error , not register"); - } - - // 手机号 - String phone = authentication.getName(); - UserDetails userDetails = optional.get().loadUserByUsername(phone); - - // userDeails 校验 - preAuthenticationChecks.check(userDetails); - - CustomAppAuthenticationToken token = new CustomAppAuthenticationToken(userDetails); - token.setDetails(authentication.getDetails()); - return token; - } - - @Override - protected UserDetails retrieveUser(String phone, UsernamePasswordAuthenticationToken authentication) - throws AuthenticationException { - return null; - } - - @Override - public boolean supports(Class authentication) { - return authentication.isAssignableFrom(CustomAppAuthenticationToken.class); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationToken.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationToken.java deleted file mode 100644 index 365110ae7..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/CustomAppAuthenticationToken.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.pig4cloud.pigx.common.security.grant; - -import lombok.Getter; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.UserDetails; - -/** - * @author hzq - * @since 2021-09-14 - */ -public class CustomAppAuthenticationToken extends AbstractAuthenticationToken { - - private final Object principal; - - /** - * 验证码/密码 - */ - private String code; - - /** - * 授权类型 - */ - @Getter - private String grantType; - - public CustomAppAuthenticationToken(String phone, String code, String grantType) { - super(AuthorityUtils.NO_AUTHORITIES); - this.principal = phone; - this.code = code; - this.grantType = grantType; - } - - public CustomAppAuthenticationToken(UserDetails sysUser) { - super(sysUser.getAuthorities()); - this.principal = sysUser; - super.setAuthenticated(true); // 设置认证成功 必须 - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public Object getCredentials() { - return this.code; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/ResourceOwnerCustomeAppTokenGranter.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/ResourceOwnerCustomeAppTokenGranter.java deleted file mode 100644 index c6d96167b..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/grant/ResourceOwnerCustomeAppTokenGranter.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.pig4cloud.pigx.common.security.grant; - -import cn.hutool.core.util.StrUtil; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AccountStatusException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.provider.*; -import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * 资源所有者电话令牌授予者 - * - * @author hzq - * @since 2021-09-14 - */ -public class ResourceOwnerCustomeAppTokenGranter extends AbstractTokenGranter { - - private static final String GRANT_TYPE = "mobile"; - - private final AuthenticationManager authenticationManager; - - public ResourceOwnerCustomeAppTokenGranter(AuthenticationManager authenticationManager, - AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, - OAuth2RequestFactory requestFactory) { - this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); - } - - protected ResourceOwnerCustomeAppTokenGranter(AuthenticationManager authenticationManager, - AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, - OAuth2RequestFactory requestFactory, String grantType) { - super(tokenServices, clientDetailsService, requestFactory, grantType); - this.authenticationManager = authenticationManager; - } - - @Override - protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { - - Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); - - // 手机号 - String mobile = parameters.get("mobile"); - // 验证码/密码 - String code = parameters.get("code"); - - if (StrUtil.isBlank(mobile) || StrUtil.isBlank(code)) { - throw new InvalidGrantException("Bad credentials [ params must be has phone with code ]"); - } - - // Protect from downstream leaks of code - parameters.remove("code"); - - Authentication userAuth = new CustomAppAuthenticationToken(mobile, code, tokenRequest.getGrantType()); - ((AbstractAuthenticationToken) userAuth).setDetails(parameters); - try { - userAuth = authenticationManager.authenticate(userAuth); - } - catch (AccountStatusException | BadCredentialsException ase) { - // covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) - throw new InvalidGrantException(ase.getMessage()); - } - // If the phone/code are wrong the spec says we should send 400/invalid grant - - if (userAuth == null || !userAuth.isAuthenticated()) { - throw new InvalidGrantException("Could not authenticate user: " + mobile); - } - - OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); - return new OAuth2Authentication(storedOAuth2Request, userAuth); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/MobileLoginSuccessHandler.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/MobileLoginSuccessHandler.java deleted file mode 100644 index 22611611c..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/MobileLoginSuccessHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.pigx.common.security.handler; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.CharsetUtil; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.pig4cloud.pigx.common.security.service.PigxCustomTokenServices; -import com.pig4cloud.pigx.common.security.util.AuthUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException; -import org.springframework.security.oauth2.provider.*; -import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - -/** - * @author lengleng - * @date 2018/8/5 手机号登录成功,返回oauth token - */ -@Slf4j -public class MobileLoginSuccessHandler implements AuthenticationSuccessHandler { - - private static final String BASIC_ = "Basic "; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Autowired - private ClientDetailsService clientDetailsService; - - @Lazy - @Autowired - private PigxCustomTokenServices tokenServices; - - /** - * Called when a user has been successfully authenticated. 调用spring security oauth API - * 生成 oAuth2AccessToken - * @param request the request which caused the successful authentication - * @param response the response - * @param authentication the Authentication object which was created during - */ - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - String header = request.getHeader(HttpHeaders.AUTHORIZATION); - - if (header == null || !header.startsWith(BASIC_)) { - throw new UnapprovedClientAuthenticationException("请求头中client信息为空"); - } - - try { - String[] tokens = AuthUtils.extractAndDecodeHeader(header); - assert tokens.length == 2; - String clientId = tokens[0]; - - ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); - - // 校验secret - if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret())) { - throw new InvalidClientException("Given client ID does not match authenticated client"); - - } - - TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, clientDetails.getScope(), - "mobile"); - - // 校验scope - new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails); - OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); - OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication); - OAuth2AccessToken oAuth2AccessToken = tokenServices.createAccessToken(oAuth2Authentication); - log.info("获取token 成功:{}", oAuth2AccessToken.getValue()); - - response.setCharacterEncoding(CharsetUtil.UTF_8); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - PrintWriter printWriter = response.getWriter(); - printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken)); - } - catch (IOException e) { - throw new BadCredentialsException("Failed to decode basic authentication token"); - } - - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/RestResponseErrorHandler.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/RestResponseErrorHandler.java deleted file mode 100644 index 284724640..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/RestResponseErrorHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.pig4cloud.pigx.common.security.handler; - -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.DefaultResponseErrorHandler; - -import java.io.IOException; - -/** - * @author lengleng - * @date 2020/4/29 - *

- * 重写默认的 响应失败处理器,400 不作为异常 - */ -public class RestResponseErrorHandler extends DefaultResponseErrorHandler { - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - if (response.getRawStatusCode() != HttpStatus.BAD_REQUEST.value()) { - super.handleError(response); - } - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/AccpetRequestInterceptor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/AccpetRequestInterceptor.java deleted file mode 100644 index ec60fab21..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/AccpetRequestInterceptor.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.pig4cloud.pigx.common.security.interceptor; - -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.stereotype.Component; - -import java.io.IOException; - -/** - * @author lengleng - * @date 2020/4/29 - *

- * 传递accpet 请求头,避免 请求返回 XML - */ -@Component -public class AccpetRequestInterceptor implements ClientHttpRequestInterceptor { - - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) - throws IOException { - request.getHeaders().set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); - return execution.execute(request, body); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignClientInterceptor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignClientInterceptor.java deleted file mode 100644 index 91b3b9869..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignClientInterceptor.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.pigx.common.security.interceptor; - -import cn.hutool.core.collection.CollUtil; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import feign.RequestTemplate; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cloud.commons.security.AccessTokenContextRelay; -import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; - -import java.util.Collection; - -/** - * @author lengleng - * @date 2018/8/13 扩展OAuth2FeignRequestInterceptor - */ -@Slf4j -public class PigxFeignClientInterceptor extends OAuth2FeignRequestInterceptor { - - private final OAuth2ClientContext oAuth2ClientContext; - - private final AccessTokenContextRelay accessTokenContextRelay; - - /** - * Default constructor which uses the provided OAuth2ClientContext and Bearer tokens - * within Authorization header - * @param oAuth2ClientContext provided context - * @param resource type of resource to be accessed - * @param accessTokenContextRelay - */ - public PigxFeignClientInterceptor(OAuth2ClientContext oAuth2ClientContext, OAuth2ProtectedResourceDetails resource, - AccessTokenContextRelay accessTokenContextRelay) { - super(oAuth2ClientContext, resource); - this.oAuth2ClientContext = oAuth2ClientContext; - this.accessTokenContextRelay = accessTokenContextRelay; - } - - /** - * Create a template with the header of provided name and extracted extract 1. 如果使用 - * 非web 请求,header 区别 2. 根据authentication 还原请求token - * @param template - */ - @Override - public void apply(RequestTemplate template) { - Collection fromHeader = template.headers().get(SecurityConstants.FROM); - if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) { - return; - } - - accessTokenContextRelay.copyToken(); - if (oAuth2ClientContext != null && oAuth2ClientContext.getAccessToken() != null) { - super.apply(template); - } - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignConfiguration.java deleted file mode 100644 index 984d5ed1f..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/interceptor/PigxFeignConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.pigx.common.security.interceptor; - -import feign.Feign; -import feign.RequestInterceptor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.commons.security.AccessTokenContextRelay; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; - -/** - * fegin 配置增强 - * - * @author L.cm - */ -@Configuration -@ConditionalOnClass(Feign.class) -public class PigxFeignConfiguration { - - @Bean - @ConditionalOnProperty("security.oauth2.client.client-id") - public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, - OAuth2ProtectedResourceDetails resource, AccessTokenContextRelay accessTokenContextRelay) { - return new PigxFeignClientInterceptor(oAuth2ClientContext, resource, accessTokenContextRelay); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationFailureEventListener.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationFailureEventListener.java deleted file mode 100644 index d3c21c409..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationFailureEventListener.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.pigx.common.security.listener; - -import cn.hutool.core.collection.CollUtil; -import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; -import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -/** - * @author lengleng - * @date 2020/03/25 认证失败事件监听器 - */ -public class AuthenticationFailureEventListener implements ApplicationListener { - - @Autowired(required = false) - private List failureHandlerList; - - /** - * Handle an application event. - * @param event the event to respond to - */ - @Override - public void onApplicationEvent(AbstractAuthenticationFailureEvent event) { - // 此类型事件不传递处理 #2386 - if (event instanceof AuthenticationFailureProviderNotFoundEvent) { - return; - } - - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); - HttpServletRequest request = requestAttributes.getRequest(); - HttpServletResponse response = requestAttributes.getResponse(); - - AuthenticationException authenticationException = event.getException(); - Authentication authentication = (Authentication) event.getSource(); - - // 调用自定义业务链实现 - if (CollUtil.isNotEmpty(failureHandlerList)) { - failureHandlerList.forEach(failureHandler -> failureHandler.handle(authenticationException, authentication, - request, response)); - } - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationLogoutSuccessEventListener.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationLogoutSuccessEventListener.java deleted file mode 100644 index ab43753d5..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationLogoutSuccessEventListener.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.pigx.common.security.listener; - -import cn.hutool.core.collection.CollUtil; -import com.pig4cloud.pigx.common.security.handler.AuthenticationLogoutHandler; -import com.pig4cloud.pigx.common.security.service.PigxUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.event.LogoutSuccessEvent; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * @author lengleng - * @date 2021/06/22 退出事件监听器 - */ -public class AuthenticationLogoutSuccessEventListener implements ApplicationListener { - - @Autowired(required = false) - private AuthenticationLogoutHandler logoutHandler; - - /** - * Handle an application event. - * @param event the event to respond to - */ - @Override - public void onApplicationEvent(LogoutSuccessEvent event) { - // 健壮性判断 - if (!(event.getSource() instanceof OAuth2Authentication)) { - return; - } - - OAuth2Authentication authentication = (OAuth2Authentication) event.getSource(); - if (logoutHandler != null && isUserAuthentication(authentication)) { - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); - HttpServletRequest request = requestAttributes.getRequest(); - HttpServletResponse response = requestAttributes.getResponse(); - - logoutHandler.handle(authentication, request, response); - } - } - - private boolean isUserAuthentication(Authentication authentication) { - return authentication.getPrincipal() instanceof PigxUser - || CollUtil.isNotEmpty(authentication.getAuthorities()); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationSuccessEventListener.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationSuccessEventListener.java deleted file mode 100644 index 4e949624c..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/listener/AuthenticationSuccessEventListener.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.pigx.common.security.listener; - -import cn.hutool.core.collection.CollUtil; -import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler; -import com.pig4cloud.pigx.common.security.service.PigxUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.core.Authentication; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.List; - -/** - * @author lengleng - * @date 2020/03/25 认证成功事件监听器 - */ -public class AuthenticationSuccessEventListener implements ApplicationListener { - - @Autowired(required = false) - private List successHandlerList; - - /** - * Handle an application event. - * @param event the event to respond to - */ - @Override - public void onApplicationEvent(AuthenticationSuccessEvent event) { - Authentication authentication = (Authentication) event.getSource(); - if (CollUtil.isNotEmpty(successHandlerList) && isUserAuthentication(authentication)) { - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder - .getRequestAttributes(); - HttpServletRequest request = requestAttributes.getRequest(); - HttpServletResponse response = requestAttributes.getResponse(); - successHandlerList.forEach(successHandler -> successHandler.handle(authentication, request, response)); - } - } - - private boolean isUserAuthentication(Authentication authentication) { - return authentication.getPrincipal() instanceof PigxUser - || CollUtil.isNotEmpty(authentication.getAuthorities()); - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationConsentService.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationConsentService.java new file mode 100644 index 000000000..d3bfdd7fa --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationConsentService.java @@ -0,0 +1,49 @@ +package com.pig4cloud.pigx.common.security.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.util.Assert; + +import java.util.concurrent.TimeUnit; + +@RequiredArgsConstructor +public class PigRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { + + private final RedisTemplate redisTemplate; + + private final static Long TIMEOUT = 10L; + + @Override + public void save(OAuth2AuthorizationConsent authorizationConsent) { + Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); + + redisTemplate.opsForValue().set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, + TimeUnit.MINUTES); + + } + + @Override + public void remove(OAuth2AuthorizationConsent authorizationConsent) { + Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); + redisTemplate.delete(buildKey(authorizationConsent)); + } + + @Override + public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { + Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); + Assert.hasText(principalName, "principalName cannot be empty"); + return (OAuth2AuthorizationConsent) redisTemplate.opsForValue() + .get(buildKey(registeredClientId, principalName)); + } + + private static String buildKey(String registeredClientId, String principalName) { + return "token:consent:" + registeredClientId + ":" + principalName; + } + + private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) { + return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationService.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationService.java new file mode 100644 index 000000000..ad7a8bb67 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRedisOAuth2AuthorizationService.java @@ -0,0 +1,140 @@ +package com.pig4cloud.pigx.common.security.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.lang.Nullable; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.util.Assert; + +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * @author lengleng + * @date 2022/5/27 + */ +@RequiredArgsConstructor +public class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationService { + + private final static Long TIMEOUT = 10L; + + private static final String AUTHORIZATION = "token"; + + private final RedisTemplate redisTemplate; + + @Override + public void save(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + + if (isState(authorization)) { + String token = authorization.getAttribute("state"); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, + TimeUnit.MINUTES); + } + + if (isCode(authorization)) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); + long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), + authorizationCodeToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), + authorization, between, TimeUnit.MINUTES); + } + + if (isRefreshToken(authorization)) { + OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); + long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), + authorization, between, TimeUnit.SECONDS); + } + + if (isAccessToken(authorization)) { + OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); + long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()); + redisTemplate.setValueSerializer(RedisSerializer.java()); + redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), + authorization, between, TimeUnit.SECONDS); + } + } + + @Override + public void remove(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + + List keys = new ArrayList<>(); + if (isState(authorization)) { + String token = authorization.getAttribute("state"); + keys.add(buildKey(OAuth2ParameterNames.STATE, token)); + } + + if (isCode(authorization)) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); + keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue())); + } + + if (isRefreshToken(authorization)) { + OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); + keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue())); + } + + if (isAccessToken(authorization)) { + OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); + keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue())); + } + redisTemplate.delete(keys); + } + + @Override + @Nullable + public OAuth2Authorization findById(String id) { + throw new UnsupportedOperationException(); + } + + @Override + @Nullable + public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { + Assert.hasText(token, "token cannot be empty"); + Assert.notNull(tokenType, "tokenType cannot be empty"); + redisTemplate.setValueSerializer(RedisSerializer.java()); + return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token)); + } + + private String buildKey(String type, String id) { + return String.format("%s::%s::%s", AUTHORIZATION, type, id); + } + + private static boolean isState(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getAttribute("state")); + } + + private static boolean isCode(OAuth2Authorization authorization) { + OAuth2Authorization.Token authorizationCode = authorization + .getToken(OAuth2AuthorizationCode.class); + return Objects.nonNull(authorizationCode); + } + + private static boolean isRefreshToken(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getRefreshToken()); + } + + private static boolean isAccessToken(OAuth2Authorization authorization) { + return Objects.nonNull(authorization.getAccessToken()); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRemoteRegisteredClientRepository.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRemoteRegisteredClientRepository.java new file mode 100644 index 000000000..b29ee99a7 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigRemoteRegisteredClientRepository.java @@ -0,0 +1,124 @@ +package com.pig4cloud.pigx.common.security.service; + +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; +import com.pig4cloud.pigx.admin.api.feign.RemoteClientDetailsService; +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.util.RetOps; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; +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.settings.ClientSettings; +import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Optional; + +/** + * 查询客户端相关信息实现 + * + * @author lengleng + * @date 2022/5/29 + */ +@RequiredArgsConstructor +public class PigRemoteRegisteredClientRepository implements RegisteredClientRepository { + + /** + * 刷新令牌有效期默认 30 天 + */ + private final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; + + /** + * 请求令牌有效期默认 12 小时 + */ + private final static int accessTokenValiditySeconds = 60 * 60 * 12; + + private final RemoteClientDetailsService clientDetailsService; + + /** + * Saves the registered client. + * + *

+ * IMPORTANT: Sensitive information should be encoded externally from the + * implementation, e.g. {@link RegisteredClient#getClientSecret()} + * @param registeredClient the {@link RegisteredClient} + */ + @Override + public void save(RegisteredClient registeredClient) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the registered client identified by the provided {@code id}, or + * {@code null} if not found. + * @param id the registration identifier + * @return the {@link RegisteredClient} if found, otherwise {@code null} + */ + @Override + public RegisteredClient findById(String id) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the registered client identified by the provided {@code clientId}, or + * {@code null} if not found. + * @param clientId the client identifier + * @return the {@link RegisteredClient} if found, otherwise {@code null} + */ + + /** + * 重写原生方法支持redis缓存 + * @param clientId + * @return + */ + @Override + @SneakyThrows + // @Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = + // "#result == null") + public RegisteredClient findByClientId(String clientId) { + + SysOauthClientDetails clientDetails = RetOps + .of(clientDetailsService.getClientDetailsById(clientId, SecurityConstants.FROM_IN)).getData() + .orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException( + new OAuth2Error("客户端查询异常,请检查数据库链接"), null)); + + RegisteredClient.Builder builder = RegisteredClient.withId(clientDetails.getClientId()) + .clientId(clientDetails.getClientId()) + .clientSecret(SecurityConstants.NOOP + clientDetails.getClientSecret()) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); + + // 授权模式 + Arrays.stream(clientDetails.getAuthorizedGrantTypes()) + .forEach(grant -> builder.authorizationGrantType(new AuthorizationGrantType(grant))); + + // 回调地址 + Optional.ofNullable(clientDetails.getWebServerRedirectUri()).ifPresent(redirectUri -> Arrays + .stream(redirectUri.split(StrUtil.COMMA)).filter(StrUtil::isNotBlank).forEach(builder::redirectUri)); + + // scope + Optional.ofNullable(clientDetails.getScope()).ifPresent( + scope -> Arrays.stream(scope.split(StrUtil.COMMA)).filter(StrUtil::isNotBlank).forEach(builder::scope)); + + return builder + .tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE) + .accessTokenTimeToLive(Duration.ofSeconds(Optional + .ofNullable(clientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds))) + .refreshTokenTimeToLive( + Duration.ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity()) + .orElse(refreshTokenValiditySeconds))) + .build()) + .clientSettings(ClientSettings.builder() + .requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoapprove())).build()) + .build(); + + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxClientDetailsServiceImpl.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxClientDetailsServiceImpl.java deleted file mode 100644 index fb704b7e4..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxClientDetailsServiceImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.pig4cloud.pigx.common.security.service; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; -import com.pig4cloud.pigx.admin.api.feign.RemoteClientDetailsService; -import com.pig4cloud.pigx.common.core.constant.CacheConstants; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.util.RetOps; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Primary; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.NoSuchClientException; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.util.StringUtils; - -import java.util.Map; - -/** - * @author lengleng - * @date 2020/03/25 - *

- * 扩展 JdbcClientDetailsService 支持多租户 - */ -@Slf4j -@Primary -@RequiredArgsConstructor -public class PigxClientDetailsServiceImpl implements ClientDetailsService { - - private final RemoteClientDetailsService clientDetailsService; - - /** - * 重写原生方法支持redis缓存 - * @param clientId - * @return ClientDetails - * @throws InvalidClientException - */ - @Override - @Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null") - public ClientDetails loadClientByClientId(String clientId) { - // 适配成oauth2内置类型 - return RetOps.of(clientDetailsService.getClientDetailsById(clientId, SecurityConstants.FROM_IN)).getData() - .map(this::clientDetailsWrapper) - .orElseThrow(() -> new NoSuchClientException("No client with requested id: " + clientId)); - } - - /** - * 客户端类型转化 参考 - * {@link org.springframework.security.oauth2.provider.client.JdbcClientDetailsService} - * @param origin 数据库保存参数 - * @return target oauth 类型客户端参数 - */ - private ClientDetails clientDetailsWrapper(SysOauthClientDetails origin) { - BaseClientDetails target = new BaseClientDetails(); - // 必选项 - target.setClientId(origin.getClientId()); - target.setClientSecret(String.format("{noop}%s", origin.getClientSecret())); - - if (ArrayUtil.isNotEmpty(origin.getAuthorizedGrantTypes())) { - target.setAuthorizedGrantTypes(CollUtil.newArrayList(origin.getAuthorizedGrantTypes())); - } - - if (StrUtil.isNotBlank(origin.getAuthorities())) { - target.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(origin.getAuthorities())); - } - - if (StrUtil.isNotBlank(origin.getResourceIds())) { - target.setResourceIds(StringUtils.commaDelimitedListToSet(origin.getResourceIds())); - } - - if (StrUtil.isNotBlank(origin.getWebServerRedirectUri())) { - target.setRegisteredRedirectUri(StringUtils.commaDelimitedListToSet(origin.getWebServerRedirectUri())); - } - - if (StrUtil.isNotBlank(origin.getScope())) { - target.setScope(StringUtils.commaDelimitedListToSet(origin.getScope())); - } - - if (StrUtil.isNotBlank(origin.getAutoapprove())) { - target.setAutoApproveScopes(StringUtils.commaDelimitedListToSet(origin.getAutoapprove())); - } - - if (origin.getAccessTokenValidity() != null) { - target.setAccessTokenValiditySeconds(origin.getAccessTokenValidity()); - } - - if (origin.getRefreshTokenValidity() != null) { - target.setRefreshTokenValiditySeconds(origin.getRefreshTokenValidity()); - } - - String json = origin.getAdditionalInformation(); - if (StrUtil.isNotBlank(json)) { - try { - @SuppressWarnings("unchecked") - Map additionalInformation = JSONUtil.toBean(json, Map.class); - target.setAdditionalInformation(additionalInformation); - } - catch (Exception e) { - log.warn("Could not decode JSON for additional information: " + json, e); - } - } - - return target; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java deleted file mode 100644 index 56a243f85..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxCustomTokenServices.java +++ /dev/null @@ -1,391 +0,0 @@ -package com.pig4cloud.pigx.common.security.service; - -import cn.hutool.core.map.MapUtil; -import com.pig4cloud.pigx.common.core.constant.CommonConstants; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.*; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; -import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import org.springframework.security.oauth2.provider.*; -import org.springframework.security.oauth2.provider.token.*; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Assert; - -import java.util.*; - -/** - * 自定义 token 放发处理逻辑 - * - * @author lengleng - * @date 2021/10/15 - */ -@Slf4j -public class PigxCustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, - ConsumerTokenServices, InitializingBean { - - private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days. - - private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours. - - private int defaultOnlineQuantity = 0; // 客户端默认同时在线数量 - - private boolean supportRefreshToken = false; - - private boolean reuseRefreshToken = true; - - private TokenStore tokenStore; - - private ClientDetailsService clientDetailsService; - - private TokenEnhancer accessTokenEnhancer; - - /** - * Initialize these token services. If no random generator is set, one will be - * created. - */ - public void afterPropertiesSet() { - Assert.notNull(tokenStore, "tokenStore must be set"); - } - - @Override - public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { - OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); - OAuth2RefreshToken refreshToken = null; - // 若已产生token , 过期时删除相关token,执行下边的重新生成逻辑 - if (existingAccessToken != null) { - int onlineQuantity = getOnlineQuantity(authentication.getOAuth2Request()); - // (0)默认情况下支持同时在线 - if (onlineQuantity == defaultOnlineQuantity) { - if (existingAccessToken.isExpired()) { - if (existingAccessToken.getRefreshToken() != null) { - refreshToken = existingAccessToken.getRefreshToken(); - tokenStore.removeRefreshToken(refreshToken); - } - tokenStore.removeAccessToken(existingAccessToken); - } - else { - // Re-store the access token in case the authentication has changed - tokenStore.storeAccessToken(existingAccessToken, authentication); - return existingAccessToken; - } - } - - // 增加允许同时在线数量逻辑 - Collection currentTokenList = tokenStore.findTokensByClientIdAndUserName( - authentication.getOAuth2Request().getClientId(), authentication.getName()); - if (currentTokenList.size() >= getOnlineQuantity(authentication.getOAuth2Request())) { - Optional oldestTokenOpt = currentTokenList.stream() - .min(Comparator.comparing(OAuth2AccessToken::getExpiration)); - if (oldestTokenOpt.isPresent()) { - OAuth2AccessToken oldestToken = oldestTokenOpt.get(); - log.info("online quantity reaches the upper limit"); - tokenStore.removeAccessToken(oldestToken); - if (oldestToken.getRefreshToken() != null) { - refreshToken = oldestToken.getRefreshToken(); - tokenStore.removeRefreshToken(refreshToken); - } - } - } - } - - refreshToken = createRefreshToken(authentication); - OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); - tokenStore.storeAccessToken(accessToken, authentication); - - refreshToken = accessToken.getRefreshToken(); - if (refreshToken != null) { - tokenStore.storeRefreshToken(refreshToken, authentication); - } - return accessToken; - } - - @Transactional(noRollbackFor = { InvalidTokenException.class, InvalidGrantException.class }) - public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) - throws AuthenticationException { - - if (!supportRefreshToken) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); - } - - OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue); - if (refreshToken == null) { - throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue); - } - - OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken); - String clientId = authentication.getOAuth2Request().getClientId(); - if (clientId == null || !clientId.equals(tokenRequest.getClientId())) { - throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue); - } - - // clear out any access tokens already associated with the refresh - // token. - tokenStore.removeAccessTokenUsingRefreshToken(refreshToken); - - if (isExpired(refreshToken)) { - tokenStore.removeRefreshToken(refreshToken); - throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken); - } - - authentication = createRefreshedAuthentication(authentication, tokenRequest); - - if (!reuseRefreshToken) { - tokenStore.removeRefreshToken(refreshToken); - refreshToken = createRefreshToken(authentication); - } - - OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); - tokenStore.storeAccessToken(accessToken, authentication); - if (!reuseRefreshToken) { - tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication); - } - return accessToken; - } - - public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) { - return tokenStore.getAccessToken(authentication); - } - - /** - * Create a refreshed authentication. - * @param authentication The authentication. - * @param request The scope for the refreshed token. - * @return The refreshed authentication. - * @throws InvalidScopeException If the scope requested is invalid or wider than the - * original scope. - */ - private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, - TokenRequest request) { - OAuth2Authentication narrowed = authentication; - Set scope = request.getScope(); - OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request); - if (scope != null && !scope.isEmpty()) { - Set originalScope = clientAuth.getScope(); - if (originalScope == null || !originalScope.containsAll(scope)) { - throw new InvalidScopeException( - "Unable to narrow the scope of the client authentication to " + scope + ".", originalScope); - } - else { - clientAuth = clientAuth.narrowScope(scope); - } - } - narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication()); - return narrowed; - } - - protected boolean isExpired(OAuth2RefreshToken refreshToken) { - if (refreshToken instanceof ExpiringOAuth2RefreshToken) { - ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken; - return expiringToken.getExpiration() == null - || System.currentTimeMillis() > expiringToken.getExpiration().getTime(); - } - return false; - } - - public OAuth2AccessToken readAccessToken(String accessToken) { - return tokenStore.readAccessToken(accessToken); - } - - public OAuth2Authentication loadAuthentication(String accessTokenValue) - throws AuthenticationException, InvalidTokenException { - OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue); - if (accessToken == null) { - throw new InvalidTokenException("Invalid access token: " + accessTokenValue); - } - else if (accessToken.isExpired()) { - tokenStore.removeAccessToken(accessToken); - throw new InvalidTokenException("Access token expired: " + accessTokenValue); - } - - OAuth2Authentication result = tokenStore.readAuthentication(accessToken); - if (result == null) { - // in case of race condition - throw new InvalidTokenException("Invalid access token: " + accessTokenValue); - } - if (clientDetailsService != null) { - String clientId = result.getOAuth2Request().getClientId(); - try { - clientDetailsService.loadClientByClientId(clientId); - } - catch (ClientRegistrationException e) { - throw new InvalidTokenException("Client not valid: " + clientId, e); - } - } - return result; - } - - public String getClientId(String tokenValue) { - OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue); - if (authentication == null) { - throw new InvalidTokenException("Invalid access token: " + tokenValue); - } - OAuth2Request clientAuth = authentication.getOAuth2Request(); - if (clientAuth == null) { - throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue); - } - return clientAuth.getClientId(); - } - - public boolean revokeToken(String tokenValue) { - OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue); - if (accessToken == null) { - return false; - } - if (accessToken.getRefreshToken() != null) { - tokenStore.removeRefreshToken(accessToken.getRefreshToken()); - } - tokenStore.removeAccessToken(accessToken); - return true; - } - - private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) { - if (!isSupportRefreshToken(authentication.getOAuth2Request())) { - return null; - } - int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request()); - String value = UUID.randomUUID().toString(); - if (validitySeconds > 0) { - return new DefaultExpiringOAuth2RefreshToken(value, - new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); - } - return new DefaultOAuth2RefreshToken(value); - } - - private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { - DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); - int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); - if (validitySeconds > 0) { - token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); - } - token.setRefreshToken(refreshToken); - token.setScope(authentication.getOAuth2Request().getScope()); - - return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; - } - - /** - * The access token validity period in seconds - * @param clientAuth the current authorization request - * @return the access token validity period in seconds - */ - protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - Integer validity = client.getAccessTokenValiditySeconds(); - if (validity != null && validity > 0) { - return validity; - } - } - return accessTokenValiditySeconds; - } - - /** - * The refresh token validity period in seconds - * @param clientAuth the current authorization request - * @return the refresh token validity period in seconds - */ - protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - Integer validity = client.getRefreshTokenValiditySeconds(); - if (validity != null && validity > 0) { - return validity; - } - } - return refreshTokenValiditySeconds; - } - - /** - * the client online quantity - * @param clientAuth the current authorization request - * @return the client online quantity - */ - protected int getOnlineQuantity(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - return MapUtil.getInt(client.getAdditionalInformation(), CommonConstants.ONLINE_QUANTITY, -1); - } - return defaultOnlineQuantity; - } - - /** - * Is a refresh token supported for this client (or the global setting if - * {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not - * set. - * @param clientAuth the current authorization request - * @return boolean to indicate if refresh token is supported - */ - protected boolean isSupportRefreshToken(OAuth2Request clientAuth) { - if (clientDetailsService != null) { - ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); - return client.getAuthorizedGrantTypes().contains("refresh_token"); - } - return this.supportRefreshToken; - } - - /** - * An access token enhancer that will be applied to a new token before it is saved in - * the token store. - * @param accessTokenEnhancer the access token enhancer to set - */ - public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) { - this.accessTokenEnhancer = accessTokenEnhancer; - } - - /** - * The validity (in seconds) of the refresh token. If less than or equal to zero then - * the tokens will be non-expiring. - * @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token. - */ - public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) { - this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; - } - - /** - * The default validity (in seconds) of the access token. Zero or negative for - * non-expiring tokens. If a client details service is set the validity period will be - * read from the client, defaulting to this value if not defined by the client. - * @param accessTokenValiditySeconds The validity (in seconds) of the access token. - */ - public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) { - this.accessTokenValiditySeconds = accessTokenValiditySeconds; - } - - /** - * Whether to support the refresh token. - * @param supportRefreshToken Whether to support the refresh token. - */ - public void setSupportRefreshToken(boolean supportRefreshToken) { - this.supportRefreshToken = supportRefreshToken; - } - - /** - * Whether to reuse refresh tokens (until expired). - * @param reuseRefreshToken Whether to reuse refresh tokens (until expired). - */ - public void setReuseRefreshToken(boolean reuseRefreshToken) { - this.reuseRefreshToken = reuseRefreshToken; - } - - /** - * The persistence strategy for token storage. - * @param tokenStore the store for access and refresh tokens. - */ - public void setTokenStore(TokenStore tokenStore) { - this.tokenStore = tokenStore; - } - - /** - * The client details service to use for looking up clients (if necessary). Optional - * if the access token expiry is set globally via - * {@link #setAccessTokenValiditySeconds(int)}. - * @param clientDetailsService the client details service - */ - public void setClientDetailsService(ClientDetailsService clientDetailsService) { - this.clientDetailsService = clientDetailsService; - } - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxAppUserDetailsServiceImpl.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java similarity index 93% rename from pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxAppUserDetailsServiceImpl.java rename to pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java index 58c60ae1f..f240cec9d 100755 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxAppUserDetailsServiceImpl.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocDefaultUserDetailsServiceImpl.java @@ -16,10 +16,7 @@ package com.pig4cloud.pigx.common.security.service; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ArrayUtil; -import com.pig4cloud.pigx.admin.api.dto.UserInfo; -import com.pig4cloud.pigx.admin.api.entity.SysUser; import com.pig4cloud.pigx.app.api.dto.AppUserInfo; import com.pig4cloud.pigx.app.api.entity.AppUser; import com.pig4cloud.pigx.app.api.feign.RemoteAppUserService; @@ -29,6 +26,7 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.core.constant.enums.UserTypeEnum; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.core.util.RetOps; +import com.pig4cloud.pigx.common.core.util.WebUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -51,7 +49,7 @@ import java.util.Set; */ @Slf4j @RequiredArgsConstructor -public class PigxAppUserDetailsServiceImpl implements PigxUserDetailsService { +public class PigxTocDefaultUserDetailsServiceImpl implements PigxUserDetailsService { private final CacheManager cacheManager; @@ -129,7 +127,8 @@ public class PigxAppUserDetailsServiceImpl implements PigxUserDetailsService { */ @Override public boolean support(String clientId, String grantType) { - return SecurityConstants.CLIENT_MINI.equals(clientId); + String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC); + return SecurityConstants.HEADER_TOC_YES.equals(header); } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxMobildAppUserDetailsServiceImpl.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java similarity index 93% rename from pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxMobildAppUserDetailsServiceImpl.java rename to pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java index f767e13ca..cab359b60 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxMobildAppUserDetailsServiceImpl.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxTocMobileUserDetailsServiceImpl.java @@ -26,6 +26,7 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.core.constant.enums.UserTypeEnum; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.core.util.RetOps; +import com.pig4cloud.pigx.common.core.util.WebUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -34,7 +35,6 @@ import org.springframework.cache.CacheManager; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.Arrays; @@ -49,9 +49,7 @@ import java.util.Set; */ @Slf4j @RequiredArgsConstructor -public class PigxMobildAppUserDetailsServiceImpl implements PigxUserDetailsService { - - private final UserDetailsService pigxAppUserDetailsServiceImpl; +public class PigxTocMobileUserDetailsServiceImpl implements PigxUserDetailsService { private final CacheManager cacheManager; @@ -126,7 +124,8 @@ public class PigxMobildAppUserDetailsServiceImpl implements PigxUserDetailsServi */ @Override public boolean support(String clientId, String grantType) { - return SecurityConstants.CLIENT_MINI.equals(clientId) && SecurityConstants.GRANT_MOBILE.equals(grantType); + String header = WebUtils.getRequest().getHeader(SecurityConstants.HEADER_TOC); + return SecurityConstants.HEADER_TOC_YES.equals(header) && SecurityConstants.GRANT_MOBILE.equals(grantType); } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java index 056f5c43d..91fd35be4 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/service/PigxUser.java @@ -24,14 +24,17 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.userdetails.User; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; /** * @author lengleng * @date 2020/4/16 扩展用户信息 */ -public class PigxUser extends User { +public class PigxUser extends User implements OAuth2AuthenticatedPrincipal { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; @@ -131,4 +134,9 @@ public class PigxUser extends User { this.userType = userType; } + @Override + public Map getAttributes() { + return new HashMap<>(); + } + } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/QqSocialConfig.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/QqSocialConfig.java deleted file mode 100644 index 4685a9ae9..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/QqSocialConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.pigx.common.security.social; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * @author lengleng - * @date 2018/8/16 qq登录配置信息 - */ -@Data -@Configuration -@ConfigurationProperties(prefix = "social.qq") -public class QqSocialConfig { - - private String appid; - - private String secret; - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/WxSocialConfig.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/WxSocialConfig.java deleted file mode 100644 index 6df159978..000000000 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/social/WxSocialConfig.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.pigx.common.security.social; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * @author lengleng - * @date 2018/8/16 微信登录配置 - */ -@Data -@Configuration -@ConfigurationProperties(prefix = "social.wx") -public class WxSocialConfig { - - private String appid; - - private String secret; - -} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2EndpointUtils.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2EndpointUtils.java new file mode 100644 index 000000000..a9531c98c --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2EndpointUtils.java @@ -0,0 +1,80 @@ +package com.pig4cloud.pigx.common.security.util; + +import cn.hutool.core.map.MapUtil; +import lombok.experimental.UtilityClass; +import org.springframework.security.oauth2.core.*; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.time.temporal.ChronoUnit; +import java.util.Map; + +/** + * @author jumuning + * @description OAuth2 端点工具 + */ +@UtilityClass +public class OAuth2EndpointUtils { + + public final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; + + public MultiValueMap getParameters(HttpServletRequest request) { + Map parameterMap = request.getParameterMap(); + MultiValueMap parameters = new LinkedMultiValueMap<>(parameterMap.size()); + parameterMap.forEach((key, values) -> { + if (values.length > 0) { + for (String value : values) { + parameters.add(key, value); + } + } + }); + return parameters; + } + + public boolean matchesPkceTokenRequest(HttpServletRequest request) { + return AuthorizationGrantType.AUTHORIZATION_CODE.getValue() + .equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) + && request.getParameter(OAuth2ParameterNames.CODE) != null + && request.getParameter(PkceParameterNames.CODE_VERIFIER) != null; + } + + public void throwError(String errorCode, String parameterName, String errorUri) { + OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); + throw new OAuth2AuthenticationException(error); + } + + /** + * 格式化输出token 信息 + * @param authentication 用户认证信息 + * @param claims 扩展信息 + * @return + * @throws IOException + */ + public OAuth2AccessTokenResponse sendAccessTokenResponse(OAuth2Authorization authentication, + Map claims) { + + OAuth2AccessToken accessToken = authentication.getAccessToken().getToken(); + OAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken(); + + OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) + .tokenType(accessToken.getTokenType()).scopes(accessToken.getScopes()); + if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) { + builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt())); + } + if (refreshToken != null) { + builder.refreshToken(refreshToken.getTokenValue()); + } + + if (MapUtil.isNotEmpty(claims)) { + builder.additionalParameters(claims); + } + return builder.build(); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2ErrorCodesExpand.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2ErrorCodesExpand.java new file mode 100644 index 000000000..6f1dc34a4 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuth2ErrorCodesExpand.java @@ -0,0 +1,43 @@ +package com.pig4cloud.pigx.common.security.util; + +/** + * @author jumuning + * @description OAuth2 异常信息 + */ +public interface OAuth2ErrorCodesExpand { + + /** 用户名未找到 */ + String USERNAME_NOT_FOUND = "username_not_found"; + + /** 错误凭证 */ + String BAD_CREDENTIALS = "bad_credentials"; + + /** 用户被锁 */ + String USER_LOCKED = "user_locked"; + + /** 用户禁用 */ + String USER_DISABLE = "user_disable"; + + /** 用户过期 */ + String USER_EXPIRED = "user_expired"; + + /** 证书过期 */ + String CREDENTIALS_EXPIRED = "credentials_expired"; + + /** scope 为空异常 */ + String SCOPE_IS_EMPTY = "scope_is_empty"; + + /** + * 令牌不存在 + */ + String TOKEN_MISSING = "token_missing"; + + /** 未知的登录异常 */ + String UN_KNOW_LOGIN_ERROR = "un_know_login_error"; + + /** + * 不合法的Token + */ + String INVALID_BEARER_TOKEN = "invalid_bearer_token"; + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuthClientException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuthClientException.java new file mode 100644 index 000000000..2252012c6 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/OAuthClientException.java @@ -0,0 +1,29 @@ +package com.pig4cloud.pigx.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 ScopeException 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); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/ScopeException.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/ScopeException.java new file mode 100644 index 000000000..af2920189 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/ScopeException.java @@ -0,0 +1,29 @@ +package com.pig4cloud.pigx.common.security.util; + +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; + +/** + * @author jumuning + * @description ScopeException 异常信息 + */ +public class ScopeException extends OAuth2AuthenticationException { + + /** + * Constructs a ScopeException with the specified message. + * @param msg the detail message. + */ + public ScopeException(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 ScopeException(String msg, Throwable cause) { + super(new OAuth2Error(msg), cause); + } + +} diff --git a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 9b1c810c1..000000000 --- a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,15 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.pig4cloud.pigx.common.security.component.PigxTokenEnhancer,\ - com.pig4cloud.pigx.common.security.component.PigxSecurityInnerAspect,\ - com.pig4cloud.pigx.common.security.service.PigxDefaultUserDetailsServiceImpl,\ - com.pig4cloud.pigx.common.security.service.PigxAppUserDetailsServiceImpl,\ - com.pig4cloud.pigx.common.security.service.PigxMobileUserDetailServiceImpl,\ - com.pig4cloud.pigx.common.security.service.PigxMobildAppUserDetailsServiceImpl,\ - com.pig4cloud.pigx.common.security.service.PigxClientDetailsServiceImpl,\ - com.pig4cloud.pigx.common.security.component.PigxSecurityCommonAutoConfiguration,\ - com.pig4cloud.pigx.common.security.component.PigxSecurityMessageSourceConfiguration,\ - com.pig4cloud.pigx.common.security.component.PigxTokenStoreCleanSchedule,\ - com.pig4cloud.pigx.common.security.listener.AuthenticationFailureEventListener,\ - com.pig4cloud.pigx.common.security.listener.AuthenticationSuccessEventListener,\ - com.pig4cloud.pigx.common.security.listener.AuthenticationLogoutSuccessEventListener - diff --git a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..8f949c55b --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,9 @@ +com.pig4cloud.pigx.common.security.service.PigxDefaultUserDetailsServiceImpl +com.pig4cloud.pigx.common.security.service.PigxTocDefaultUserDetailsServiceImpl +com.pig4cloud.pigx.common.security.service.PigxMobileUserDetailServiceImpl +com.pig4cloud.pigx.common.security.service.PigxTocMobileUserDetailsServiceImpl +com.pig4cloud.pigx.common.security.service.PigRedisOAuth2AuthorizationService +com.pig4cloud.pigx.common.security.service.PigRedisOAuth2AuthorizationConsentService +com.pig4cloud.pigx.common.security.component.PigSecurityInnerAspect +com.pig4cloud.pigx.common.security.component.PigSecurityMessageSourceConfiguration +com.pig4cloud.pigx.common.security.service.PigRemoteRegisteredClientRepository diff --git a/pigx-common/pigx-common-security/src/main/resources/errors/messages_zh_CN.properties b/pigx-common/pigx-common-security/src/main/resources/errors/messages_zh_CN.properties index 7a24272b0..5d459f852 100644 --- a/pigx-common/pigx-common-security/src/main/resources/errors/messages_zh_CN.properties +++ b/pigx-common/pigx-common-security/src/main/resources/errors/messages_zh_CN.properties @@ -22,8 +22,10 @@ AbstractLdapAuthenticationProvider.emptyPassword=\u5BC6\u7801\u4E0D\u80FD\u4E3A\ AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61 AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u4E0D\u5B58\u5728\u6216\u8005\u5BC6\u7801\u9519\u8BEF AbstractUserDetailsAuthenticationProvider.badClientCredentials=\u5BA2\u6237\u7AEF\u4FE1\u606F\u9519\u8BEF\uFF0CBasic\u8BA4\u8BC1\u5931\u8D25 -AbstractUserDetailsAuthenticationProvider.noopBindAccount=\u672A\u7ED1\u5B9A\u767B\u5F55\u8D26\u53F7\uFF0C\u8BF7\u4F7F\u7528\u5BC6\u7801\u767B\u5F55\u540E\u7ED1\u5B9A +AbstractUserDetailsAuthenticationProvider.smsBadCredentials=\u7528\u6237\u4E0D\u5B58\u5728\uFF0C\u767B\u5F55\u5931\u8D25 +AbstractUserDetailsAuthenticationProvider.noopBindAccount=\u672A\u7ED1\u5B9A\u767B\u5F55\u8D26\u53F7 AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F +OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u672A\u6FC0\u6D3B AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F AbstractUserDetailsAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A diff --git a/pigx-common/pigx-common-test/pom.xml b/pigx-common/pigx-common-test/pom.xml deleted file mode 100755 index 5fdc66366..000000000 --- a/pigx-common/pigx-common-test/pom.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - 4.0.0 - - com.pig4cloud - pigx-common - 4.5.0 - - - pigx-common-test - jar - - pigx oauth 2.0 单元测试工具类 - - - - com.pig4cloud - pigx-common-data - - - com.pig4cloud - pigx-common-security - - - org.springframework.security - spring-security-test - - - diff --git a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/WithMockSecurityContextFactory.java b/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/WithMockSecurityContextFactory.java deleted file mode 100644 index 7849584fc..000000000 --- a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/WithMockSecurityContextFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.pig4cloud.pigx.test; - -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; -import com.pig4cloud.pigx.test.annotation.WithMockOAuth2User; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.security.test.context.support.WithSecurityContextFactory; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.nio.charset.StandardCharsets; - -/** - * @author lengleng - * @date 2020/9/22 - *

- * oauth2 上下文生成处理器 - */ -public class WithMockSecurityContextFactory implements WithSecurityContextFactory { - - private final String TOKEN_URL = "http://pigx-auth/oauth/token"; - - @Override - public SecurityContext createSecurityContext(WithMockOAuth2User oAuth2User) { - // 0. 初始化环境 - TenantContextHolder.setTenantId(oAuth2User.tenant()); - - // 1. 请求认证中心获取token - String token = getToken(oAuth2User); - - // 2. 解析认证中心返回用户 - OAuth2Authentication authentication = getUser(token); - - // 3. 构建 oauth2 上下文 - SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(authentication); - - // 4. 上下文保存 token - DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(token); - OAuth2ClientContext clientContext = SpringContextHolder.getBean(OAuth2ClientContext.class); - clientContext.setAccessToken(accessToken); - return context; - } - - /** - * 请求认证中心获取token - * @param oAuth2User 账号、密码 - * @return String token - */ - private String getToken(final WithMockOAuth2User oAuth2User) { - OAuth2ProtectedResourceDetails clientProperties = SpringContextHolder - .getBean(OAuth2ProtectedResourceDetails.class); - - HttpHeaders requestHeaders = new HttpHeaders(); - requestHeaders.set(HttpHeaders.AUTHORIZATION, HttpUtil.buildBasicAuth(clientProperties.getClientId(), - clientProperties.getClientSecret(), StandardCharsets.UTF_8)); - - MultiValueMap requestBody = new LinkedMultiValueMap<>(); - requestBody.add("username", oAuth2User.username()); - requestBody.add("password", oAuth2User.password()); - requestBody.add("grant_type", "password"); - requestBody.put("scope", clientProperties.getScope()); - - HttpEntity> requestEntity = new HttpEntity<>(requestBody, requestHeaders); - - RestTemplate restTemplate = SpringContextHolder.getBean(RestTemplate.class); - - // 优先获取配置文件URI 配置 - String result; - if (StrUtil.isBlank(clientProperties.getAccessTokenUri())) { - result = restTemplate.exchange(TOKEN_URL, HttpMethod.POST, requestEntity, String.class).getBody(); - } - else { - result = new RestTemplate() - .exchange(clientProperties.getAccessTokenUri(), HttpMethod.POST, requestEntity, String.class) - .getBody(); - } - - return JSONUtil.parseObj(result).getStr("access_token"); - } - - /** - * 使用token 获取用户详情 - * @param token token - * @return user详细 - */ - private OAuth2Authentication getUser(final String token) { - ResourceServerTokenServices tokenServices = SpringContextHolder.getBean(ResourceServerTokenServices.class); - return tokenServices.loadAuthentication(token); - } - -} diff --git a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/annotation/WithMockOAuth2User.java b/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/annotation/WithMockOAuth2User.java deleted file mode 100644 index 43256bb90..000000000 --- a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/annotation/WithMockOAuth2User.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.pig4cloud.pigx.test.annotation; - -import com.pig4cloud.pigx.test.WithMockSecurityContextFactory; -import org.springframework.security.test.context.support.WithSecurityContext; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * @author lengleng - * @date 2020/9/22 - *

- * WithMockOAuth2User 注解 - */ -@Retention(RetentionPolicy.RUNTIME) -@WithSecurityContext(factory = WithMockSecurityContextFactory.class) -public @interface WithMockOAuth2User { - - /** - * 用户名 - */ - String username() default "admin"; - - /** - * 密码 - */ - String password() default "123456"; - - /** - * 租户编号 默认租户编号1 - */ - long tenant() default 1L; - -} diff --git a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/kit/OAuthKit.java b/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/kit/OAuthKit.java deleted file mode 100644 index e39879987..000000000 --- a/pigx-common/pigx-common-test/src/main/java/com/pig4cloud/pigx/test/kit/OAuthKit.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.pig4cloud.pigx.test.kit; - -import com.pig4cloud.pigx.common.core.util.SpringContextHolder; -import org.springframework.http.HttpHeaders; -import org.springframework.security.oauth2.client.OAuth2ClientContext; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -/** - * @author lengleng - * @date 2020/9/22 - *

- * Mocke 工具类 - */ -public class OAuthKit { - - /** - * mock 请求增加统一请求头 - * @return - */ - public static RequestPostProcessor token() { - return mockRequest -> { - OAuth2ClientContext clientContext = SpringContextHolder.getBean(OAuth2ClientContext.class); - String token = clientContext.getAccessToken().getValue(); - mockRequest.addHeader(HttpHeaders.AUTHORIZATION, - String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, token)); - return mockRequest; - }; - } - -} diff --git a/pigx-common/pom.xml b/pigx-common/pom.xml index 2c8ced488..d1f37cc4a 100644 --- a/pigx-common/pom.xml +++ b/pigx-common/pom.xml @@ -51,7 +51,6 @@ pigx-common-sentinel pigx-common-sequence pigx-common-swagger - pigx-common-test pigx-common-websocket pigx-common-xss diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/TokenVo.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/TokenVo.java new file mode 100644 index 000000000..820862d64 --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/TokenVo.java @@ -0,0 +1,28 @@ +package com.pig4cloud.pigx.admin.api.vo; + +import lombok.Data; + +/** + * 前端展示令牌管理 + * + * @author lengleng + * @date 2022/6/2 + */ +@Data +public class TokenVo { + + private String id; + + private Long userId; + + private String clientId; + + private String username; + + private String accessToken; + + private String issuedAt; + + private String expiresAt; + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysUserController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysUserController.java index 50636e363..5fea5ed15 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysUserController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysUserController.java @@ -218,7 +218,7 @@ public class SysUserController { * @param username 用户名 * @return R */ - @Inner + @Inner(value = false) @PutMapping("/lock/{username}") public R lockUser(@PathVariable String username) { return userService.lockUser(username); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/handler/MiniAppLoginHandler.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/handler/MiniAppLoginHandler.java deleted file mode 100644 index bad651968..000000000 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/handler/MiniAppLoginHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.pigx.admin.controller.handler; - -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.pig4cloud.pigx.admin.api.dto.UserInfo; -import com.pig4cloud.pigx.admin.api.entity.SysSocialDetails; -import com.pig4cloud.pigx.admin.api.entity.SysUser; -import com.pig4cloud.pigx.admin.mapper.SysSocialDetailsMapper; -import com.pig4cloud.pigx.admin.service.SysUserService; -import com.pig4cloud.pigx.common.core.constant.SecurityConstants; -import com.pig4cloud.pigx.common.core.constant.enums.LoginTypeEnum; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - * @author lengleng - * @date 2019年11月02日 - *

- * 微信小程序 - */ -@Slf4j -@Component("MINI") -@AllArgsConstructor -public class MiniAppLoginHandler extends AbstractLoginHandler { - - private final SysUserService sysUserService; - - private final SysSocialDetailsMapper sysSocialDetailsMapper; - - /** - * 小程序登录传入code - *

- * 通过code 调用qq 获取唯一标识 - * @param code - * @return - */ - @Override - public String identify(String code) { - SysSocialDetails condition = new SysSocialDetails(); - condition.setType(LoginTypeEnum.MINI_APP.getType()); - SysSocialDetails socialDetails = sysSocialDetailsMapper.selectOne(new QueryWrapper<>(condition)); - - String url = String.format(SecurityConstants.MINI_APP_AUTHORIZATION_CODE_URL, socialDetails.getAppId(), - socialDetails.getAppSecret(), code); - String result = HttpUtil.get(url); - log.debug("微信小程序响应报文:{}", result); - - Object obj = JSONUtil.parseObj(result).get("openid"); - return obj.toString(); - } - - /** - * openId 获取用户信息 - * @param openId - * @return - */ - @Override - public UserInfo info(String openId) { - SysUser user = sysUserService.getOne(Wrappers.query().lambda().eq(SysUser::getMiniOpenid, openId)); - - if (user == null) { - log.info("微信小程序未绑定:{}", openId); - return null; - } - return sysUserService.findUserInfo(user); - } - - /** - * 绑定逻辑 - * @param user 用户实体 - * @param identify 渠道返回唯一标识 - * @return - */ - @Override - public Boolean bind(SysUser user, String identify) { - user.setMiniOpenid(identify); - sysUserService.updateById(user); - return true; - } - -} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSocialDetailsServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSocialDetailsServiceImpl.java index 02f41e4ea..d50b54b09 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSocialDetailsServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSocialDetailsServiceImpl.java @@ -22,7 +22,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pigx.admin.api.dto.UserInfo; import com.pig4cloud.pigx.admin.api.entity.SysSocialDetails; import com.pig4cloud.pigx.admin.api.entity.SysUser; -import com.pig4cloud.pigx.admin.controller.handler.LoginHandler; +import com.pig4cloud.pigx.admin.handler.LoginHandler; import com.pig4cloud.pigx.admin.mapper.SysSocialDetailsMapper; import com.pig4cloud.pigx.admin.mapper.SysUserMapper; import com.pig4cloud.pigx.admin.service.SysSocialDetailsService; diff --git a/pom.xml b/pom.xml index 97a4c1b63..41c26b9bb 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ 1.8 3.8.1 0.0.34 + 0.4.0 2.2.5 2.7.7 1.2.7