♻️ Refactoring code. 重写token 生成规则

This commit is contained in:
lbw 2022-05-30 11:10:27 +08:00
commit f846556527
18 changed files with 488 additions and 336 deletions

View File

@ -16,9 +16,13 @@
package com.pig4cloud.pig.auth.config;
import com.nimbusds.jose.jwk.source.JWKSource;
import cn.hutool.core.lang.UUID;
import com.pig4cloud.pig.auth.support.CustomeOAuth2AccessTokenGenerator;
import com.pig4cloud.pig.auth.support.OAuth2ResourceOwnerPasswordAuthenticationConverter;
import com.pig4cloud.pig.auth.support.OAuth2ResourceOwnerPasswordAuthenticationProvider;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.util.WebUtils;
import com.pig4cloud.pig.common.security.component.PigDaoAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@ -26,20 +30,12 @@ import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
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.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.*;
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.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
@ -51,21 +47,25 @@ import java.util.Arrays;
/**
* @author lengleng
* @date 2022/5/27 认证服务器配置
* @date 2022/5/27
*
* 认证服务器配置
*/
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfiguration {
private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/login";
/**
* 定义 Spring Security 的拦截器链比如我们的 授权url获取token的url 需要由那个过滤器来处理此处配置这个 1.开放oauth2
* 相关地址 2.增加密码模式的扩展 方法 addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider
* 定义 Spring Security 的拦截器链比如我们的 授权url获取token的url 需要由那个过滤器来处理此处配置这个
*
* 1.开放oauth2 相关地址</br>
* 2.增加密码模式的扩展 方法 addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
OAuth2AuthorizationService authorizationService) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer.tokenEndpoint(
@ -77,6 +77,8 @@ public class AuthorizationServerConfiguration {
authorizationServerConfigurer.authorizationEndpoint(
authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI));
authorizationServerConfigurer.authorizationService(authorizationService);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http.requestMatcher(endpointsMatcher)
@ -85,31 +87,29 @@ public class AuthorizationServerConfiguration {
SecurityFilterChain securityFilterChain = http.formLogin(Customizer.withDefaults()).build();
// Custom configuration for Resource Owner Password grant type. Current
// implementation has no support for Resource Owner
// Password grant type
addCustomOAuth2PasswordAuthenticationProvider(http);
return securityFilterChain;
}
@Bean
public OAuth2TokenGenerator tokenGenerator(JWKSource jwkSource) {
JwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
/**
* 令牌生成实现 username:uuid
* @return OAuth2TokenGenerator
*/
@Bean
public OAuth2TokenGenerator oAuth2TokenGenerator() {
CustomeOAuth2AccessTokenGenerator tokenGenerator = new CustomeOAuth2AccessTokenGenerator();
tokenGenerator.setAccessTokenGenerator(() -> String.format("%s:%s:%s", SecurityConstants.PROJECT_PREFIX,
WebUtils.getRequest().get().getParameter(SecurityConstants.USERNAME), UUID.fastUUID()));
return new DelegatingOAuth2TokenGenerator(tokenGenerator, new OAuth2RefreshTokenGenerator());
}
/**
* 扩展密码模式
* 扩展密码模式
*/
@SuppressWarnings("all")
private void addCustomOAuth2PasswordAuthenticationProvider(HttpSecurity http) {
@ -118,10 +118,12 @@ public class AuthorizationServerConfiguration {
OAuth2TokenGenerator oAuth2TokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);
OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider =
new OAuth2ResourceOwnerPasswordAuthenticationProvider(authenticationManager,authorizationService,oAuth2TokenGenerator);
OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
authenticationManager, authorizationService, oAuth2TokenGenerator);
// This will add new authentication provider in the list of existing authentication providers.
// 处理 UsernamePasswordAuthenticationToken
http.authenticationProvider(new PigDaoAuthenticationProvider());
// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
}

View File

