产生原因
Chrome新版本中禁用第三方操作(当前版本84.0.4147.125)
场景重现
业务方域名为 zbj.com,嵌入iframe域名为 z***.la 用户访问时,cookie不属于同一域名,导致不能获取值,一直跳转登录页
问题分析
项目基于 spring-session 和 spring-security 处理session和授权校验 通过请教前端大佬,可以通过cookie中设置SameSite=None解决(配合secure=true使用)
关键代码
public class BossAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public BossAuthenticationFilter ( String defaultFilterProcessesUrl) {
super ( defaultFilterProcessesUrl) ;
}
public BossAuthenticationFilter ( RequestMatcher requiresAuthenticationRequestMatcher) {
super ( requiresAuthenticationRequestMatcher) ;
}
@Override
public Authentication attemptAuthentication ( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
BossAuthenticationToken bat = new BossAuthenticationToken ( "" , "" ) ;
String authCode = request. getParameter ( "code" ) ;
if ( authCode != null && authCode. trim ( ) . length ( ) > 0 ) {
Map< String, String> authInfo = BossAuthUtil. decode ( authCode, this . key) ;
String userId = authInfo. get ( "userId" ) ;
String token = authInfo. get ( "token" ) ;
bat = new BossAuthenticationToken ( userId, token) ;
bat. setDetails ( authInfo) ;
}
return this . getAuthenticationManager ( ) . authenticate ( bat) ;
}
}
public class BossAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1 L;
private String userId;
private String token;
@Getter
private volatile long lastCheckTime;
public BossAuthenticationToken ( String userId, String token) {
this ( userId, token, null) ;
}
public BossAuthenticationToken ( String userId, String token,
Collection< ? extends GrantedAuthority > authorities) {
super ( authorities) ;
this . userId = userId;
this . token = token;
}
public BossAuthenticationToken ( BossAuthenticationToken token) {
super ( token. getAuthorities ( ) ) ;
this . userId = token. userId;
this . token = token. token;
setDetails ( token. getDetails ( ) ) ;
}
@Override
public Object getPrincipal ( ) {
return this . userId;
}
@Override
public Object getCredentials ( ) {
return this . token;
}
public void setLastCheckTime ( long time) {
synchronized ( this ) {
this . lastCheckTime = time;
}
}
}
@Component
public class BossTokenAuthenticationProvider implements AuthenticationProvider {
private static final Logger log = LoggerFactory. getLogger ( BossTokenAuthenticationProvider. class ) ;
private UserService userService;
public BossTokenAuthenticationProvider ( UserService userService) {
this . userService = userService;
}
@Override
public Authentication authenticate ( Authentication authentication) throws AuthenticationException {
String userId = ( String) authentication. getPrincipal ( ) ;
String token = ( String) authentication. getCredentials ( ) ;
if ( userId == null) {
throw new UsernameNotFoundException ( "User not found." ) ;
}
if ( token == null) {
throw new BadCredentialsException ( "Invalid user." ) ;
}
UserVO user = null;
try {
user = userService. findUserById ( Integer. parseInt ( userId) ) ;
} catch ( Exception e) {
log. error ( "Find user occurs exception: {}" , e. getMessage ( ) , e) ;
}
BossAuthenticationToken bt = new BossAuthenticationToken ( userId, token, grantedAuthorities) ;
bt. setDetails ( authentication. getDetails ( ) ) ;
bt. setAuthenticated ( true ) ;
bt. setLoginUser ( user) ;
bt. setLastCheckTime ( System. currentTimeMillis ( ) ) ;
return bt;
}
@Override
public boolean supports ( Class< ? > authentication) {
return authentication == BossAuthenticationToken. class || authentication == UsernamePasswordAuthenticationToken. class ;
}
}
@Bean ( name = "zbjCookie" )
public CookieSerializer createCookieSerializer ( ) {
DefaultCookieSerializer chasCookieSerializer = new DefaultCookieSerializer ( ) ;
chasCookieSerializer. setCookiePath ( "/" ) ;
chasCookieSerializer. setCookieName ( "ZBJ_SESSION" ) ;
chasCookieSerializer. setUseBase64Encoding ( true ) ;
chasCookieSerializer. setUseSecureCookie ( true ) ;
chasCookieSerializer. setSameSite ( "None" ) ;
return chasCookieSerializer;
}
chasCookieSerializer. setUseSecureCookie ( true ) ;
chasCookieSerializer. setSameSite ( "None" ) ;
如果项目使用spring-boot 1.* 是没有 setSameSite 方法 可以尝试将 spring-boot 2.1.0 以上
public void setSameSite ( String sameSite) {
this . sameSite = sameSite;
}
如果底层依赖较多,可将 2.1.0 以上版本的源码 org.springframework.session.web.http.DefaultCookieSerializer 拷贝到项目中
问题集锦
经过测试,在无痕模式还是存在循环跳转登录问题,可能因为无痕模式下,***(前端不专一,不敢下结论) 本地测试这个功能,会存在一直轮询问题。因为SameSite搭配scure使用,本地默认不支持https访问 这种方式也可以设置cookie
response. setHeader ( "Set-Cookie" , "SESSION=" + cookie. getValue ( ) + ";sameSite=None;secure=true;httpOnly=true" ) ;