SpringSecurity整合redission序列化问题小结(最新整理)

本文主要是介绍SpringSecurity整合redission序列化问题小结(最新整理),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序...

1. 前言

这个问题网上找了各种资料,困扰了几周,终于是解决了。记住一点,不要用SpringSecurity官方提供的jackson序列化。序列化没问题,反序列化有大坑,会报错missing type id property ‘@class’ 。

//这个就不要用了,试了,反序列化不行。
SecurityJackson2Modules.getModules(this.getClass().getClassLoader())
					.forEach(objectMapper::registerModule);

2. redission配置

2.1 RedissonProperties

@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
    /**
     * redis缓存key前缀
     */
    private String keyPrefix;
    /**
     * 线程池数量,默认值 = 当前处理核数量 * 2
     */
    private int threads;
    /**
     * Netty线程池数量,默认值 = 当前处理核数量 * 2
     */
    private int nettyThreads;
    /**
     * 单机服务配置
     */
    private SingleServerConfig singleServerConfig;
    /**
     * 集群服务配置
     */
    private ClusterServersConfig clusterServersConfig;
    @Data
    @NoArgsConstructor
    public static class SingleServerConfig {
        /**
         * 客户端名称
         */
        private String clientName;
        /**
         * 最小空闲连接数
         */
        private int connectionMinimumIdleSize;
        /**
         * 连接池大小
         */
        private int connectionPoolSize;
        /**
         * 连接空闲超时,单位:毫秒
         */
        private int idleConnectionTimeout;
        /**
         * 命令等待超时,单位:毫秒
         */
        private int timeout;
        /**
         * 发布和订阅连接池大小 【未使用,加上启动不起】
         */
        private int subscriptionConnectionPoolSize;
    }
    @Data
    @NoArgsConstructor
    public static class ClusterServersConfig {
        /**
         * 客户端名称
         */
        private String clientName;
        /**
         * master最小空闲连接数
         */
        private int masterConnectionMinimumIdleSize;
        /**
         * master连接池大小
         */
        private int masterConnectionPoolSize;
        /**
         * slave最小空闲连接数
         */
        private int slaveConnectionMinimumIdleSize;
        /**
         * slave连接池大小
         */
        private int slaveConnectionPoolSize;
        /**
         * 连接空闲超时,单位:毫秒
         */
        private int idleConnectionTimeout;
        /**
         * 命令等待超时,单位:毫秒
         */
        private int timeout;
        /**
         * 发布和订阅连接池大小
         */
        private int subscriptionConnectionPoolSize;
        /**
         * 读取模式
         */
        private ReadMode readMode;
        /**
         * 订阅模式
         */
        private SubscriptionMode subscriptionMode;
    }
}

2.2 RedissionConfiguration

注意依赖排除,引用redis包排除无用依赖.

