彻底解决springcurityoauth2⾃动续签token问题
最近项⽬中引⼊oauth2框架,发现token存在固定30分钟失效问题;⽽⽤户在实际使⽤过程中,如果固定30分钟就要登出,重新登录⽅能使⽤,体验度极差;需要后端能够提供token续签的功能;
⽹上也搜索过不少资料,例如:
后端提供刷新token接⼝,前端加⼊定时器,依赖后端返回的过期时间定时刷新token;
但此⽅式⽆法满⾜当前项⽬的需要,项⽬允许同⼀个账号开启多个⽹页访问,需要登录;且前端使⽤的是本地ssion缓存,token只针对单个页⾯有效;同⼀个账号若是通过刷新token接⼝获取新的token,会导致其他界⾯的token失效;
为了解决项⽬token续签问题,通过源码分析,获得了突破;
思路⼀:如何实现token续签?
⽤户登录成功后,会在redis中缓存key值:
private static final String ACCESS ="access:";
private static final String AUTH_TO_ACCESS ="auth_to_access:";
private static final String AUTH ="auth:";
private static final String CLIENT_ID_TO_ACCESS ="client_id_to_access:";
private static final String UNAME_TO_ACCESS ="uname_to_access:";
这些key都是有过期时间的,若是想在原token的基础上实现⾃动续签,更新这⼏个key的过期时间就可以了;(后续实践证明除了要更新key的过期时间,还要更新对应的value⾥⾯的expiration)
思路⼆:什么时间实现token续签?
前端每⼀个请求都会携带token,在gateway⾥⾯增加⼀个过滤器,拦截所有的请求,对token进⾏解析,如果token还有10分钟(举例)过期,就重新设置token过期时间为30分钟;
(此处不可每次请求都对token续签,效率很低)
思路有了,可以垒代码了,在上代码之前,先看⼀下源码:
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = AccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if(existingAccessToken != null){
if(existingAccessToken.isExpired()){
月桥花院RefreshToken()!= null){
refreshToken = RefreshToken();
// The token store could remove the refresh token when the
// access token is removed, but we want to
//
}
}
el{
// Re-store the access token in ca the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// Only create a new refresh token if there wasn't an existing one
// associated with an expired access token.
/
/ Clients might be holding existing refresh tokens, so we re-u it in
// the ca that the old access token
// expired.
if(refreshToken == null){
refreshToken = createRefreshToken(authentication);
}
// But the refresh token itlf might need to be re-issued if it has
// expired.
el if(refreshToken instanceof ExpiringOAuth2RefreshToken){
ExpiringOAuth2RefreshToken expiring =(ExpiringOAuth2RefreshToken) refreshToken;
带孩子去北京旅游攻略if(System.currentTimeMillis()> Expiration().getTime()){
refreshToken = createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
// In ca it was modified
refreshToken = RefreshToken();
if(refreshToken != null){
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
登录的时候会⽣成token 信息,oauth2会先根据登录请求参数去redis⾥⾯获取缓存的token对象信息。
若存在token,,或判断该对象的过期时间,若过期了会删除token,重新⽣成;若没有过期,则会返回原token对象;若不存在token,直接⽣成新的token对象;
获取token信息代码很关键,取的是AUTH_TO_ACCESS 对象:
石辛含片@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication){
String key = actKey(authentication);
byte[] rializedKey = rializeKey(AUTH_TO_ACCESS + key);
新年晚会主持词byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = (rializedKey);
} finally {
conn.clo();
}
OAuth2AccessToken accessToken = derializeAccessToken(bytes);
if(accessToken != null
&&!key.actKey(Value())))){ // Keep the stores consistent (maybe the same ur is
// reprented by this authentication but the details have
// changed)
storeAccessToken(accessToken, authentication);
}
return accessToken;
}
了解了token的⽣成逻辑,对token的续签操作就很简单了;
过滤器代码很简单,因为涉及到项⽬隐私,直接上token续签逻辑:
以下是代码逻辑:
maven依赖:
<!-- auth2配置,只是为了引jar包解析token -->
<dependency>
<groupId>org.springframework.curity.oauth</groupId>
<artifactId>spring-curity-oauth2</artifactId>
<version>2.0.15.RELEASE</version>
<exclusions>
猪头怎么画<!-- 必须去除,否则会加载 curity 配置导致请求⽆效 -->
<exclusion>
<groupId>org.springframework.curity</groupId>
<artifactId>spring-curity-config</artifactId>
世界肝炎日</exclusion>
<exclusion>
<groupId>org.springframework.curity</groupId>
<artifactId>spring-curity-web</artifactId>
</exclusion>
</exclusions>
</dependency>
过滤器代码:
package xxx;
import com.alibaba.fastjson.JSON;
le.gson.Gson;
flix.zuul.ZuulFilter;
t.RequestContext;
slf4j.Slf4j;
import s.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.flix.zuul.filters.support.FilterConstants;
import org.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import javax.annotation.Resource;
import javax.rvlet.http.HttpServletRequest;
import javax.rvlet.http.HttpServletRespon;
@Slf4j
@Component
public class WebTokenFilter extends ZuulFilter {
private Gson gson = new Gson();
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Resource
private CosSecurityProperties cosSecurityProperties;
@Override
public String filterType(){
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder(){
return -1;
}
@Override
public boolean shouldFilter(){
RequestContext ctx = CurrentContext(); String url = Request().getRequestURI();
if(getPass(url)){//⽩名单url不校验token
return fal;
}
return true;
}
public boolean getPass(String methodUrl){
for(String passUrl : IgnoreUrlList()){
ains(passUrl)){
return true;
}
可爱女生名字}
return fal;
}
@Override
public Object run(){
log.info("---WebTokenFilter---");
RequestContext ctx = CurrentContext(); Model error = checkToken(ctx);
if(error == null){
ctx.t("isSuccess", true);
}el{
ctx.tSendZuulRespon(fal);
ctx.t("isSuccess", fal);
fillRespon(ctx, error);
}
return null;
}
/**
* 验证token
* @param ctx
* @return
*/
private Model checkToken(RequestContext ctx){
HttpServletRequest request = Request();
//验证请求token
String token = Header("Authorization");
try {
if(StringUtils.isBlank(token)){
log.info("没有读取到token");
return null;
}
String urname = UrnameByToken(token);
if(StringUtils.isEmpty(urname)){
return null;
}
ctx.addZuulRequestHeader("urname", urname);
}catch (BusinessException e){
<("【token】校验不通过,token=[{}],errorMessage=[{}]", token, e.getMessage()); ("error:",e);
ctx.tSendZuulRespon(fal);
ctx.tResponStatusCode(HttpStatus.UNAUTHORIZED.value());
ResultMessage result = new ResultMessage(fal, e.getErrMsg());
监理合同
ctx.JSONString(result, SerializerFeature.BrowrCompatible)); // ctx.t(CosSecurityConstants.KEY_IS_SECURITY_PASS, fal);
}
return null;
}
/**
* 设置respon
*
* @param ctx
* @param error
*/
private void fillRespon(RequestContext ctx, Model error){
HttpServletRequest request = Request();
HttpServletRespon respon = Respon();
//序列化message
String message = Json(error);
log.info("respon message:{}", message);
String contentType = Header("Content-Type");
String accept = Header("accept");
if((contentType != null && LowerCa().contains("application/json")) ||(accept != null && LowerCa().contains("application/json"))){
respon.tContentType("application/json;chart=UTF-8");
respon.tHeader("Access-Control-Allow-Origin", "*");
respon.tHeader("Access-Control-Allow-Methods", "*");
respon.tHeader("Access-Control-Allow-Headers", "*");
ctx.tResponBody(message);
}el{
ctx.tSendZuulRespon(fal);
respon.tContentType("text/html;chart=UTF-8");
respon.tHeader("Access-Control-Allow-Origin", "*");
respon.tHeader("Access-Control-Allow-Methods", "*");
respon.tHeader("Access-Control-Allow-Headers", "*");
ctx.tResponBody("<h3>error<h3></br>no Permission denied");
}
}
}
token 解析⼯具类,包含续签逻辑:
package xxx;