这两天接手了下师兄的项目,要给系统加个日志管理模块,其中需要记录登录功能的日志,那么首先要知道系统的登录是在哪里实现验证的。
该系统把所有登录验证还有权限控制的工作都交给了shiro。
这篇文章就先简单记录下这两天看的关于shiro登录验证的小总结。
(本文是看了一天代码和博客总结出的大概理解,有点模糊还可能不一定对……有大佬知道哪里错的话希望能评论指出下哈哈哈)
主要就一直在解决几个问题:
1. 怎么验证身份?
首先理解几个概念,token是用户提交的东西,一般有两部分Principal(账号或者用户名)和Credentials(密码或者验证的东西)。
然后Subject很重要,可以看作是验证用户或者用户的一个抽象。
然后有个AuthenticationInfo,这个可以理解成是正确的用户验证信息的一个抽象吧。验证主要就是重写一个方法:
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取用户的输入的账号. String name = (String)token.getPrincipal(); // System.out.println("token---------++++++++---------->"+(String)token.getPrincipal()+(String)token.getCredentials()); //通过name从数据库中查找 User对象,如果找到,没找到. //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 Mapmap= new HashMap (); map.put("uName", name); User user = securityShiroService.selectByUserInfo(map); if(user == null){ return null; } //System.out.println("token---------++++++++---------->"+user.getuName()+user.getId()); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user, //用户名 user.getuPass(), //密码 ByteSource.Util.bytes(user.getuName()+"jintu"),//salt=username+salt getName() //realm name ); // 当验证都通过后,把用户信息放在session里 Session session = SecurityUtils.getSubject().getSession(); session.setAttribute("userInfo", user); return authenticationInfo; }
然后看这里的代码我很不解——这里就根据用户提交的token的name也就用户名,然后去数据库里面找到用户的具体信息,然后就把信息保存在session中???
exm??不用对比密码对不对的吗??
然后看了这篇博客:https://www.jianshu.com/p/1a371f3cec27
他说 查看shiro源码才知道:
大概就是首先在进入自定义Realm之前,会经过AuthenticatingRealm这个类的getAuthenticationInfo方法,在这个方法里面会用到你的authenticationInfo然后和token进行比较,然后判断是不是验证成功,也就是说,你在这个重写的realm方法中给出正确的用户信息后,登录验证这个东西交给shiro去做了。
嗯应该这样理解吧。
2. 师兄代码里面并没有currentUser.login(token)???
我看网上的demo基本都是在控制器里面,拦截登录请求,然后调用这个login(token)语句,怎么项目里面找不到呢????
这个我看了这篇文章大概理解了下:https://www.jianshu.com/p/0662cf366161
首先,shiro会有个过滤器filter,你可以配置哪些资源需要拦截然后通过验证才能访问,哪些资源不用验证可以直接访问。
一般会有几个属性设置:
filter里面是可以放行的资源;然后logout显然就和退出登录有关,大概就是退出登录的请求吧;authc=/**意味着所有资源都要经过验证除了前面放行的;loginUrl就是登录请求的路径咯,然后两个很显然了,shiro判断你登录成功后跳转的界面还有最后一个应该是没有权限后显示的界面。
根据上面那个博客,大概可以猜到,系统没有开放login界面,因此我们访问login界面也好其他系统界面也好,首先就会要验证,要验证shiro就会启动他那套验证,内部调用我们重写的doGetAuthenticationInfo然后完成验证工作。
所以大概梳理了下系统可能的登录验证逻辑
首先是网关处controller的代码:
@Controller public class HomeController { private final static Logger logger = Logger.getLogger(HomeController.class); @RequestMapping({ "/", "/index" }) public String index(ModelMap model,HttpServletResponse resp) { Session session = SecurityUtils.getSubject().getSession(); User user = (User) session.getAttribute("userInfo"); Cookie cookie = new Cookie("sssid", user.getId()); cookie.setMaxAge(7200); resp.addCookie(cookie); Cookie cookies = new Cookie("zytypes", String.valueOf(user.getuType())); cookies.setMaxAge(7200); resp.addCookie(cookies); return "redirect:/gate-ui/index"; } @RequestMapping("/login") //public ModelAndView login(HttpServletRequest request,Model model) throws Exception { public String login( HttpServletRequest request,Model model) throws Exception { //进入登录页面时,清空数据 Subject subject = SecurityUtils.getSubject(); subject.logout(); // 登录失败从request中获取shiro处理的异常信息,shiroLoginFailure:就是shiro异常类的全类名. String exception = (String) request.getAttribute("shiroLoginFailure"); logger.info("登录异常 -- > " + exception); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { logger.info("UnknownAccountException -- > 账号不存在!"); msg = "账号不存在!"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { logger.info("IncorrectCredentialsException -- > 密码不正确!"); msg = "密码不正确!"; } else if ("kaptchaValidateFailed".equals(exception)) { logger.info("kaptchaValidateFailed -- > 验证码错误!"); msg = "验证码错误!"; } else { msg = "else >> " + exception; logger.info("else -- >" + exception); } } model.addAttribute("msg",msg); // 此方法不处理登录成功,由shiro进行处理 // return new ModelAndView("/login"); return "login"; } @RequestMapping("/403") public String unauthorizedRole() { return "403"; } }
这个代码我看的真的很懵一开始……为什么login那个controller完全没有登录业务?????而是在处理登录失败的信息????
现在大概梳理下:首先,我们输入url的时候不管是不是login的请求,就不管是不是/login,都是会被shiro的filter拦截,然后就要验证嘛。
因为我们设定了登录地址:
所以我猜应该shiro一旦需要验证你身份了,就会跳到这个界面。所以应该出现这个界面的时候,还没经过login的那个controller。
然后你就提交表单请求,有token然后shiro就会根据你的doGetAuthenticationInfo和token对比完成验证,我们不是设置了shiro的successUrl嘛:
那登录失败呢???登录失败应该就是还有验证信息的意思吧,所以应该跳转到/login,所有我们看到homeController里面/login的controller,它第一步是logOut(),这个应该是清除session中存好的用户信息,然后将之前登陆失败的原因从request中拿出来,这个失败的原因应该是shiro在登录失败后会放在request中。
然后将登录失败的信息通过SPringMVC给到前端显示。
为什么要第一步logOut???因为师兄doGetAuthenticationInfo方法的实现里面,根据token的用户名拿到用户信息后,不管三七二十一都会吧信息存到shiro的session中,登录成功的话就session里面有用户信息,登录失败又跳到/login就肯定要先logout()清楚session呗。
附:关于Shiro的session
项目用的是SpringCloud微服务的框架,登录的验证代码是在gate工程中,但在admin工程中我看到一样可以使用shiro的session???、
不应该是不一样的tomcat吗,怎么会一样的session???
后面百度了下,这个session和http中的session是不一样的:
shiro的session好像是基于Java对象的,是和之前理解的不一样。
如上图所示,shiro自己定义了一个新的Session接口,用于统一操作接口,并通过SessionManager实现Session管理。
其中的3个实现类HttpServletSession,SimpleSession和StoppingAwareProxiedSession是我们经常需要打交道的。
然后shiro有个sessionDao的东西,就是可以吧session持久化,然后项目里面看到相关的代码实现好像用了redis,也就是说系统应该是吧session存到redis里了。
还有个不懂的地方
我们来看看这个登录界面login:
DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>广东省中药资源动态监测系统title>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
head>
<body>
<div class="htmleaf-container">
<div class="wrapper">
<div class="container">
<h1>广东省中药资源动态监测系统h1>
<form class="form" action="" method="post">
<input type="text" name="username" id="username" autofocus placeholder="Username">
<input type="password" name="password" id="password" placeholder="Password">
<button type="submit" id="login-button">登陆button>
<input type="checkbox" type="hidden" name="rememberMe" title="记住我" checked="">
<p id="msg" th:text="${msg}">p>
form>
div>
<ul class="bg-bubbles">
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
<li>li>
ul>
div>
div>
<script src="js/jquery-2.1.1.min.js" type="text/javascript">script>
body>
html>
可以看到,这个页面除了jQuery的js包,没有引入其他的js文件了。
那登录请求是怎么发给服务器的????
而且,暂时在代码中没有找到任何解析登录表单然后转换成shiro相关token的代码……
所以两个疑问:
1. 登录请求怎么发到后端的?
2. 用户请求怎么转换成token的???
我猜测就是指定了shiro登录的界面后,就会将登录界面表单的东西自动封装成token,但网上没能找到相关资料………………
代码也看不到相关配置……和代码,希望哪个大佬知道为什么可以解答我哈哈哈谢谢。
最后还找到了个思路和这个项目差不多的登录验证博客:https://blog.csdn.net/caiqiandu/article/details/88973995
评论了作者我的疑问但到目前还没回复哈哈
写的挺详细的但仍然没有回答我不明白的地方&………………
参考博客:
https://www.jianshu.com/p/1a371f3cec27——《应该在自定义Realm的doGetAuthenticationInfo方法中做什么》——告诉我大概怎这个方法有什么用,一般怎么重写
https://www.jianshu.com/p/0662cf366161——《shiro 登录验证的一个问题》——让我大概知道为什么可以不用调用user.login(token)
两个关于shirosession的:
https://blog.csdn.net/changudeng1992/article/details/81914628——《Shiro笔记四(会话管理):SessionDao》
https://blog.csdn.net/bitree1/article/details/50498970——《org.apache.shiro.SecurityUtils.getSubject().getSession()》
一个常规的登录授权demo——https://blog.csdn.net/qq_33556185/article/details/51579680——《详解登录认证及授权--Shiro系列(一)》
一个和项目基本思路一样的登录验证例子——https://blog.csdn.net/caiqiandu/article/details/88973995——《shiro登陆注册拦截器》