1.导入依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--像Md5加密呀-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--jedis,远程操作redis-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.在properties里配置redis相关配置
#Redis服务器地址
spring.redis.host=123.57.64.216
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
#redis的密码
spring.redis.password=xxx
3.配置redis的配置类config
package com.mszlu.blog.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching//开启缓存
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
4.JWT的实现
4.1什么是jwt
JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案,JWT的精髓在于:“去中心化”,数据是保存在客户端的。
jwt可以生成一个加密token,作为用户登陆的令牌,当用户登陆成功后,发放给客户端.
请求需要登陆的资源和接口时,将token携带,后端验证token是否合法
流程上是这样的:
1、用户使用用户名密码来请求服务器
2、服务器进行验证用户的信息
3、服务器通过验证发送给用户一个token
4、客户端存储token,并在每次请求时附送上这个token值
5、服务端验证token值,并返回数据
4.2jwt有什么好处
基于session认证所显露的问题
Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利,用户的信息是存在在客户端的。
4.3创建jwtUtils工具类
抽取出token与检查token的功能
package com.mszlu.blog.utils;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtils {
private static final String jwtToken = "12345Lum!@#$%"; //密钥
public static String createToken(Long userId){
Map<String, Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,jwtToken) //签发算法,密钥为jwtToken
.setClaims(claims)//body数据,唯一,自行设置
.setIssuedAt(new Date()) //设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 *1000)); //一天都有效时间
return jwtBuilder.compact();
}
public static Map<String,Object> checkToken(String token){
try {
Jwt parser = Jwts.parser().setSigningKey(jwtToken).parse(token); //解析jwtToken
return (Map<String, Object>) parser.getBody();
}catch ( Exception e) {
e.printStackTrace();
}
return null;
}
/*测试jwt*/
public static void main(String[] args) {
String token = JWTUtils.createToken((long) 1234);
System.out.println(token);
Map<String, Object> map = JWTUtils.checkToken(token);
System.out.println(map.get("userId"));
}
}
5.Service层实现对jwt+redis+md5的使用
package com.lum.blog.service.Impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.lum.blog.dao.pojo.SysUser;
import com.lum.blog.service.LoginService;
import com.lum.blog.service.SysUserService;
import com.lum.blog.utils.JWTUtils;
import com.lum.blog.vo.ErrorCode;
import com.lum.blog.vo.Result;
import com.lum.blog.vo.params.LoginParam;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private SysUserService sysUserService;//需要用到用户表
@Autowired
private RedisTemplate<String,String> redisTemplate;
private static final String salt="lum!@#";
@Override
public Result login(LoginParam loginParam) {
/**
* 1.检查参数是否合法
* 2.根据用户名和密码区user表中查询是否存在
* 3.如果不存在 登陆失败
* 4.如果存在,使用jwt 生成 token 返回给前端
* 5.token放入redis中,redis映射token和user信息,设置过期时间,先认证token是否合法,再认证redis中是否存在
*/
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account)||StringUtils.isBlank(password)) {
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
password = DigestUtils.md5Hex(password + salt); //密码加盐
SysUser sysUser = sysUserService.findUser(account,password);
if (sysUser == null) {
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(), ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS); //过期时间
return Result.success(token);
}
}
redisTemplate存key,value键值对时,value存的是将当前用户转存成的json格式。
因此获取用户信息时,将token带上,去redis里面取用户的json格式,然后将json格式转为用户格式。
SysUser sysUser = JSON.parseObject(userJson, SysUser.class);