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中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

Java中Integer128陷阱

《Java中Integer128陷阱》本文主要介绍了Java中Integer与int的区别及装箱拆箱机制,重点指出-128至127范围内的Integer值会复用缓存对象,导致==比较结果为true,下... 目录一、Integer和int的联系1.1 Integer和int的区别1.2 Integer和in

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

nginx 负载均衡配置及如何解决重复登录问题

《nginx负载均衡配置及如何解决重复登录问题》文章详解Nginx源码安装与Docker部署,介绍四层/七层代理区别及负载均衡策略,通过ip_hash解决重复登录问题,对nginx负载均衡配置及如何... 目录一:源码安装:1.配置编译参数2.编译3.编译安装 二,四层代理和七层代理区别1.二者混合使用举例

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads

Spring boot整合dubbo+zookeeper的详细过程

《Springboot整合dubbo+zookeeper的详细过程》本文讲解SpringBoot整合Dubbo与Zookeeper实现API、Provider、Consumer模式,包含依赖配置、... 目录Spring boot整合dubbo+zookeeper1.创建父工程2.父工程引入依赖3.创建ap

SpringBoot3.X 整合 MinIO 存储原生方案

《SpringBoot3.X整合MinIO存储原生方案》本文详细介绍了SpringBoot3.X整合MinIO的原生方案,从环境搭建到核心功能实现,涵盖了文件上传、下载、删除等常用操作,并补充了... 目录SpringBoot3.X整合MinIO存储原生方案:从环境搭建到实战开发一、前言:为什么选择MinI