@ -20,29 +20,32 @@ import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
/**
* 服务安全相关配置
*
* @author lengleng
* @date 2022/1/12 认证相关配置
* @date 2022/1/12
*/
@EnableWebSecurity
public class WebSecurityConfiguration {
// @formatter:off
/**
* spring security 默认的安全策略
* @param http security注入点
* @return SecurityFilterChain
* @throws Exception
*/
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests -> authorizeRequests
.antMatchers("/oauth/*").permitAll()
.anyRequest().authenticated()
)
.csrf().disable()
.formLogin(Customizer.withDefaults());
http.authorizeRequests(
// 暴露自定义 password 等端点
authorizeRequests -> authorizeRequests.antMatchers("/oauth/*").permitAll().anyRequest().authenticated())
// 个性化 formLogin
.csrf().disable().formLogin(Customizer.withDefaults());
// http.authenticationProvider(new PigDaoAuthenticationProvider());
return http.build();
}

View File

@ -1,165 +0,0 @@
//package com.pig4cloud.pig.auth.endpoint;
//
//import cn.hutool.core.util.StrUtil;
//import cn.hutool.extra.spring.SpringUtil;
//import com.pig4cloud.pig.common.core.constant.SecurityConstants;
//import com.pig4cloud.pig.common.core.util.R;
//import com.pig4cloud.pig.common.security.service.PigUserDetailsService;
//import lombok.RequiredArgsConstructor;
//import lombok.SneakyThrows;
//import org.springframework.core.Ordered;
//import org.springframework.http.HttpHeaders;
//import org.springframework.http.converter.HttpMessageConverter;
//import org.springframework.http.server.ServletServerHttpResponse;
//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
//import org.springframework.security.core.Authentication;
//import org.springframework.security.core.userdetails.UserDetails;
//import org.springframework.security.oauth2.core.*;
//import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
//import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
//import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
//import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
//import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
//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.token.DefaultOAuth2TokenContext;
//import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
//import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
//import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
//import org.springframework.util.CollectionUtils;
//import org.springframework.web.bind.annotation.*;
//
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.IOException;
//import java.time.temporal.ChronoUnit;
//import java.util.Comparator;
//import java.util.HashMap;
//import java.util.Map;
//import java.util.Optional;
//
///**
// * 登录端点
// *
// * @author lengleng
// * @date 2022/5/27
// */
//@RestController
//@RequiredArgsConstructor
//@RequestMapping("/oauth")
//public class LoginEndpoint {
//
// private final OAuth2AuthorizationService tokenService;
//
// private final RegisteredClientRepository registeredClientRepository;
//
// private final OAuth2TokenGenerator tokenGenerator;
//
// private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
//
// private final OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
//
// private BasicAuthenticationConverter authenticationConverter = new BasicAuthenticationConverter();
//
// @SneakyThrows
// @PostMapping("/token")
// public void login(HttpServletRequest request, HttpServletResponse response, String username, String password) {
//
// // 获取请求header 中的basic 信息
// UsernamePasswordAuthenticationToken clientAuthentication = authenticationConverter.convert(request);
// RegisteredClient client = registeredClientRepository.findByClientId(clientAuthentication.getName());
//
// // 根据用户名查询用户
// Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
// .getBeansOfType(PigUserDetailsService.class);
// Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
// .filter(service -> service.support(client.getClientId(), AuthorizationGrantType.PASSWORD.getValue()))
// .max(Comparator.comparingInt(Ordered::getOrder));
// UserDetails userDetails = optional.get().loadUserByUsername(username);
//
// // 生成accessToken
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,
// null);
// DefaultOAuth2TokenContext.Builder builder = DefaultOAuth2TokenContext.builder().registeredClient(client)
// .principal(authenticationToken).tokenType(OAuth2TokenType.ACCESS_TOKEN)
// .authorizationGrantType(AuthorizationGrantType.PASSWORD);
// OAuth2Token generatedAccessToken = this.tokenGenerator.generate(builder.build());
// OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
// generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
// generatedAccessToken.getExpiresAt(), builder.build().getAuthorizedScopes());
//
// OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(client)
// .principalName(authenticationToken.getName()).authorizationGrantType(AuthorizationGrantType.PASSWORD);
// if (generatedAccessToken instanceof ClaimAccessor) {
// authorizationBuilder.token(accessToken,
// (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
// ((ClaimAccessor) generatedAccessToken).getClaims()));
// }
// else {
// authorizationBuilder.accessToken(accessToken);
// }
//
// // 创建刷新令牌
// OAuth2Token generatedRefreshToken = this.refreshTokenGenerator
// .generate(builder.tokenType(OAuth2TokenType.REFRESH_TOKEN)
// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).build());
// authorizationBuilder.refreshToken((OAuth2RefreshToken) generatedRefreshToken);
// OAuth2Authorization authorization = authorizationBuilder.build();
//
// // 保存认证信息
// tokenService.save(authorization);
//
// // 对外输出
// Map<String, Object> additionalParameters = new HashMap<>();
// additionalParameters.put("license", "pig");
// additionalParameters.put(SecurityConstants.DETAILS_USER, userDetails);
// OAuth2AccessTokenAuthenticationToken oAuth2AccessTokenAuthenticationToken = new OAuth2AccessTokenAuthenticationToken(
// client, authenticationToken, accessToken, (OAuth2RefreshToken) generatedRefreshToken,
// additionalParameters);
// sendAccessTokenResponse(response, oAuth2AccessTokenAuthenticationToken);
//
// }
//
// @GetMapping("/info")
// public OAuth2Authorization info(String token) {
// return tokenService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
// }
//
// private void sendAccessTokenResponse(HttpServletResponse response, Authentication authentication)
// throws IOException {
//
// OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
//
// OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
// OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
// Map<String, Object> 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);
// this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
// }
//
// @DeleteMapping("/logout")
// public R<Boolean> 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();
// OAuth2Authorization oAuth2Authorization = tokenService.findByToken(tokenValue, OAuth2TokenType.ACCESS_TOKEN);
// tokenService.remove(oAuth2Authorization);
// return R.ok();
// }
//
//}

