feat: 自定义数据响应格式和添加刷新模式

This commit is contained in:
郝先瑞 2023-07-04 19:16:50 +08:00
parent 2807b604d7
commit a07a3c8839
15 changed files with 267 additions and 129 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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
}
};
}
}

View File

@ -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 {
}

View File

@ -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)));

View File

@ -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 {
}

View File

@ -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();
}
}

View File

@ -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";
}

View File

@ -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);

View File

@ -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;
}

View File

@ -16,5 +16,5 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
* @param roles
* @return
*/
Integer getMaximumDataScope(Set<String> roles);
Integer getMaxDataRangeDataScope(Set<String> roles);
}

View File

@ -93,7 +93,7 @@ public interface SysRoleService extends IService<SysRole> {
* @param roles
* @return
*/
Integer getMaximumDataScope(Set<String> roles);
Integer getMaxDataRangeDataScope(Set<String> roles);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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