以前,做过的项目中有单点登录的模块,以前做的模块没仔细观察,查看资料发现单点登录的解决方案还挺多!以下就记录单点的不同解决方案;
http协议的特性
无状态的,就是用户连接只要获取响应,服务器不记录用户状态,就算用户访问100次,服务器也不知道用户具体信息;
1.集群环境下的session共享问题
通过cookie+session可以保存用户状态;
2.关于负载均衡算法分析
2.1)轮询,加权轮询
2.2)Hash算法(可以解决session共享问题)
2.3)随机
2.4)最小连接数
3.Session共享问题的解决办法
3.1)Session replication Session复制 配置文件 (tomcat中可以配置)
使得集群中各个服务器相互保存各自节点的session数据,tomcat本身就可以实现session复制的功能,基于IP组播的方式,同步时候性能会很低;出现问题:
3.1.1)随着集群的数量越来越大,session带来的带宽影响网络开销
3.1.2)每个节点都要存储集群中所有节点的session数据,很耗费内存
3.2) Cookie Based Token(JWT)(通行证)
基于服务的一定算法,生成一个token给客户端,客户端每次清楚,都携带这个token服务器验证token是否有效,再对token的关键字进行处理(一种纯cookie的方式);JWT强调的是服务器端不对token进行存储,而是通过签名算法进行解密.
3.3) Session的统一管理 Redis存储或者mysql存储(把jsessio存储到同一个位置),无论哪个节点新增和修改了session,最终都发生在存储的地方;需要双击热备份,防止突然宕机 出现的问题:
3.3.1)session数据进行网络操作,存在延迟性
3.3.2) 如果session服务器宕机,将大规模影响到应用
3.4) Session sticky (会话粘性) 利用hash算法可以解决session sticky的问题。这种解决方案会出现的问题:
3.4.1)有一台服务器宕机,这台机器上保存的session会丢失
3.4.2)这种方式实现了session保存,没有办法在四层进行网络转发,只能在7层进行网络转发
4.单点登录实现方案
利用jwt token的方式
Jwt token三部分组成,头部(header),有效载荷(playload),签名(signature)
附录:
jwt 实际上就是定义了一套数据加密以及验签的算法的规范,根据这个规范来实现单点登录,以及数据传输及验签功能。
问题:1)不能传递敏感问题
2)jwt中的部分内容可以解密破解
利用token来解决session共享问题解决方案的核心代码:
1.TokenIntercepter.java
import java.lang.reflect.Method;
public class TokenIntercepter extends HandlerInterceptorAdapter {
private final String ACCESS_TOKEN="access_token";
@Autowired
IUserCoreService iUserCoreService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)handler;
Object bean=handlerMethod.getBean();
if(isAnoymous(handlerMethod)){
return true;
}
if(!(bean instanceof BaseController)){
throw new RuntimeException("must extend basecontroller");
}
// 利用CookieUitl工具类取出cookie的值
String token=CookieUtil.getCookieValue(request,ACCESS_TOKEN);
// 判断request请求是不是Ajax
boolean isAjax=CookieUtil.isAjax(request);
if(StringUtils.isEmpty(token)){ //如果为空
if(isAjax){ // 设置返回值
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("{\"code\":\"-1\",\"msg\":\"error\"}");
return false;
}
response.sendRedirect(GpmallWebConstant.GPMALL_SSO_ACCESS_URL); //并重定向到sso_access_url链接
return false;
}
CheckAuthRequest checkAuthRequest=new CheckAuthRequest();
checkAuthRequest.setToken(token);
CheckAuthResponse checkAuthResponse=iUserCoreService.validToken(checkAuthRequest);
if("000000".equals(checkAuthResponse.getCode())){
BaseController baseController=(BaseController)bean;
baseController.setUid(checkAuthResponse.getUid());
return super.preHandle(request, response, handler);
}
if(isAjax){
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("{\"code\":\""+checkAuthResponse.getCode()+"\"" +
",\"msg\":\""+checkAuthResponse.getMsg()+"\"}");
return false;
}
response.sendRedirect(GpmallWebConstant.GPMALL_SSO_ACCESS_URL);
return false;
}
private boolean isAnoymous(HandlerMethod handlerMethod){
Object bean=handlerMethod.getBean();
Class clazz=bean.getClass();
if(clazz.getAnnotation(Anoymous.class)!=null){
return true;
}
Method method=handlerMethod.getMethod();
return method.getAnnotation(Anoymous.class)!=null;
}
}
2.BaseController.java
public class BaseController {
static ThreadLocal<String> uidThreadLocal=new ThreadLocal<>();
public void setUid(String uid){
uidThreadLocal.set(uid);
}
public String getUid(){
return uidThreadLocal.get();
}
}
3.UserContoller.java
@RestController
public class UserController extends BaseController{
@Autowired
IUserCoreService userCoreService;
@Autowired
KafkaTemplate kafkaTemplate;
@Anoymous
@PostMapping("/login")
public ResponseData doLogin(String username, String password,
HttpServletResponse response){
ResponseData data=new ResponseData();
UserLoginRequest request=new UserLoginRequest();
request.setPassword(password);
request.setUserName(username);
UserLoginResponse userLoginResponse=userCoreService.login(request);
response.addHeader("Set-Cookie",
"access_token="+userLoginResponse.getToken()+";Path=/;HttpOnly");
data.setMessage(userLoginResponse.getMsg());
data.setCode(userLoginResponse.getCode());
data.setData(GpmallWebConstant.GPMALL_ACTIVITY_ACCESS_URL);
return data;
}
@GetMapping("/register")
@Anoymous
public @ResponseBody
ResponseData register(String username, String password, String mobile){
ResponseData data=new ResponseData();
UserRegisterRequest request=new UserRegisterRequest();
request.setMobile(mobile);
request.setUsername(username);
request.setPassword(password);
try {
UserRegisterResponse response = userCoreService.register(request);
//异步化解耦
kafkaTemplate.send("test",response.getUid());
data.setMessage(response.getMsg());
data.setCode(response.getCode());
}catch(Exception e) {
data.setMessage(ResponseEnum.FAILED.getMsg());
data.setCode(ResponseEnum.FAILED.getCode());
}
return data;
}
}