@Slf4j
@EnableCaching
@AutoConfiguration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissionConfiguration implements InitializingBean {
	@Resource
	private RedisProperties redisProperties;
	@Resource
	private RedissonProperties redissonProperties;
	private ObjectMapper om;
	@Qualifier
	@Bean("redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		StringRedisSerializer keySerializer = new StringRedisSerializer();
		RedisSerializer<Object> valueSerializer =new GenericJackson2jsonRedisSerializer( om);
		redisTemplate.setKeySerializer(keySerializer);
		redisTemplate.setValueSerializer(valueSerializer);
		redisTemplate.setHashKeySerializer(keySerializer);
		redisTemplate.setHashValueSerializer(valueSerializer);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}
	@Bean
	public Redisson redisson() {
		log.info("初始化redission配置...");
		Config config = new Config();
		config.setThreads(redissonProperties.getThreads())
				.setNettyThreads(redissonProperties.getNettyThreads())
				// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
				.setUseScriptCache(true)
				.setCodec(new JsonJacksonCodec(om));
		RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
		if (ObjectUtil.isNotNull(singleServerConfig)) {
			// 使用单机模式
			SingleServerConfig singleConfig = config.useSingleServer()
					.setAddress("redis://" + redisProperties.getHost() + ":"+redisProperties.getPort())
					.setDatabase(redisProperties.getDatabase() )
					.setTimeout(singleServerConfig.getTimeout())
					.setClientName(singleServerConfig.getClientName())
					.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
					.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
					.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
					//# DNS监测时间间隔,单位:毫秒
					.setDnsMonitoringInterval(60000L);
			if (ObjectUtil.isNotEmpty(redisProperties.getPassword())) {
				singleConfig.setPassword(redisProperties.getPassword());
			}
		}
		// 集群配置方式
		RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
		if (ObjectUtil.isNotNull(clusterServersConfig)) {
			ClusterServersConfig serversConfig = config.useClusterServers()
					.setTimeout(clusterServersConfig.getTimeout())
					.setClientName(clusterServersConfig.getClientName())
					.setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
					.setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
					.setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
					.setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
					.setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
					.setReadMode(clusterServersConfig.getReadMode())
					.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
			if (ObjectUtil.isNotEmpty(redisProperties.getPassword())) {
				serversConfig.setPassword(redisProperties.getPassword());
			}
		}
		log.info("初始化redission配置完成");
		return (Redisson) Redisson.create(config);
	}
	@Override
	public void afterPropertiesSet() {
		log.info("设置objectMapper参数...");
		//不影响全局objectMapper
		ObjectMapper copy = new ObjectMapper();
		copy.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		//必须设置,否则无法将JSON转化为对象,会转化成Map类型,JsonTypeInfo.As.PROPERTY序列化加@class属性
		copy.activateDefaultTyping(copy.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
		copy.registerSubtypes(AjUser.class, UsernamePasswordAuthenticationToken.class, SysRole.class);
		// 自定义ObjectMapper的时间处理模块
		JavaTimeModule javaTimeModule = new JavaTimeModule();
		javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
		javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
		javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
		javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
		javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
		javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
		copy.registerModule(javaTimeModule);
		// 启用模块
		SimpleModule module = new SimpleModule();
		module.addDeserializer(AuthUser.class, new AuthUserDeserializer());
		copy.registerModule(module);
		copy.addMixIn(AuthorizationGrantType.class,AuthorizationGrantTypeMixin.class);
		//自定义UnmodifiableMapMixin
		copy.addMixIn(Collections.unmodifiableMap(new HashMap<>()).getClass(),UnmodifiableMapMixin.class);
		//自定义UnmodifiableSetMixin
		copy.addMixIn(Collections.unmodifiableSet(new HashSet<>()).getClass(), UnmodifiableSetMixin.class);
		copy.addMixIn(Principal.class, PrincipalMixin.class);
		copy.addMixIn(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class);
		//提前加载
		copy.enable(DeserializationFeature.EAGER_DESERIALIZER_FETCH);
		// 忽略未知属性
		copy.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
		// 检查子类型 没有匹配的会报错 可调试用
		//copy.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
		// 禁用将日期序列化为时间戳的行为,解决jackson2无法反序列化LocalDateTime的问题
		copy.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
		// 忽略非法字符 \r, \n, \t
		copy.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
		//单独配置object赋值
		om = copy;
		log.info("objectMapper参数设置完成...");
	}
}

3.自定义mixin

3.1 AuthorizationGrantTypeMixin

@JsonDeserialize(using = AuthorizationGrantTypeDeserializer.class)
public abstract class AuthorizationGrantTypeMixin {
    @JsonCreator
    AuthorizationGrantTypeMixin(@JsonProperty("value") String value) {
    }
}

3.2 PrincipalMixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class PrincipalMixin {
}

3.3 UnmodifiableMapMixin

@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS
)
@JsonDeserialize(
    using = UnmodifiableMapDeserializer.class
)
public class UnmodifiableMapMixin {
    @JsonCreator
    UnmodifiableMapMixin(Map<?, ?> map) {
    }
}

3.4 UnmodifiableSetMixin

@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS,
    include = JsonTypeInfo.As.PROPERTY
)
@JsonDeserialize(
    using = UnmodifiableSetDeserializer.class
)
public abstract class UnmodifiableSetMixin {
    @JsonCreator
    UnmodifiableSetMixin(Set<?> s) {
    }
}

3.5 UsernamePasswordAuthenticationTokenMixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
@JsonDeserialize(
		using = UsernamePasswordAuthenticationTokenDeserializer.class
)
public abstract class UsernamePasswordAuthenticationTokenMixin {
	@JsonCreator
	public UsernamePasswordAuthenticationTokenMixin(
			@JsonProperty("principal") Object principal,
			@JsonProperty("credentials") Object credentials,
			@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
	}
}

4. 自定义deserializer

这里得注意几个问题:
1.为什么deserializer要用new ObjectMapper
答:用JsonParse的 jp.getCodec().readTree(jp);会报错 missing type id property ‘@class’ 。
2.为什么读取子类转化子类的时候要用jp.getCodec().treeToValue(jsonNode, clazz)
答:自定义反序列化器注册在原ObjectMapper里面的,new ObjectMapper不包含这些处理会报错 missing type id property ‘@class’ 。
3.为什么不适用SpringSecurity自带的序列化器,比如CoreJackson2Module
答:同问题1,源码里面的是
ObjectMapper mapper = (ObjectMapper)jp.getCodec();
JsonNode node = (JsonNode)mapper.readTree(jp);//运行到这行会报错
换成新ObjectMapper则不报错