View File

@ -0,0 +1,127 @@
package com.pig4cloud.pig.auth.support;
import lombok.Setter;
import org.springframework.lang.Nullable;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
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.*;
/**
* @author lengleng
* @date 2022/5/29
*/
public class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
@Setter
private StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator(
Base64.getUrlEncoder().withoutPadding(), 96);
private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> 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.getProviderContext() != null) {
issuer = context.getProviderContext().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())
.providerContext(context.getProviderContext())
.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();
OAuth2AccessToken accessToken = new CustomeOAuth2AccessTokenGenerator.OAuth2AccessTokenClaims(
OAuth2AccessToken.TokenType.BEARER, this.accessTokenGenerator.generateKey(),
accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(),
accessTokenClaimsSet.getClaims());
return accessToken;
}
/**
* 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<OAuth2TokenClaimsContext> accessTokenCustomizer) {
Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
this.accessTokenCustomizer = accessTokenCustomizer;
}
private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
private final Map<String, Object> claims;
private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
Set<String> scopes, Map<String, Object> claims) {
super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
this.claims = claims;
}
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
}
}

View File

@ -1,5 +1,6 @@
package com.pig4cloud.pig.auth.support;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -70,6 +71,9 @@ public class OAuth2ResourceOwnerPasswordAuthenticationConverter implements Authe
&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
// 注入license
additionalParameters.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal,
requestedScopes, additionalParameters);

View File

@ -1,5 +1,6 @@
package com.pig4cloud.pig.auth.support;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.support.MessageSourceAccessor;
@ -74,8 +75,7 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements Authen
/**
* Performs authentication with the same contract as
* {@link AuthenticationManager#authenticate(Authentication)}
* .
* {@link AuthenticationManager#authenticate(Authentication)} .
* @param authentication the authentication request object.
* @return a fully authenticated object including credentials. May return
* <code>null</code> if the <code>AuthenticationProvider</code> is unable to support
@ -99,7 +99,7 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements Authen
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
Set<String> authorizedScopes = registeredClient.getScopes();
Set<String> authorizedScopes;
// Default to configured scopes
if (!CollectionUtils.isEmpty(resouceOwnerPasswordAuthentication.getScopes())) {
for (String requestedScope : resouceOwnerPasswordAuthentication.getScopes()) {
@ -113,9 +113,11 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements Authen
throw new ScopeException(OAuth2ErrorCodesExpand.SCOPE_IS_EMPTY);
}
Map<String, Object> additionalParameters = resouceOwnerPasswordAuthentication.getAdditionalParameters();
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
Map<String, Object> reqParameters = resouceOwnerPasswordAuthentication.getAdditionalParameters();
String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME);
String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD);
Map<String, Object> additionalParameters = new HashMap<>(4);
try {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
@ -125,6 +127,9 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements Authen
Authentication usernamePasswordAuthentication = authenticationManager
.authenticate(usernamePasswordAuthenticationToken);
// ----- 输出扩展 暴露信息 -----
additionalParameters.put(SecurityConstants.DETAILS_USER, usernamePasswordAuthentication.getPrincipal());
// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
@ -193,12 +198,9 @@ public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements Authen
LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken");
// ----- 扩展 暴露信息 -----
Map<String, Object> objectMap = new HashMap<>();
objectMap.put(OAuth2ParameterNames.USERNAME, username);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken,
refreshToken, objectMap);
refreshToken, additionalParameters);
}
catch (Exception ex) {

View File

@ -30,7 +30,7 @@ public interface SecurityConstants {
/**
* 前缀
*/
String PROJECT_PREFIX = "pig_";
String PROJECT_PREFIX = "pig";
/**
* 项目的license
@ -50,7 +50,7 @@ public interface SecurityConstants {
/**
* 默认登录URL
*/
String OAUTH_TOKEN_URL = "/oauth/token";
String OAUTH_TOKEN_URL = "/oauth2/token";
/**
* grant_type
@ -68,32 +68,20 @@ public interface SecurityConstants {
String BCRYPT = "{bcrypt}";
/**
* sys_oauth_client_details 表的字段不包括client_idclient_secret
* {noop} 加密的特征码
*/
String CLIENT_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, "
+ "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, "
+ "refresh_token_validity, additional_information, autoapprove";
/**
* JdbcClientDetailsService 查询语句
*/
String BASE_FIND_STATEMENT = "select " + CLIENT_FIELDS + " from sys_oauth_client_details";
/**
* 默认的查询语句
*/
String DEFAULT_FIND_STATEMENT = BASE_FIND_STATEMENT + " order by client_id";
/**
* 按条件client_id 查询
*/
String DEFAULT_SELECT_STATEMENT = BASE_FIND_STATEMENT + " where client_id = ?";
String NOOP = "{noop}";
/***
* 资源服务器默认bean名称
*/
String RESOURCE_SERVER_CONFIGURER = "resourceServerConfigurerAdapter";
/**
* 用户名
*/
String USERNAME = "username";
/**
* 用户信息
*/

