我们使用一个springboot+Shiro集成项目来了解一下Shiro在登录认证方面是如何运作的。
1、pom依赖添加
<dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-coreartifactId> <version>1.2.2version> dependency> <dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-springartifactId> <version>1.2.2version> dependency> <dependency> <groupId>org.apache.shirogroupId> <artifactId>shiro-ehcacheartifactId> <version>1.2.2version> dependency>
2.数据库用户权限表、实体类、用户权限DAO操作,略略略。
3.拦截器会在下一篇专门说明拦截器配置使用,略。
4.realm配置
在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。
public class MyShiroRealm extends AuthorizingRealm
通过继承公共Realm抽象类,实现认证处理逻辑抽象方法
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("doGetAuthenticationInfo:" + token); // 将AuthenticationToken强转为AuthenticationToken对象 UsernamePasswordToken upToken = (UsernamePasswordToken) token; // 获得从表单传过来的用户名 String username = upToken.getUsername(); // 从数据库查看是否存在用户 UserService userService = new UserService(); // 如果用户不存在,抛此异常 if (!userService.selectUsername(username)) { throw new UnknownAccountException("无此用户名!"); } // 认证的实体信息,可以是username,也可以是用户的实体类对象,这里用的用户名 Object principal = username; // 从数据库中查询的密码 Object credentials = userService.selectPassword(username); // 颜值加密的颜,可以用用户名 ByteSource credentialsSalt = ByteSource.Util.bytes(username); // 当前realm对象的名称,调用分类的getName() String realmName = this.getName(); // 创建SimpleAuthenticationInfo对象,并且把username和password等信息封装到里面 // 用户密码的比对是Shiro帮我们完成的 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName); return info; }
然后在Config配置类中将自定义的realm加载到容器中:
@Configurationpublic class ShiroConfigBean { @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; }}
容器启动时候会把这个Bean扫描进去,后边进行登录验证进行密码验证就会调用这个Bean,怎么调用的呢?往下看。
5.登录代码
@RequestMapping("/login.action") public String login(String username, String password, Map map, HttpSession session) { System.out.println(username + "---" + password); // 获得当前Subject Subject currentUser = SecurityUtils.getSubject(); // 验证用户是否验证,即是否登录 if (!currentUser.isAuthenticated()) { String msg = ""; // 把用户名和密码封装为 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // remembermMe记住密码 token.setRememberMe(true); try { // 执行登录. currentUser.login(token); // 登录成功... return "redirect:/LoginSuccess.action"; } catch (IncorrectCredentialsException e) { msg = "登录密码错误"; System.out.println("登录密码错误!!!" + e); } catch (ExcessiveAttemptsException e) { msg = "登录失败次数过多"; System.out.println("登录失败次数过多!!!" + e); } catch (LockedAccountException e) { msg = "帐号已被锁定"; System.out.println("帐号已被锁定!!!" + e); } catch (DisabledAccountException e) { msg = "帐号已被禁用"; System.out.println("帐号已被禁用!!!" + e); } catch (ExpiredCredentialsException e) { msg = "帐号已过期"; System.out.println("帐号已过期!!!" + e); } catch (UnknownAccountException e) { msg = "帐号不存在"; System.out.println("帐号不存在!!!" + e); } catch (UnauthorizedException e) { msg = "您没有得到相应的授权!"; System.out.println("您没有得到相应的授权!" + e); } catch (Exception e) { System.out.println("出错!!!" + e); } map.put("msg", msg); return "/index"; } // 登录成功,重定向到LoginSuccess.action return "redirect:/LoginSuccess.action"; }
上边一大堆代码,咱们只看三句就可以:
// 获得当前Subject(shiro核心类库中的方法)Subject currentUser = SecurityUtils.getSubject(); // 把用户名和密码封装为 UsernamePasswordToken 对象UsernamePasswordToken token = new UsernamePasswordToken(username, password);// 执行登录.currentUser.login(token);
上面三个方法都是Shiro类库中的方法,直接调用。
用户从登录页将表单中用户名和密码传递过来后,封装成UsernamePasswordToken 对象传入到Shiro的登录入口方法login中,看一下login方法:
进入securityManager安全管理器里边的login方法:
然后一直往下走,找到获取Realms的方法:
进入doSingleRealmAuthentication方法:
从shiro缓存中读取用户信息,如果没有,才从realm中获取信息。
点击这个doGetAuthenticationInfo方法,就到了我们第4步中进行realm配置时实现的抽象方法:
再进入这个实现方法时会出现我们配置的realm:
以上步骤是通过我们自定义的realm根据表单信息查询数据库获取到数据库用户信息和表单信息的一个SimpleAuthenticationInfo 对象:
@param principal the 'primary' principal associated with the specified realm.@param hashedCredentials the hashed credentials that verify the given principal.@param credentialsSalt the salt used when hashing the given hashedCredentials@param realmName the realm from where the principal and credentials were acquired.SimpleAuthenticationInfo info = new SimpleAuthenticationInfo( principal, //认证的实体信息,可以是实体类也可以是用户名 hashedCredentials , //用于跟表单密码进行比对的已存储的密码值 credentialsSalt, //已存储密码加密算法中使用的盐值 realmName //realm的名称);
然后才开始进行密码验证。
6、密码验证
获取到SimpleAuthenticationInfo 对象,开始使用算法进行比较。
assertCredentialsMatch(token, info);
token中保存了表单信息,info中保存了数据库中密码信息。
首先需要获取指定的凭证匹配器,就是使用哪种算法(常见的散列算法如MD5、SHA等,加密算法会在加密章进行讲解)来进行验证,把表单中的密码通过加盐算法进行计算,得出的值与数据库中的值进行比较,相同就验证通过。
可以在realm配置时进行指定:
@Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(1024);// 散列的次数,比如散列两次,相当于md5(md5("")); return hashedCredentialsMatcher; } @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; }
看完之后,明白了吗?没有的话再看一遍!