- 用户向服务器发送用户名和密码。
- 验证服务器后,相关数据(如用户角色,登录时间等)将保存在当前会话中。
- 服务器向用户返回session_id,session信息都会写入到用户的Cookie。
- 用户的每个后续请求都将通过在Cookie中取出session_id传给服务器。
- 服务器收到session_id并对比之前保存的数据,确认用户的身份。
随着业务扩大,业务分开部署,存储在session中时无法在另一台服务器中判断登录。
缺点
:数据冗余,增加服务器的负担
优点
: 用户身份信息独立管理,更好的分布式管理; 可以自己扩展安全策略
缺点
:认证服务器访问压力较大
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
server.port=9000
spring.application.name=spring-session
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
@EnableRedisHttpSession
@SpringBootApplication
public class SsoApplication {
public static void main(String[] args) {
SpringApplication.run(SsoApplication.class, args);
}
}
@GetMapping("put")
public String putSession(HttpSession session){
session.setAttribute("user","登录信息");
return "ok";
}
@GetMapping("get")
public String getSession(HttpSession session){
Object user = session.getAttribute("user");
return "ok"+user;
}
#keys *
1) "spring:session:expirations:1602219480000"
2) "spring:session:sessions:expires:d131e843-75f6-4c9c-97ab-a0a884a95a28"
3) "spring:session:sessions:d131e843-75f6-4c9c-97ab-a0a884a95a28"
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<Object,Object> redisTemplate =new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//Jackson序列化value
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jsonRedisSerializer.setObjectMapper(mapper);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory){
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
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();
}
}
优点
:无状态: token无状态,session有状态的;基于标准化: 可以采用标准化的 JSON Web Token (JWT)
缺点
:占用带宽;无法在服务器端销毁
alg属性
表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性
表示令牌的类型,JWT令牌统一写为JWT。- 最后,使用
Base64 URL算法
将JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
请注意
,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。JSON对象也使用Base64 URL算法转换为字符串保存。
默认提供字段:
#iss:发行人
#exp:到期时间
#sub:主题
#aud:用户
#nbf:在此之前不可用
#iat:发布时间
#jti:JWT ID用于标识该JWT
自定义字段:
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
- 首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。
- 然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
存储在客户端,不占用服务端的内存资源
JWT默认不加密,但可以加密。生成原始令牌后,可以再次对其进行加密。
当JWT未加密时,一些私密数据无法通过JWT传输。
JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,
一旦JWT签发,在有效期内将会一直有效。
JWT本身包含认证信息,token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。
为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。
public class JwtUtils {
//过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
//服务器密钥
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
//生成token
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
//头信息
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
//默认主体
.setSubject("user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
//自定义主体
.claim("id", id)
.claim("nickname", nickname)
//签名hash
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
// 判断token是否存在与有效
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
// 判断token是否存在与有效
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
//根据token获取会员id
public static String getIdByReq(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).
parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
//根据token获取会员id
public static String getIdByJwt(String token) {
if(StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
@GetMapping("token/put")
public String putSession(){
return JwtUtils.getJwtToken("1", "wdd");
}
//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
//eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjAyMjIyNzQ0LCJleHAiOjE2MDIzMDkxNDQsImlkIjoiMSIsIm5pY2tuYW1lIjoid2RkIn0.
//LJyXdXttVyd1xI91QetkcWKd3WhuNiaJs3aLWy8RqcY
@GetMapping("token/get")
public String getSession(@RequestParam("token")String token){
return JwtUtils.getIdByJwt(token);
}