【Springboot/redis】SpringBoot程序使用redis作为token管理器进行鉴权
【为何需要鉴权】
判断用户在session中是否存在即为鉴权,web程序不需要额外的鉴权器,httpSession即是;
但前后端分离后,前后端没有session进行交互,故需要设计一个类似session的鉴权器。
【鉴权器的设计理念】
用户登录成功后,往鉴权器中放置用户名(或id)和uuid的键值对,并设定超时时间;
用户请求服务时,取他header中的token,去查鉴权器中有否这个token,有则返回真实服务;
用户退出时,删除用户名(或id)和uuid的键值对;
超时时间一到,删除用户名(或id)和uuid的键值对;
【鉴权器的实现选型】
redis里的字符串即键值对模式,且自带键超时时间设置,速度也快,非常适合做鉴权器。
【鉴权器的接口定义】
package com.hy.token; public interface TokenMng { public boolean join(String uname,String token); public boolean release(String uname); public String getToken(String uname); public boolean hasToken(String token); }
【鉴权器的实现】
package com.hy.token; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import java.util.Iterator; import java.util.Set; @Primary @Component public class RedisTokenMng implements TokenMng{ @Override public boolean join(String uname, String token) { try { Jedis jedis = new Jedis("192.168.32.129"); jedis.auth("123456"); jedis.set(uname, token,"NX","EX",30);// 设置键值对并设定超时时间30秒 return true; }catch(Exception ex){ ex.printStackTrace(); return false; } } @Override public boolean release(String uname) { try { Jedis jedis = new Jedis("192.168.32.129"); jedis.auth("123456"); jedis.del(uname); return true; }catch(Exception ex){ ex.printStackTrace(); return false; } } @Override public String getToken(String uname) { try { Jedis jedis = new Jedis("192.168.32.129"); jedis.auth("123456"); return jedis.get(uname); }catch(Exception ex){ ex.printStackTrace(); return null; } } @Override public boolean hasToken(String token) { try { Jedis jedis = new Jedis("192.168.32.129"); jedis.auth("123456"); Set<String> keys = jedis.keys("*"); // 遍历所有键 Iterator<String> it=keys.iterator() ; while(it.hasNext()){ String key = it.next(); String value=jedis.get(key); if(token.equals(value)){ return true; } } return false; }catch(Exception ex){ ex.printStackTrace(); return false; } } }
【用户登录时鉴权器的处理】
package com.hy.ctrl; import com.hy.token.TokenMng; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserRestCtrl { @Autowired private TokenMng tkMng; @GetMapping("/login") public String login(String uname,String pswd){ if("ufo".equals(uname) && "1234".equals(pswd)){ // join token String token=java.util.UUID.randomUUID().toString(); tkMng.join(uname,token); // alert msg String msg=String.format("User:%s logged in,token=%s",uname,token); return msg; }else{ return "Error username/password"; } } public String logout(String uname){ tkMng.release(uname); // alert msg String msg=String.format("User:%s logged out,token cleared",uname); return msg; } }
【拦截器中鉴权器对用户token的检验】
package com.hy; import com.hy.token.TokenMng; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class Interceptor implements HandlerInterceptor { @Autowired private TokenMng tkMng; @Override public boolean preHandle(HttpServletRequest rqst, HttpServletResponse rsps,Object handler) throws Exception{ String rqstPath= rqst.getContextPath(); System.out.println("Request comming,path="+rqstPath); // ordinary settings rsps.setCharacterEncoding("UTF-8"); rsps.setContentType("text/html;charset=utf-8"); rsps.addHeader("Access-Control-Allow-Origin","*"); // token verify String token=rqst.getHeader("token"); if(StringUtils.hasText(token)==false){ rsps.getWriter().print("You have to login to get real response!"); return false; } if(tkMng.hasToken(token)==false){ String msg=String.format("Your token:%s does not exist.",token); rsps.getWriter().print(msg); return false; } return true; } }
【代表真实服务的控制器】
package com.hy.ctrl; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestRestCtrl { @GetMapping(value="/sayhello") public String sayHello(){ return "Hello world!"; } }
【检验】
用户登录错误时:
这时请求sayhello
登录正确时:
url:http://localhost:8080/login?uname=ufo&&pswd=1234
resposne:User:ufo logged in,token=824d08e4-6193-41cb-8361-2dedbd83e54c
把824d08e4-6193-41cb-8361-2dedbd83e54c拷贝到postman的header里,再请求时:
等过了30秒超时时间,再次用postman请求:
【参考文档】:
【源码下载】:
https://files.cnblogs.com/files/heyang78/RedisTokenTest220307am.rar?t=1646625444
END
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2020-02-22 求学生单科流水表中单科最近/最新的考试成绩表的三种方案(结果集鉴别篇)
2020-02-22 更新一张一千六百万大表字段值 附加一段文本和设置统一文本两方案耗时的比较
2020-02-22 『转载-保持学习的空杯心态』工作中如何做好技术积累