单机的Tomcat应用登录校验:
用户首次登录成功后,服务端会创建一个Session会话,客户端会生成一个sessionid,客户端会把sessionid保存到cookie里,每次请求都携带这个sessionid,服务端通过校验来判断是拦截还是放行
分布式应用中Session共享登录校验:
真实的应用不可能单节点部署,所以就有多个节点登录session共享的问题需要解决。tomcat支持session共享,但是有广播风暴;尤其用户量大的时候,占用资源非常严重
推荐使用Redis来存储token:
服务端使用UUID生成随机64位或者128位token,放入redis中,然后返回给客户端并存储在cookie中,用户每次访问都携带此token,服务端去redis中校验是否有此用户即可
分布式应用登录校验解决方案Json Web Token
Jwt是一个开放标准,他定义了一种用于简洁,自包含的用于通信双方之间以Json对象的形式安全传递信息的方法,可以使用HMAC算法或者RSA的公钥对其签名
简单来说,就是通过一定规范生成token,然后通过解密算法逆向解密,这样就可以获得用户信息
Json Web Token的封装通用方法,JwtTokenUtil
public class JwtTokenUtil {
public static final String TOKEN_HEADER = "Authorization";// token header
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "gkadmin";// 密钥
private static final String ISS = "gkadmin";
private static final String ROLE_CLAIMS = "claims";
private static final long EXPIRATION = 12*3600L;// 3600L;// 时间
//创建token
public static String createToken(String username) {
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS512, SECRET).claim(ROLE_CLAIMS, username)
.setSubject(username).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000)).compact();
return jwt;
}
public static String getusername(String token) {
return getTokenBody(token).getSubject();
}
// 判断token是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
public static Claims getTokenBody(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}
}
新建一个拦截器类,实现HandlerInterceptor接口,注入Bean,UserService
在重写的preHandle方法里获取token并解密,如果解析不到,通过工具类反馈给客户端无权限的提示,如果token过期,提示“重新登录”
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String accesToken = request.getHeader("token");
if(accesToken == null){
accesToken = request.getParameter("token");
}
if(!StringUtil.isEmpty(accesToken)){
Claims claims = JwtTokenUtil.getTokenBody(accesToken);
String username = (String) claims.get("username");
List<UserInfo> info = userservice.findByToken(username);
if (info.size()==0){
sendJsonMessage(response,JsonData.buildError("无权限访问,请重新登录"));
return false;
}
if (JwtTokenUtil.isExpiration(accesToken)){
sendJsonMessage(response,JsonData.buildError("身份已失效,请重新登录"));
return false;
}
return true;
}
}catch (Exception e){}
sendJsonMessage(response, JsonData.buildError("身份有误,请重新登录"));
return false;
}
再新建Config类实现WebMvcConfigurer接口,注入拦截器bean,在addInterceptors里配置拦截路径,调用excludePathPatterns方法放行需要放行的资源
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/pro/**")
.excludePathPatterns("/api/pro/register","/api/pro/login");
WebMvcConfigurer.super.addInterceptors(registry);
}
新建LoginController,实现注册方法
然后实现Login和用来测试用的Update
接下来用Postman来进行测试,发送Json数据到后台,成功响应
测试登录,登录响应成功并返回了token密钥
然后我们测试客户端携带此token,访问update,由于update方法路径我没有配置放行,所以请求会被拦截,在拦截中校验token,判断是否放行
我们模拟黑客拦截获取到token并进行篡改,再访问,看后台如何响应
这是在拦截器里调用了解析token的方法,解析不成功会return一个空,然后根据结果把提示信息返回到前台
//解析token
public static Claims getTokenBody(String token) {
return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
}
大型互联网应用用户人数上亿,如抖音、淘宝、QQ、微信等,给后台数据库带来的瞬间并发和后台登录压力可想而知,所以大型项目都采用前后端分离,服务器分布式集群部署,这样可以把压力分散到各个节点,每个服务器节点只负责处理一部分的功能
腾讯的企业级分布式数据库在处理大数据,数据冗余,数据高并发等方面,提供了较好的解决方案