为什么需要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中。
具体实现
伪代码
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);//设置用户对象
}
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
}
}
}
key为sessionId,value为session对象
sessionId也就是客户端的token
伪代码
可以重写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中,后续不需要传参
两种方式
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;
}
}
}
}
@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的用户信息就不是最新的了,这也是往往我们更改用户信息需要重新登录的原因