View File

@ -9,9 +9,6 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
@ -31,15 +28,11 @@ public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/auth/oauth2/jwks").build();
Jwt jwt = jwtDecoder.decode(token);
String principalClaimValue = jwt.getClaimAsString(JwtClaimNames.SUB);
OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigUserDetailsService.class);
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(oldAuthorization.getRegisteredClientId(),
oldAuthorization.getAuthorizationGrantType().getValue()))
@ -47,7 +40,7 @@ public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
UserDetails userDetails = null;
try {
userDetails = optional.get().loadUserByUsername(principalClaimValue);
userDetails = optional.get().loadUserByUsername(oldAuthorization.getPrincipalName());
}
catch (UsernameNotFoundException notFoundException) {
}

View File

@ -16,32 +16,17 @@
package com.pig4cloud.pig.common.security.component;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pig.admin.api.feign.RemoteUserService;
import com.pig4cloud.pig.common.security.service.PigUser;
import com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import java.util.LinkedHashSet;
import java.util.List;
/**
* @author lengleng
* @date 2020-06-23
@ -50,54 +35,10 @@ import java.util.List;
@RequiredArgsConstructor
public class PigResourceServerAutoConfiguration {
private final RemoteUserService remoteUserService;
private final CacheManager cacheManager;
/**
* 配置授权服务器连接数据库增加自定义序列化数据
*/
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate,
registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(
registeredClientRepository);
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
// You will need to write the Mixin for your class so Jackson can marshall it.
objectMapper.addMixIn(LinkedHashSet.class, LinkedHashSet.class);
objectMapper.addMixIn(PigUser.class, PigUser.class);
rowMapper.setObjectMapper(objectMapper);
service.setAuthorizationRowMapper(rowMapper);
return service;
}
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
/**
* 如果是授权码的流程可能客户端申请了多个权限比如获取用户信息修改用户信息 此Service处理的是用户给这个客户端哪些权限比如只给获取用户信息的权限
*/
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
@Bean("pms")
public PermissionService permissionService() {
return new PermissionService();
@ -119,17 +60,8 @@ public class PigResourceServerAutoConfiguration {
}
@Bean
public UserDetailsService userDetailsService(){
return new PigUserDetailsServiceImpl(remoteUserService ,cacheManager);
public UserDetailsService userDetailsService() {
return new PigUserDetailsServiceImpl(remoteUserService, cacheManager);
}
// @Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/auth/oauth2/jwks").build();
return jwtDecoder;
}
}