4.1 AuthorizationGrantTypeDeserializer

@Slf4j
public class AuthorizationGrantTypeDeserializer extends StdDeserializer<AuthorizationGrantType> {
	private final ObjectMapper noTypingMapper = new ObjectMapper();
	public AuthorizationGrantTypeDeserializer() {
		super(AuthorizationGrantType.class);
		log.info(">> AuthorizationGrantTypeDeserializer 实例化完成 >>");
	}
	@Override
	public AuthorizationGrantType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
		log.info(">> Using AuthorizationGrantTypeDeserializer");
		JsonNode node = noTypingMapper.readTree(p);
		// 支持两种格式:纯字符串 或 {"value": "client_credentials"}
		String value = node.isTextual() ? node.asText() : node.get("value").asText();
		return switch (value) {
			case "authorization_code" -> AuthorizationGrantType.AUTHORIZATION_CODE;
			case "client_credentials" -> AuthorizationGrantType.CLIENT_CREDENTIALS;
			case "refresh_token" -> AuthorizationGrantType.REFRESH_TOKEN;
			default -> new AuthorizationGrantType(value);
		};
	}
}

4.2 UnmodifiableMapDeserializer

@Slf4j
public class UnmodifiableMapDeserializer extends StdDeserializer<Map<?, ?>> {
	private final ObjectMapper noTypingMapper = new ObjectMapper();
	public UnmodifiableMapDeserializer() {
		super(Map.class);
		log.info(">> UnmodifiableMapDeserializer 实例化完成 >>");
	}
	@Override
    public Map<?, ?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
		log.info(">> Using UnmodifiableMapDeserializer");
		ObjectCodec codec = jp.getCodec();
        JsonNode node = noTypingMapper.readTree(jp);
        Map<String, Object> result = new LinkedHashMap<>();
        if (node != null && node.isObject()) {
            Objects.requireNonNull(node);
			for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
				Map.Entry<String, JsonNode> field = it.next();
				JsonNode value = field.getValue();
				String key = field.getKey();
				if (key.contains("Principal") && value.has("@class")) {
					String className = value.get("@class").asText();
					try {
						Class<?> clazz = Class.forName(className);
						//Object val =noTypingMapper.readValue(value.traverse(noTypingMapper),clazz);
						result.put(key,codec.treeToValue(value, clazz));
					} catch (Exception e) {
						throw new RuntimeException("无法反序列化 principal", e);
					}
				} else {
					// 默认处理其他字段(按 Map 反序列化)
					result.put(key, noTypingMapper.readValue(value.traverse(noTypingMapper), Object.class));
				}
			}
        }
        return Collections.unmodifiableMap(result);
    }
}

4.3 UnmodifiableSetDeserializer

@Slf4j
public class UnmodifiableSetDeserializer extends StdDeserializer<Set<?>> {
	private final ObjectMapper noTypingMapper = new ObjectMapper();
	public UnmodifiableSetDeserializer() {
		super(Set.class);
		log.info(">> UnmodifiableSetDeserializer 实例化完成 >>");
	}
	@Override
	public Set<?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
		log.info(">> Using UnmodifiableSetDeserializer");
		JsonNode node = noTypingMapper.readTree(jp);
		Set<String> result = new LinkedHashSet<>(node.size());
		if (node != null && node.isObject()) {
			Objects.requireNonNull(node);
			for (JsonNode jsonNode : node) {
				result.add(jsonNode.asText());
			}
		}
		return Collections.unmodifiableSet(result);
	}
}

4.4 UsernamePasswordAuthenticationTokenDeserializer

