单点登录
导读:
http是无状态的协议,web应用采取的是BS架构。浏览器每一次请求服务器如果都独立处理无法区分每个用户的区别。
会话机制:
浏览器第一次请求服务器以后,服务器会创建一个会话,并将会话的id作为相应的一部分发送给浏览器。浏览器后续的请求都会带上此id。以此区分用户。
我们访问tomcat服务器是,浏览器会看到一个名为“JESSIONID”的cookie。这就是tomcat会话机制维护的会话id。
假如我们只有一个服务器时的登录
此时,我们只需要在Session中设置一个登录状态即可
HttpSession session = request.getSession();
session.setAttribute("isLogin", true);
每次我们访问时只需判断session中的isLogin为true即可。
多个服务器时的登录
首先我们需要引入一个概念,负载均衡与集群:
什么是集群(简单理解)?
举个例子:有2+N个tomcat都有同一个功能相同的web项目在提供对外的服务,只是它们的端口不一样。
什么是负载均衡(简单理解)?
接上集群的例子,假如有N+个人都在同时对这个服务发送请求,这N+个请求如果同时是同一个tomcat处理,tomcat的处理能力是有极限的,就会出现个别人等了特别久才能反映过来。或者tomcat直接卡死的可能。所以引入负载均衡的概念,负载均衡可以根据每个tomcat服务器的能力,把请求平均给每一个服务器,或者给某一个服务器比较多。
出现登录问题(分布式情况也一样)
在简单理解负载均衡与集群后就会发现,如果这样,一个用户第一次请求可能会在tomcat1,第二次请求可能就会出现在tomcat2上。这个用户只在tomcat1上登陆过,session也只是tomcat1的设置,访问tomcat2时这个用户就是未登录状态。
怎么解决这一个问题?
大家可以查阅网上或者查看《深入分析javaweb技术内幕》275页,都有对登录登录问题的解决思路与方案。
这里我引入一个redis用于存取用户的登录状态,用户每次需要认证是否登录的请求,都可以向查询redis是否存在用户状态的KEY-VALUE。
用户登录以后我们可以设置一个key为token的cookie返回给浏览器(cookie注意跨域问题,端口名不同也为跨域),我们把这个cookie称为令牌。用户每次访问时,需要携带令牌,服务器可以根据令牌判断用户是否已经为登录状态。
另一种解决方式
<!--jwt tokens生成工具-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
还有一种解决方式,采用jwt生成一个token放在浏览器端,使用cookie存储。用户每次访问时带入token,jwt工具能够设置过期时间,每次访问时,用jwt工具解析token能查看token是否过期,token还可以存储一些我们的自定义信息,通过自己设置的自定义签名和算法加密,我采用内部HS256算法,BASE64编码解码加密。这样的token不存在redis中也能判断其是否登录了,还可以存储一些数据。
这样的不存状态于服务端的token问题也有很多,比如token盗用。所以谨慎使用,或者作为登录逻辑的一部分配合使用。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@ConfigurationProperties("jwt.config")
@Component
public class JwtUtil {
private String key ;
private long ttl ;//一个小时
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
/**
* 生成JWT
*
* @param id
* @param subject
* @return
*/
public String createJWT(String id, String subject, String roles) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id)
.setSubject(subject)
.setIssuedAt(now)//创建时间
.signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtStr
* @return
*/
public Claims parseJWT(String jwtStr){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}