View File

@ -4,8 +4,12 @@ import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationService;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
@ -18,11 +22,11 @@ import java.util.UUID;
*/
public class PigTokenStoreAutoConfiguration {
//todo 暂时屏蔽redis 权限配置存在不兼容的问题
// @Bean
// public OAuth2AuthorizationService authorizationService(RedisTemplate redisTemplate) {
// return new PigRedisOAuth2AuthorizationService(redisTemplate);
// }
// todo 暂时屏蔽redis 权限配置存在不兼容的问题
@Bean
public OAuth2AuthorizationService authorizationService(RedisTemplate redisTemplate) {
return new PigRedisOAuth2AuthorizationService(redisTemplate);
}
@Bean
@SneakyThrows

View File

@ -16,13 +16,20 @@
package com.pig4cloud.pig.common.security.feign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
/**
* @author lengleng
* @date 2019/2/1 feign 拦截器传递 header 中oauth token 使用hystrix 的信号量模式
*/
@ConditionalOnProperty("security.oauth2.client.client-id")
public class PigFeignClientConfiguration {
/**
* 注入 oauth2 feign token 增强
* @param tokenResolver token获取处理器
* @return 拦截器
*/
@Bean
public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) {
return new PigOAuthRequestInterceptor(tokenResolver);
}
}

View File

@ -0,0 +1,73 @@
package com.pig4cloud.pig.common.security.feign;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.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;
import java.util.Enumeration;
/**
* 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 </br>
*
* 1. 如果使用 非web 请求header 区别 </br>
*
* 2. 根据authentication 还原请求token
* @param template
*/
@Override
public void apply(RequestTemplate template) {
Collection<String> fromHeader = template.headers().get(SecurityConstants.FROM);
// 带from 请求直接跳过
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
return;
}
// 非web 请求直接跳过
if (!WebUtils.getRequest().isPresent()) {
return;
}
HttpServletRequest request = WebUtils.getRequest().get();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
// 避免请求参数的 query token 无法传递
String token = tokenResolver.resolve(request);
if (StrUtil.isBlank(token)) {
return;
}
template.header(HttpHeaders.AUTHORIZATION, String.format("%s %s", OAuth2AccessToken.TokenType.BEARER, token));
}
}

View File

