不想看分析过称的 ,文章结尾就是解决方法
OAuth2 虽然公司项目用了,但只是浅尝辄止,今天自己搭了一下服务,出现这么一个问题。
我是用RedisTokenStore存储token refresh token,在redisTokenStore 存储的时候,会把要存储的对象序列化变成byte数组放到redis里面。
这个地方就出问题了。
具体的提示是:
Resolved [org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.io.NotSerializableException: org.springframework.dao.support.PersistenceExceptionTranslationInterceptor]
解决问题
debug了一下源码,发现是在序列化OAuth2Authentication的时候 有了问题,很奇怪,这里使用的JDK的序列化,没道理会出错的,
重点是: 我的另外一个项目序列化的时候 是没问题的!
仔细对比了一下,最终发现原因
原因
OAuth2Authentication会持有一个Authentication引用,这个可以从它的构造方法得知,这个Authentication是我们前面的filter chain里面认证对象时候产生的。Authentication的GrantedAuthority,是
UserDetails提供的。他们之间有引用的关系。
/**
* Construct an OAuth 2 authentication. Since some grant types don't require user authentication, the user
* authentication may be null.
*
* @param storedRequest The authorization request (must not be null).
* @param userAuthentication The user authentication (possibly null).
*/
public OAuth2Authentication(OAuth2Request storedRequest, Authentication userAuthentication) {
super(userAuthentication == null ? storedRequest.getAuthorities() : userAuthentication.getAuthorities());
this.storedRequest = storedRequest;
this.userAuthentication = userAuthentication;
}
我这个项目里面的UserDetails是我自己扩展的
代码如下
package com.example.sercurity.bo;
import com.example.sercurity.entity.Role;
import com.example.sercurity.entity.User;
import com.example.sercurity.service.PermissonService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @Author: plani
* 创建时间: 2019/8/16 17:54
*/
public class SecurityUser implements UserDetails {
private User user;
private PermissonService permissonService;
public SecurityUser(User user, PermissonService permissonService) {
this.user = user;
this.permissonService = permissonService;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<Role> roles = permissonService.rolesByUser(user);
List<GrantedAuthority> authorities = null;
if (roles!=null&&!roles.isEmpty()){
authorities= roles.stream().map(new Function<Role, GrantedAuthority>() {
@Override
public GrantedAuthority apply(Role role) {
//用注解时候 hasRole 判断 角色前面 加 "ROLE_"
// return new SimpleGrantedAuthority("ROLE_"+role.getRoleName());
//hasAuthority 的时候 就不需要加任何前缀
return new SimpleGrantedAuthority(role.getRoleName());
}
}).collect(Collectors.toList());
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
//账户是否未过期,过期无法验证
@Override
public boolean isAccountNonExpired() {
return true;
}
//指定用户是否解锁,锁定的用户无法进行身份验证
@Override
public boolean isAccountNonLocked() {
return true;
}
//指示是否已过期的用户的凭据(密码),过期的凭据防止认证
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用 ,禁用的用户不能身份验证
@Override
public boolean isEnabled() {
return true;
}
}
可以看到引用用了一个PermissonService,在这里 PermissonService我把它交给spring管理了;
序列化的时候 就出现问题了,我这里的解决思路是,构造方法哪里 不要出入PermissonService的引用了,直接传入它需要的方法就行
改造后的UserDetails
/**
* @Author: plani
* 创建时间: 2019/8/16 17:54
*/
public class SecurityUser implements UserDetails {
private User user;
private List<Role> roles;
public SecurityUser(User user, List<Role> roles) {
this.user = user;
this.roles = roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = null;
if (roles != null && !roles.isEmpty()) {
authorities = roles.stream().map(new Function<Role, GrantedAuthority>() {
@Override
public GrantedAuthority apply(Role role) {
//用注解时候 hasRole 判断 角色前面 加 "ROLE_"
// return new SimpleGrantedAuthority("ROLE_"+role.getRoleName());
//hasAuthority 的时候 就不需要加任何前缀
return new SimpleGrantedAuthority(role.getRoleName());
}
}).collect(Collectors.toList());
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
//账户是否未过期,过期无法验证
@Override
public boolean isAccountNonExpired() {
return true;
}
//指定用户是否解锁,锁定的用户无法进行身份验证
@Override
public boolean isAccountNonLocked() {
return true;
}
//指示是否已过期的用户的凭据(密码),过期的凭据防止认证
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用 ,禁用的用户不能身份验证
@Override
public boolean isEnabled() {
return true;
}
}
结果
试了一下完全ok
总结
序列化的时候 ,不能把spring管理的对象也传进去。
如果有不懂的,可以关注我的公众号 “知我饭否” 向我留言。我会每天更新一些文章,有兴趣的可以 微信 搜索"知我饭否" or 扫描我的 博客头像。