mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 12:48:59 +08:00
feat: 自定义数据响应格式和添加刷新模式
This commit is contained in:
parent
2807b604d7
commit
a07a3c8839
@ -13,10 +13,6 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.*;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
@ -27,11 +23,10 @@ import org.springframework.security.oauth2.server.authorization.context.Authoriz
|
||||
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.CollectionUtils;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 验证码模式身份验证提供者
|
||||
@ -45,10 +40,7 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||
|
||||
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
@ -93,7 +85,7 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
String verifyCode = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE);
|
||||
String verifyCodeKey = (String) additionalParameters.get(CaptchaParameterNames.VERIFY_CODE_KEY);
|
||||
|
||||
String cacheCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_KEY_PREFIX +verifyCodeKey);
|
||||
String cacheCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_KEY_PREFIX + verifyCodeKey);
|
||||
if (!StrUtil.equals(verifyCode, cacheCode)) {
|
||||
throw new OAuth2AuthenticationException("验证码错误");
|
||||
}
|
||||
@ -105,25 +97,11 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
// 用户名密码身份验证,成功后返回带有权限的认证信息
|
||||
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
||||
|
||||
// 验证申请访问范围(Scope)
|
||||
Set<String> authorizedScopes = registeredClient.getScopes();
|
||||
Set<String> requestedScopes = captchaAuthenticationToken.getScopes();
|
||||
if (!CollectionUtils.isEmpty(requestedScopes)) {
|
||||
Set<String> unauthorizedScopes = requestedScopes.stream()
|
||||
.filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope))
|
||||
.collect(Collectors.toSet());
|
||||
if (!CollectionUtils.isEmpty(unauthorizedScopes)) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
|
||||
}
|
||||
authorizedScopes = new LinkedHashSet<>(requestedScopes);
|
||||
}
|
||||
|
||||
// 访问令牌(Access Token) 构造器
|
||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal(usernamePasswordAuthentication) // 身份验证成功的认证信息(用户名、权限等信息)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.authorizedScopes(authorizedScopes)
|
||||
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA) // 授权方式
|
||||
.authorizationGrant(captchaAuthenticationToken) // 授权具体对象
|
||||
;
|
||||
@ -144,7 +122,6 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(usernamePasswordAuthentication.getName())
|
||||
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
|
||||
.authorizedScopes(authorizedScopes)
|
||||
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
|
||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||
authorizationBuilder.token(accessToken, (metadata) ->
|
||||
@ -171,39 +148,9 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
||||
authorizationBuilder.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
// 生成 ID token
|
||||
OidcIdToken idToken;
|
||||
if (requestedScopes.contains(OidcScopes.OPENID)) {
|
||||
// @formatter:off
|
||||
tokenContext = tokenContextBuilder
|
||||
.tokenType(ID_TOKEN_TOKEN_TYPE)
|
||||
.authorization(authorizationBuilder.build()) // ID token customizer may need access to the access token and/or refresh token
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2Token generatedIdToken = this.tokenGenerator.generate(tokenContext);
|
||||
if (!(generatedIdToken instanceof Jwt)) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"The token generator failed to generate the ID token.", ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(),
|
||||
generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims());
|
||||
authorizationBuilder.token(idToken, (metadata) ->
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()));
|
||||
} else {
|
||||
idToken = null;
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||
|
||||
this.authorizationService.save(authorization);
|
||||
|
||||
additionalParameters = Collections.emptyMap();
|
||||
if (idToken != null) {
|
||||
additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
|
||||
}
|
||||
additionalParameters = Collections.EMPTY_MAP;
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,13 @@
|
||||
package com.youlai.auth.config;
|
||||
|
||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import com.nimbusds.jose.shaded.json.JSONObject;
|
||||
import com.youlai.auth.authentication.captcha.CaptchaAuthenticationConverter;
|
||||
import com.youlai.auth.authentication.captcha.CaptchaAuthenticationProvider;
|
||||
import com.youlai.auth.authentication.captcha.CaptchaAuthenticationToken;
|
||||
@ -21,57 +21,52 @@ import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationConverter
|
||||
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationProvider;
|
||||
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
|
||||
import com.youlai.auth.handler.MyAuthenticationSuccessHandler;
|
||||
import com.youlai.auth.userdetails.member.MemberDetails;
|
||||
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||
import com.youlai.common.result.Result;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import com.youlai.auth.userdetails.user.jackson.SysUseMixin;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.*;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||
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.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
|
||||
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.token.*;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -88,7 +83,7 @@ public class AuthorizationServerConfig {
|
||||
private final WxMaService wxMaService;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final MemberDetailsService memberDetailsService;
|
||||
|
||||
private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||
|
||||
/**
|
||||
* 授权配置
|
||||
@ -203,7 +198,22 @@ public class AuthorizationServerConfig {
|
||||
@Bean
|
||||
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
|
||||
RegisteredClientRepository registeredClientRepository) {
|
||||
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
|
||||
|
||||
JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
|
||||
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
|
||||
rowMapper.setLobHandler(new DefaultLobHandler());
|
||||
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(SysUserDetails.class, SysUseMixin.class);
|
||||
objectMapper.addMixIn(Long.class, Object.class);
|
||||
rowMapper.setObjectMapper(objectMapper);
|
||||
service.setAuthorizationRowMapper(rowMapper);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -217,7 +227,7 @@ public class AuthorizationServerConfig {
|
||||
@Bean
|
||||
OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
|
||||
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource));
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer());
|
||||
jwtGenerator.setJwtCustomizer(jwtCustomizer);
|
||||
|
||||
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
|
||||
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
|
||||
@ -226,25 +236,7 @@ public class AuthorizationServerConfig {
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
||||
return context -> {
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
|
||||
// Customize headers/claims for access_token
|
||||
Optional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal -> {
|
||||
JwtClaimsSet.Builder claims = context.getClaims();
|
||||
if (principal instanceof SysUserDetails userDetails) {
|
||||
claims.claim("user_id", String.valueOf(userDetails.getUserId()));
|
||||
} else if (principal instanceof MemberDetails userDetails) {
|
||||
claims.claim("member_id", String.valueOf(userDetails.getId()));
|
||||
}
|
||||
});
|
||||
} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
|
||||
// Customize headers/claims for id_token
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ -284,6 +276,7 @@ public class AuthorizationServerConfig {
|
||||
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope(OidcScopes.PROFILE)
|
||||
.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
registeredClientRepository.save(mallAppClient);
|
||||
@ -320,6 +313,7 @@ public class AuthorizationServerConfig {
|
||||
.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
|
||||
.scope(OidcScopes.OPENID)
|
||||
.scope(OidcScopes.PROFILE)
|
||||
.tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofDays(1)).build())
|
||||
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
|
||||
.build();
|
||||
registeredClientRepository.save(mallAppClient);
|
||||
|
@ -0,0 +1,79 @@
|
||||
package com.youlai.auth.config;
|
||||
|
||||
import com.youlai.auth.userdetails.member.MemberDetails;
|
||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||
import com.youlai.common.constant.SecurityConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 自定义 JWT 的 Claims(声明)
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/7/4
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class JwtCustomizerConfig {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Bean
|
||||
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
||||
return context -> {
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
|
||||
// Customize headers/claims for access_token
|
||||
Optional.ofNullable(context.getPrincipal().getPrincipal()).ifPresent(principal -> {
|
||||
JwtClaimsSet.Builder claims = context.getClaims();
|
||||
if (principal instanceof SysUserDetails userDetails) {
|
||||
|
||||
Long userId = userDetails.getUserId();
|
||||
claims.claim("user_id", userId);
|
||||
|
||||
// 这里存入角色至JWT,解析JWT的角色用于鉴权的位置: ResourceServerConfig#jwtAuthenticationConverter
|
||||
var authorities = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities())
|
||||
.stream()
|
||||
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
|
||||
claims.claim(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY, authorities);
|
||||
|
||||
// 权限数据比较多,缓存至redis
|
||||
Set<String> perms = userDetails.getPerms();
|
||||
redisTemplate.opsForValue().set(SecurityConstants.USER_PERMS_CACHE_PREFIX + userId, perms);
|
||||
|
||||
} else if (principal instanceof MemberDetails userDetails) {
|
||||
claims.claim("member_id", String.valueOf(userDetails.getId()));
|
||||
}else{
|
||||
User user = (User) principal;
|
||||
|
||||
// 这里存入角色至JWT,解析JWT的角色用于鉴权的位置: ResourceServerConfig#jwtAuthenticationConverter
|
||||
var authorities = AuthorityUtils.authorityListToSet(context.getPrincipal().getAuthorities())
|
||||
.stream()
|
||||
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
|
||||
claims.claim(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY, authorities);
|
||||
}
|
||||
});
|
||||
} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
|
||||
// Customize headers/claims for id_token
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.youlai.auth.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @since 2023/6/29
|
||||
*/
|
||||
|
||||
@RestController
|
||||
public class AuthController {
|
||||
|
||||
}
|
@ -52,8 +52,7 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
|
||||
|
||||
OAuth2AccessTokenResponse.Builder builder =
|
||||
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||
.tokenType(accessToken.getTokenType())
|
||||
.scopes(accessToken.getScopes());
|
||||
.tokenType(accessToken.getTokenType());
|
||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||
}
|
||||
@ -68,6 +67,7 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
|
||||
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
|
||||
.convert(accessTokenResponse);
|
||||
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.getWriter().write(JSONUtil.toJsonStr(Result.success(tokenResponseParameters)));
|
||||
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.youlai.auth.userdetails.user.jackson;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
/**
|
||||
* @see org.springframework.security.jackson2.UserMixin
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/7/4
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonDeserialize(using = SysUserDeserializer.class)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SysUseMixin {
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2015-2018 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.youlai.auth.userdetails.user.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Custom Deserializer for {@link User} class. This is already registered with
|
||||
* {@link UserMixin}. You can also use it directly with your mixin class.
|
||||
*
|
||||
* @author Jitendra Singh
|
||||
* @since 4.2
|
||||
* @see UserMixin
|
||||
*/
|
||||
class SysUserDeserializer extends JsonDeserializer<User> {
|
||||
|
||||
private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
|
||||
};
|
||||
|
||||
/**
|
||||
* This method will create {@link User} object. It will ensure successful object
|
||||
* creation even if password key is null in serialized json, because credentials may
|
||||
* be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
|
||||
* that case there won't be any password key in serialized json.
|
||||
* @param jp the JsonParser
|
||||
* @param ctxt the DeserializationContext
|
||||
* @return the user
|
||||
* @throws IOException if a exception during IO occurs
|
||||
* @throws JsonProcessingException if an error during JSON processing occurs
|
||||
*/
|
||||
@Override
|
||||
public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||
JsonNode jsonNode = mapper.readTree(jp);
|
||||
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
|
||||
SIMPLE_GRANTED_AUTHORITY_SET);
|
||||
JsonNode passwordNode = readJsonNode(jsonNode, "password");
|
||||
String username = readJsonNode(jsonNode, "username").asText();
|
||||
String password = passwordNode.asText("");
|
||||
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
|
||||
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
|
||||
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
|
||||
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
|
||||
User result = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
||||
authorities);
|
||||
if (passwordNode.asText(null) == null) {
|
||||
result.eraseCredentials();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
|
||||
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
|
||||
}
|
||||
|
||||
}
|
@ -15,12 +15,7 @@ public interface SecurityConstants {
|
||||
/**
|
||||
* 短信验证码key前缀
|
||||
*/
|
||||
String SMS_CODE_PREFIX = "SMS_CODE:";
|
||||
|
||||
/**
|
||||
* 接口文档 Knife4j 测试客户端ID
|
||||
*/
|
||||
String TEST_CLIENT_ID = "client";
|
||||
String SMS_CODE_PREFIX = "AUTH:SMS_CODE:";
|
||||
|
||||
/**
|
||||
* 系统管理 web 客户端ID
|
||||
@ -32,16 +27,14 @@ public interface SecurityConstants {
|
||||
*/
|
||||
String APP_CLIENT_ID = "mall-app";
|
||||
|
||||
/**
|
||||
* 微信小程序客户端ID
|
||||
*/
|
||||
String WEAPP_CLIENT_ID = "mall-weapp";
|
||||
|
||||
|
||||
/**
|
||||
* 用户权限集合缓存前缀
|
||||
*/
|
||||
String USER_PERMS_CACHE_PREFIX = "AUTH:USER_PERMS:";
|
||||
|
||||
/**
|
||||
* 授权信息中的权限或角色的key
|
||||
*/
|
||||
String AUTHORITIES_CLAIM_NAME_KEY="authorities";
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ package com.youlai.common.security.config;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.youlai.common.constant.SecurityConstants;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -78,10 +80,11 @@ public class ResourceServerConfig {
|
||||
* @return Converter
|
||||
* @see JwtAuthenticationProvider#setJwtAuthenticationConverter(Converter)
|
||||
*/
|
||||
@Bean
|
||||
public Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
|
||||
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
||||
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
|
||||
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(Strings.EMPTY);
|
||||
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(SecurityConstants.AUTHORITIES_CLAIM_NAME_KEY);
|
||||
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
|
||||
|
@ -1,10 +1,9 @@
|
||||
package com.youlai.common.security.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
|
||||
@ -13,6 +12,9 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Spring Security 工具类
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
public static Long getUserId() {
|
||||
@ -34,15 +36,10 @@ public class SecurityUtils {
|
||||
|
||||
public static Set<String> getRoles() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
var roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities())
|
||||
.stream()
|
||||
.collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
|
||||
|
||||
Set roles;
|
||||
if (CollectionUtil.isNotEmpty(authentication.getAuthorities())) {
|
||||
roles = authentication.getAuthorities()
|
||||
.stream()
|
||||
.map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_")).collect(Collectors.toSet());
|
||||
} else {
|
||||
roles = Collections.EMPTY_SET;
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
@ -16,5 +16,5 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
|
||||
* @param roles
|
||||
* @return
|
||||
*/
|
||||
Integer getMaximumDataScope(Set<String> roles);
|
||||
Integer getMaxDataRangeDataScope(Set<String> roles);
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public interface SysRoleService extends IService<SysRole> {
|
||||
* @param roles
|
||||
* @return
|
||||
*/
|
||||
Integer getMaximumDataScope(Set<String> roles);
|
||||
Integer getMaxDataRangeDataScope(Set<String> roles);
|
||||
|
||||
|
||||
}
|
||||
|
@ -206,8 +206,8 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Integer getMaximumDataScope(Set<String> roles) {
|
||||
Integer dataScope = this.baseMapper.getMaximumDataScope(roles);
|
||||
public Integer getMaxDataRangeDataScope(Set<String> roles) {
|
||||
Integer dataScope = this.baseMapper.getMaxDataRangeDataScope(roles);
|
||||
return dataScope;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.youlai.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@ -21,6 +22,7 @@ import com.youlai.system.model.query.UserPageQuery;
|
||||
import com.youlai.system.model.vo.UserExportVO;
|
||||
import com.youlai.system.model.vo.UserInfoVO;
|
||||
import com.youlai.system.model.vo.UserPageVO;
|
||||
import com.youlai.system.service.SysMenuService;
|
||||
import com.youlai.system.service.SysRoleService;
|
||||
import com.youlai.system.service.SysUserRoleService;
|
||||
import com.youlai.system.service.SysUserService;
|
||||
@ -55,6 +57,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
private final SysMenuService menuService;
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
@ -198,9 +202,15 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfo(username);
|
||||
if (userAuthInfo != null) {
|
||||
Set<String> roles = userAuthInfo.getRoles();
|
||||
// 获取最大范围的数据权限
|
||||
Integer dataScope = roleService.getMaximumDataScope(roles);
|
||||
userAuthInfo.setDataScope(dataScope);
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
// 根据角色编码集合获取权限标识集合
|
||||
Set<String> perms = menuService.listRolePerms(roles);
|
||||
userAuthInfo.setPerms(perms);
|
||||
|
||||
// 获取最大范围的数据权限(目前设定DataScope越小,拥有的数据权限范围越大,所以获取得到角色列表中最小的DataScope)
|
||||
Integer dataScope = roleService.getMaxDataRangeDataScope(roles);
|
||||
userAuthInfo.setDataScope(dataScope);
|
||||
}
|
||||
}
|
||||
return userAuthInfo;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<mapper namespace="com.youlai.system.mapper.SysRoleMapper">
|
||||
|
||||
<!-- 获取最大范围的数据权限 -->
|
||||
<select id="getMaximumDataScope" resultType="java.lang.Integer">
|
||||
<select id="getMaxDataRangeDataScope" resultType="java.lang.Integer">
|
||||
SELECT
|
||||
min(data_scope)
|
||||
FROM
|
||||
|
Loading…
Reference in New Issue
Block a user