@Slf4j
puChina编程blic class UsernamePasswordAuthenticationTokenDeserializer extends StdDeserializer<UsernamePasswordAuthenticationToken> {
	private final ObjectMapper noTypingMapper = new ObjectMapper();
	protected UsernamePasswordAuthenticationTokenDeserializer() {
		super(UsernamePasswordAuthenticationToken.class);
		log.info(">> UsernamePasswordAuthenticationTokenDeserializer 实例化完成 >>");
	}
	@Override
	@SneakyThrows
	public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt) {
		log.info(">> Using UsernamePasswordAuthenticationTokenDeserializer");
		JsonNode jsonNode = noTypingMapper.readTree(jp);
		JsonNode principalNode = this.readJsonNode(jsonNode, "principal");
		Object principal = this.getPrincipal(jp, principalNode);
		JsonNode credentialsNode = this.readJsonNode(jsonNode, "credentials");
		JsonNode authoritiesNode = this.readJsonNode(jsonNode, "authorities");
		Object credentials = this.getCredentials(credentialsNode);
		Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
		if (authoritiesNode != null && authoritiesNode.isArray() && authoritiesNode.size() == 2) {
			//第一个是类型,第二个才是存的值
			JsonNode actualAuthoritiesArray = authoritiesNode.get(1); // 第二个元素是真实列表
			if (actualAuthoritiesArray != null && actualAuthoritiesArray.isArray()) {
				for (JsonNode authNode : actualAuthoritiesArray) {
					if (!authNode.has("@class"))  {
						String role = authNode.get("authority").asText();
						SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role);
						authorities.add(authority);
					}
				}
			}
		}
		// 构造 token 对象
		return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
	}
	private Object getCredentials(JsonNode credentialsNode) {
		return !credentialsNode.isNull() && !credentialsNode.isMissingNode() ? credentialsNode.asText() : null;
	}
	private Object getPrincipal(JsonParser jp, JsonNode principalNode) throws IOException, ClassNotFoundException {
		String className = principalNode.get("@class").asText();
		Class<?> clazz = Class.forName(className);
		//使用原mapper才能使用UserDeserializer
		return principalNode.isObject() ? jp.getCodec().treeToValue(principalNode, clazz): principalNode.asText();
	}
	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
	}
}

4.5 AuthUserDeserializer

@Slf4j
public class AuthUserDeserializer extends StdDeserializer<AuthUser> {
	private final ObjectMapper noTypingMapper = new ObjectMapper();
    public AuthUserDeserializer() {
        super(AuthUser.class);
		log.info(">> AjUserDeserializer 实例化完成 >>");
    }
    @Override
	@SneakyThrows
    public AuthUser deserialize(JsonParser p, DeserializationContext ctxt) {
		log.info(">> Using AjUserDeserializer");
		JsonNode jsonNode = noTypingMapper.readTree(p);
		JsonNode idNode = this.readJsonNode(jsonNode, "id");
		JsonNode deptIdNode = this.readJsonNode(jsonNode, "deptId");
		JsonNode phoneNode = this.readJsonNode(jsonNode, "phone");
		JsonNode usernameNode = this.readJsonNode(jsonNode, "username");
		JsonNode passwordNode = this.readJsonNode(jsonNode, "password");
		JsonNode accountNonLockedNode = this.readJsonNode(jsonNode, "accountNonLocked");
		//索引0是类型,1是数据
		long id = Long.parseLong(idNode.get(1).asText());
		long deptId = Long.parseLong(deptIdNode.get(1).asText());
		String phone = phoneNode.asText();
		String username= usernameNode.asText();
		String password =  passwordNode.asText();
		boolean accountNonLocked  =  Boolean.parseBoolean(accountNonLockedNode.asText());
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
		JsonNode authoritiesNode = this.getAuthorities(jsonNode);
		if (authoritiesNode != null && authoritiesNode.isArray() && authoritiesNode.size() == 2) {
			//第一个是类型,第二个才是存的值
			JsonNode actualAuthoritiesArray = authoritiesNode.get(1); // 第二个元素是http://www.chinasem.cn真实列表
			if (actualAuthoritiesArray != null && actualAuthoritiesArray.isArray()) {
				for (JsonNode authNode : actualAuthoritiesArray) {
					if (!authNode.has("@class"))  {
						String role = authNode.get("authority").asText();
						SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role);
						authorities.add(authority);
					}
				}
			}
		}
		//取缓存不加SecurityConstants.BCRYPT 加密的特征码
		return new AuthUser(id, deptId,username,
				password, phone, (SysRole) this.getSysRole(p,this.readJsonNode(jsonNode, "sysRole")),true, true, true,
			FasdoYvNMC	accountNonLocked, authorities);
    }
	private JsonNode getAuthorities(JsonNode jsonNode) {
		return jsonNode.has("authorities") ? jsonNode.get("authorities") : MissingNode.getInstance();
	}
	private Object getSysRole(JsonParser jp,  JsonNode node) throws IOException, ClassNotFoundException {
		String className = node.get("@class").asText();
		Class<?> clazz = Class.forName(className);
		return node.isObject() ?jp.getCodec().treeToValue(node, clazz) : node.asText();
	}
	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
	}
}

5. 自定义扩展用户信息 AuthUser

