分布式下如何根据token解析登录用户

token介绍

为什么需要token,浏览器向服务器发的请求为Http请求,而Http是无状态的,也就是说,服务器处理每个http请求是并不知道这个请求是那个客户端发送的。
因此需要token作为一个凭证,token是用户登录成功后,服务器为用户生成的唯一凭证,并保存token与用户对象的映射关系,也就是我们常说的session,然后服务器会将这个凭证反回到浏览器,浏览器保存这个凭证,后续每次发送请求到服务器时都会将token放置到请求头中,服务器根据token可以获取到请求是那个用户发送的

tomcat维护了一个sessionMap
在ManagerBase类中

  /**
     * The set of currently active Sessions for this Manager, keyed by
     * session identifier.
     */
    protected Map<String, Session> sessions = new ConcurrentHashMap<>();

key为session的唯一凭证,value为Session对象
在单机的情况下,tomcat替我们管理了session,在接受到http请求时,读取请求头的token,在拦截器中,取出session
并且set到HttpServletRequest的seesion中,这样我们在controller中可以直接获取到用户。

分布式情况

分布式情况下,需要做到session在多个机器上共享。tomcat维护的session已经无法满足需要了,因此一般将session存储到redis中。
具体实现

登录时记录token到redis

伪代码

public void login(String account,String pwd){
       HttpSession session = httpServletRequest.getSession();


        UserLoginResponse userLoginResponse =  userReadFacade.findByAccount(loginRequest);
        if(userLoginResponse == null){
            throw new RestException("账号活密码不正确");
        }

        if(!passwordEncoder.matches(loginRequest.getPassward(),userLoginResponse.getPassward())) {
         throw new RestException("账号活密码不正确");
        }
       session.setAttribute("user",userDTO);//设置用户对象
}

实现tomcat拦截器

请求处理后保存session到resis

public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter{
@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
				response);

		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
			wrappedRequest.commitSession();//当请求结束后,session可能会发生变化,需要保存session到redis
		}
	}
}

分布式下如何根据token解析登录用户_第1张图片
key为sessionId,value为session对象
sessionId也就是客户端的token

请求前获取session

伪代码
可以重写httpRequest的getSession方法,从redis中获取

	private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
    	@Override
		public HttpSessionWrapper getSession(boolrean create) {
			
			S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);
					if (session != null) {
						this.requestedSession = session;
						this.requestedSessionId = sessionId;
						break;
					}
		    return session;
		}
}

设置用户信息到ThreadLocal

为了获取用户信息方法,经常将用户信息设置到threadLocal中,后续不需要传参
两种方式

1.使用springMvc拦截器

public class PageInterceptor implements HandlerInterceptor {
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
           SsoSecUser ssoSecUser;
              
                ssoSecUser = (SsoSecUser)request.getSession().getAttribute("login_user");
                      if (ssoSecUser == null) {
                        Authentication.setSecUser((SsoSecUser)null);
                        return true;
                    } else {
                        Authentication.setSecUser(ssoSecUser);
                        return true;
                    }
                }
     }   
}

2.使用AOP切面拦截Controller,获取request中的session

@Aspect
@Slf4j
@Order(3)
public class UserContextAop {

    @Pointcut("execution(public * com.xishan.store.usercenter.userweb.controller.*.*(..))")
    private void userContextAspect() {
    }

    @Around("userContextAspect()")
    public Object webContextAround(ProceedingJoinPoint point) throws Throwable {
        //执行前,塞进UserContext中,执行后清除UserContext
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        UserDTO user = null;
        if( request.getSession().getAttribute("user") != null){
            user = (UserDTO) request.getSession().getAttribute("user");
        }
        if(user != null){
            UserContext.putCurrentUser(user);
            RpcContext.getContext().setAttachment("user",JSON.toJSONString(user));
        }
        Object result = null;
        try {
            result = point.proceed();//可能抛出异常,并且异常不能被吃了
        }catch (Throwable e){
            throw  e;
        }finally {
            UserContext.clearCurrentUser();
            RpcContext.getContext().removeAttachment("user");
        }
        return result;
        //执行目标
    }
}

总结

使用缓存session的方法,往往只有在登录的时候,才会将session存起来。那么加入后续用户对象更新,那么session的用户信息就不是最新的了,这也是往往我们更改用户信息需要重新登录的原因

你可能感兴趣的:(登录验证,分布式,java,tomcat)