@ -0,0 +1,116 @@
package com.pig4cloud.pig.common.security.service;
import cn.hutool.core.util.BooleanUtil;
import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.util.R;
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.OAuth2TokenFormat;
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.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.util.StringUtils;
import java.time.Duration;
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.
*
* <p>
* 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) {
R<SysOauthClientDetails> detailsR = clientDetailsService.getClientDetailsById(clientId,
SecurityConstants.FROM_IN);
SysOauthClientDetails clientDetails = detailsR.getData();
RegisteredClient.Builder builder = RegisteredClient.withId(clientDetails.getClientId())
.clientId(clientDetails.getClientSecret())
.clientSecret(SecurityConstants.NOOP + clientDetails.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
// 授权模式
Optional.ofNullable(clientDetails.getAuthorizedGrantTypes())
.ifPresent(grants -> StringUtils.commaDelimitedListToSet(grants)
.forEach(s -> builder.authorizationGrantType(new AuthorizationGrantType(s))));
// 回调地址
Optional.ofNullable(clientDetails.getWebServerRedirectUri()).ifPresent(builder::redirectUri);
// scope
Optional.ofNullable(clientDetails.getScope()).ifPresent(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();
}
}

View File

@ -67,9 +67,7 @@ public class PigUser extends User implements OAuth2AuthenticatedPrincipal {
*/
@Override
public Map<String, Object> getAttributes() {
Map<String, Object> attributes = new HashMap<>();
attributes.put("a", "v");
return attributes;
return new HashMap<>();
}
@Override

View File

@ -3,3 +3,4 @@ com.pig4cloud.pig.common.security.service.PigAppUserDetailsServiceImpl
com.pig4cloud.pig.common.security.component.PigSecurityInnerAspect
com.pig4cloud.pig.common.security.component.PigTokenStoreAutoConfiguration
com.pig4cloud.pig.common.security.component.PigSecurityMessageSourceConfiguration
com.pig4cloud.pig.common.security.service.PigRemoteRegisteredClientRepository

View File

@ -0,0 +1,58 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/
package com.pig4cloud.pig.admin.api.feign;
import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.constant.ServiceNameConstants;
import com.pig4cloud.pig.common.core.util.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
/**
* @author lengleng
* @date 2020/12/05
*/
@FeignClient(contextId = "remoteClientDetailsService", value = ServiceNameConstants.UMPS_SERVICE)
public interface RemoteClientDetailsService {
/**
* 通过clientId 查询客户端信息
* @param clientId 用户名
* @param from 调用标志
* @return R
*/
@GetMapping("/client/getClientDetailsById/{clientId}")
R<SysOauthClientDetails> getClientDetailsById(@PathVariable("clientId") String clientId,
@RequestHeader(SecurityConstants.FROM) String from);
/**
* 查询全部客户端
* @param from 调用标识
* @return R
*/
@GetMapping("/client/list")
R<List<SysOauthClientDetails>> listClientDetails(@RequestHeader(SecurityConstants.FROM) String from);
}

View File

@ -1,3 +1,4 @@
com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService
com.pig4cloud.pig.admin.api.feign.RemoteDictService
com.pig4cloud.pig.admin.api.feign.RemoteDeptService
com.pig4cloud.pig.admin.api.feign.RemoteLogService

View File

@ -23,6 +23,7 @@ import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
import com.pig4cloud.pig.admin.service.SysOauthClientDetailsService;
import com.pig4cloud.pig.common.core.util.R;
import com.pig4cloud.pig.common.log.annotation.SysLog;
import com.pig4cloud.pig.common.security.annotation.Inner;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@ -117,4 +118,11 @@ public class OauthClientDetailsController {
return R.ok();
}
@Inner(false)
@GetMapping("/getClientDetailsById/{clientId}")
public R getClientDetailsById(@PathVariable String clientId) {
return R.ok(sysOauthClientDetailsService.getOne(
Wrappers.<SysOauthClientDetails>lambdaQuery().eq(SysOauthClientDetails::getClientId, clientId), false));
}
}