@Getter
public class AuthUser extends User implements OAuth2AuthenticatedPrincipal {
	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
	/**
	 * 用户ID
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	private final Long id;
	/**
	 * 部门ID
	 */
	@JsonSerialize(using = ToStringSerializer.class)
	private final Long deptId;
	/**
	 * 手机号
	 */
    private final String phone;
	/**
	 * 角色
	 */
    private final SysRole sysRole;
	@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
	public AuthUser(@JsonProperty("id") Long id,
				  @JsonProperty("deptId") Long deptId,
				  @JsonProperty("username") String username,
				  @JsonProperty("password") String password,
				  @JsonProperty("phone") String phone,
				  @JsonProperty("sysRole") SysRole sysRole,
				  @JsonProperty("enabled") boolean enabled,
				  @JsonProperty("accountNonExpired") boolean accountNonExpired,
				  @JsonProperty("credentialsNonExpired") boolean credentialsNonExpired,
				  @JsonProperty("accountNonLocked") boolean accountNonLocked,
				  @JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
		super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
		this.id = id;
		this.deptId = deptId;
		this.phone = phone;
		this.sysRole = sysRole;
	}
	/**
	 * Get the OAuth 2.0 token attributes
	 * @return the OAuth 2.0 token attributes
	 */
	@Override
	public Map<String, Object> getAttributes() {
		return new HashMap<>();
	}
	@Override
	public String getName() {
		return this.getUsername();
	}
}

6. SpringSecurity其他地方改动

这里就不再贴SpringSecurity其他代码了,自行实现,开源框架Pig自取

6.1 认证服务器配置

@Configuration
@RequiredArgsConstructor
public class AuthorizationServerConfiguration {
	private final OAuth2AuthorizationService authorizationService;
	private final PasswordDecoderFilter passwordDecoderFilter;
	private final ValidateCodeFilter validateCodeFilter;
	/**
	 * Authorization Server 配置,仅对 /oauth2/** 的请求有效
	 * @param http http
	 * @return {@link SecurityFilterChain }
	 * @throws Exception 异常
	 */
	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SecurityFilterChain authorizationServer(HttpSecurity http) throws Exception {
		// 配置授权服务器的安全策略,只有http://www.chinasem.cn/oauth2/**的请求才会走如下的配置
		http.securityMatcher("/oauth2/**");
		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
		// 增加验证码过滤器
		http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
		// 增加密码解密过滤器
		http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class);
		http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点
							tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
									.accessTokenResponseHandler(new AjAuthenticationSuccessEventHandler()) // 登录成功处理器
									.errorResponseHandler(new AjAuthenticationFailureEventHandler());// 登录失败处理器
						}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
								oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new AjAuthenticationFailureEventHandler()))// 处理客户端认证异常
						.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面
								.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults())
				.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated());
		// 设置 Token 存储的策略
		http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
						.authorizationServerSettings(
								AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()),
				Customizer.withDefaults());
		// 设置授权码模式登录页面
		http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults());
		DefaultSecurityFilterChain securityFilterChain = http.build();
		// 注入自定义授权模式实现
		addCustomOAuth2GrantAuthenticationProvider(http);
		return securityFilterChain;
	}
	/**
	 * 令牌生成规则实现 </br>
	 * client:username:uuid
	 * @return OAuth2TokenGenerator
	 */
	@Bean
	public OAuth2TokenGenerator oAuth2TokenGenerator() {
		CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator();
		// 注入Token 增加关联用户信息
		accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer());
		return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
	}
	/**
	 * request -> xToken 注入请求转换器
	 * @return DelegatingAuthenticationConverter
	 */
	@Bean
	public AuthenticationConverter accessTokenRequestConverter() {
		return new DelegatingAuthenticationConverter(Arrays.asList(
				new OAuth2ResourceOwnerPasswordAuthenticationConverter(),
				new OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(),
				new OAuth2ClientCredentialsAuthenticationConverter(),
				new OAuth2AuthorizationCodeAuthenticationConverter(),
				new OAuth2AuthorizationCodeRequestAuthenticationConverter()));
	}
	/**
	 * 注入授权模式实php现提供方
	 * <p>
	 * 1. 密码模式 </br>
	 * 2. 短信登录 </br>
	 */
	private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
				authenticationManager, authorizationService, oAuth2TokenGenerator());
		OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
				authenticationManager, authorizationService, oAuth2TokenGenerator());
		// 处理 UsernamePasswordAuthenticationToken
		http.authenticationProvider(new DaoAuthenticationProvider());
		// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
		// 处理 OAuth2ResourceOwnerSmsAuthenticationToken
		http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
	}
}

到此这篇关于SpringSecurity整合redission序列化问题的文章就介绍到这了,更多相关SpringSecurity整合redission序列化内容请搜索编程China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于SpringSecurity整合redission序列化问题小结(最新整理)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155372

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input