文件夹结构图.png
- 第四课主要是基于 redis 实现单点登录。早期互联网应用由于用户少、访问量低,多个系统的代码可以部署在一台机器中,所以 session 可以直接共享。但是随着互联网的发展,现如今大型网站均是分布式系统,session 无法再像以前那样共享。
- 单点登录就是在这样多系统共存的环境下,实现一次登录,多次访问不同的互信的系统,单点登录可以简化用户操作,提高友好性。单点登录可以有多种实现方式,本实验采用 token+ redis 方式实现。在用户第一次访问后端接口时直接跳到登录页面,使用账号、密码验证用户真实性,验证成功后,后端返回给用户随机产生的 token(存储在 redis 中)。随后,用户访问后端的接口都要携带这个 token,使用 token 来验证用户的有效性。用户注销时,token 从redis 中删除。
RedisConfig.java
// 标识configuration
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
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);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
WebConfig.java
// 标识configuration
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
// 自动注入拦截器
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor).addPathPatterns("/user/**").excludePathPatterns("/user/login");
super.addInterceptors(registry);
}
}
UserController.java
// 标记controller,返回json数据,URL前缀:/user
@RestController
@RequestMapping("user")
public class UserController {
// 自动注入service
@Autowired
private UserService userService;
/**
* 登录
* @param user
* @return
*/
@PostMapping("login")
public ResultObject login(@RequestBody User user) {
if (user == null) {
return new ResultObject(-1, "param empty", null);
}
ResultObject resultObject = userService.login(user);
return resultObject;
}
/**
* 测试单点登录
* @param
* @return
*/
@GetMapping("test")
public ResultObject test() {
ResultObject resultObject = new ResultObject(1, "test success", null);
return resultObject;
}
/**
* 注销
* @param token
* @return
*/
@GetMapping("logout")
public ResultObject logout(@RequestParam String token) {
ResultObject resultObject = userService.logout(token);
return resultObject;
}
}
UserMapper.java
@Mapper
public interface UserMapper {
/**
* 查询用户
* @param user
* @return
*/
@Select("select id, name, gender, age, password from user " +
" where name = #{name} and password = #{password}")
User select(User user);
}
ResultObject.java
public class ResultObject {
// 后台状态
private int code;
// 相关消息
private String msg;
// 后台返回数据
private Object result;
// 构造函数
public ResultObject() {
super();
// TODO Auto-generated constructor stub
}
// 构造函数
public ResultObject(int code, String msg, Object result) {
super();
this.code = code;
this.msg = msg;
this.result = result;
}
// 属性setter、getter方法
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
// 重写toString方法
@Override
public String toString() {
return "ResultObject [code=" + code + ", msg=" + msg + ", result=" + result + "]";
}
}
User.java
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
// 主键自增id
private int id;
// 姓名
private String name;
// 性别
private int gender;
// 年龄
private int age;
// 密码
private String password;
// 构造函数
public User() {
super();
// TODO Auto-generated constructor stub
}
// 构造函数
public User(String name, int gender, int age, String password) {
super();
this.name = name;
this.gender = gender;
this.age = age;
this.password = password;
}
// 属性setter、getter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 重写toString方法
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", password=" + password + "]";
}
}
LoginInterceptor.java
@Component
public class LoginInterceptor implements HandlerInterceptor{
private static final Log logger = LogFactory.getLog(LoginInterceptor.class);
@Autowired
private RedisUtil redisUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
try {
// 校验用户是否已经登录, 如果登录过, 将之前用户踢掉, 同时更新缓存中用户信息
// 获取用户id
// step 1 检查请求是否携带token
String token = request.getParameter("token");
if (token == null) {
render(response, "lack token");
return false;
}
// step 2 检查token是否合法
User user = (User)redisUtil.get(token);
if (user != null) {
Object preToken = redisUtil.get(Integer.toString(user.getId()));
// step 3 检查同一账号是否被其他用户登录
if (!token.equals(preToken)) {
render(response, "another user login");
return false;
// step 4 至此登录成功,查看token过期时间是否小于1分钟, 返回值单位毫秒,故 * 1000比较
} else {
Long expire = redisUtil.getExpire(token);
if (expire < 60 * 1000) {
redisUtil.expire(token, 60 * 30);
}
}
} else {
render(response, "token error");
return false;
}
} catch (Exception e) {
logger.info("preHandle=" + e);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
public void render(HttpServletResponse response, String msg) throws IOException {
ResultObject resultObject = new ResultObject(-1, msg, null);
JSONObject object = new JSONObject(resultObject);
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
out.write(object.toString().getBytes("UTF-8"));
out.flush();
out.close();
}
}
UserService.java
@Service
public class UserService {
// 自动注入mapper
@Autowired
private UserMapper userMapper;
// 自动注入redisUtil
@Autowired
private RedisUtil redisUtil;
/**
* 登录
* @param user
* @return
*/
public ResultObject login(User user) {
ResultObject resultObject = new ResultObject();
// 查询用户账号、密码是否正确
User fullUser = userMapper.select(user);
// 不正确直接返回
if (fullUser == null) {
resultObject.setCode(-1);
resultObject.setMsg("name or password error");
return resultObject;
}
// 生成随机token
String token = UUID.randomUUID().toString();
int interval = 60 * 5;
// 将用户的token作为key, 用户信息作为value
redisUtil.set(token, fullUser, interval);
// 防止同一个账号多人登录
redisUtil.set(Integer.toString(fullUser.getId()), token, interval);
resultObject.setCode(1);
resultObject.setMsg("login success");
Map<String, Object> map = new HashMap<>();
map.put("token", token);
fullUser.setPassword(null);
map.put("user", fullUser);
resultObject.setResult(map);
return resultObject;
}
/**
* 注销
* @param token
* @return
*/
public ResultObject logout(String token) {
User user = (User)redisUtil.get(token);
redisUtil.del(Integer.toString(user.getId()));
redisUtil.del(token);
return new ResultObject(1, "success", null);
}
}
RedisUtil.java
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置过期时间
* @param key
* @param time
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取key的过期时间
* @param key
* @return
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 检测key是否存在
* @param key
* @return
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除key
* @param key
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 获取key
* @param key
* @return
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 设置key
* @param key
* @param value
* @return
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 设置带有过期时间的key,默认永不过期
* @param key
* @param value
* @param time
* @return
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* value + delta
* @param key
* @param delta
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("delta must > 0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* value - delta
* @param key
* @param delta
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("delta must > 0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
}
文集推荐:
Java基础方法集1
Python基础知识完整版
Spring Boot学习笔记
Linux指令进阶
Java高并发编程
SpringMVC基础知识进阶
Mysql基础知识完整版
健康管理系统学习花絮(学习记录)
Node.js基础知识(随手笔记)
MongoDB基础知识
Dubbo学习笔记
Vue学习笔记(随手笔记)
声明:发表此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本我们(QQ:981086665;邮箱:981086665@qq.com)联系联系,我们将及时更正、删除,谢谢。
网友评论