大致过程
用前台的用户名、明文密码等信息创建验证的token令牌对象
执行subject.login(token);登陆时,会跳转到自定义的Realm的认证方法,一般在认证方法里面查询数据库,获取数据库对应的user信息(包括加密后的密码),将user信息封装到info对象里
Shiro内部验证的时候,会将token的明文密码加密后,与info里user的密码(也就是数据库中用户密码)进行比较,结果一致则返回true,登陆成功
执行过程
1 .前台页面登陆后,执行登陆验证方法subject.login(token);实际调用的是securityManager.login(this, token)
1-1.securityManager.login(this, token)方法里调用 authenticate(token);方法获取需要匹配的AuthenticationInfo info对象
1-2.authenticate方法里调用doAuthenticate(token);来获取需要匹配的AuthenticationInfo info对象
1-3.在doAuthenticate方法里面,会获取自定义的Realm,本例只设置一个Realm,所以跳转单Realm的认证方法doSingleRealmAuthentication
1-4.在doSingleRealmAuthentication方法里,会跳转getAuthenticationInfo方法:
1-5.在getAuthenticationInfo方法中,先从缓存中获取匹配的AuthenticationInfo info对象,本例没有设置缓存,所以往下通过自定义Realm的doGetAuthenticationInfo(token)方法获取info对象:
1-6.往下会跳转到自定义Realm重写的doGetAuthenticationInfo方法里。方法返回SimpleAuthenticationInfo的info对象,SimpleAuthenticationInfo(,,,,)有四个参数:
1).第一个Object principal,可以是用户名,也可以是user对象
2).第二个Object hashedCredentials,需要认证的密码,前台输入的密码会和此密码匹配
3).第三个ByteSource credentialsSalt,加密认证是的salt
4).第四个String realmName,就是realm的名字,即shiroConfig配置的relam
1-7.执行完自定义Realm获得info对象,info对象包含了数据库的密码(加密过的密码),然后执行assertCredentialsMatch(token, info);进行密码验证:
1-8.进入密码验证方法assertCredentialsMatch先获取密码比较器,然后再调用密码比较方法doCredentialsMatch:
1-9.在doCredentialsMatch方法里,hashProvidedCredentials(token, info);方法主要是将前台的明文密码通过加密算法SimpleHash后,获取加密后的密码。
1-10.前台明文密码加密后密码tokenHashedCredentials与自定义Realm从数据库中获取的加密密码进行匹配:
1-11.匹配成功subject.login(token);返回true;
2.登陆成功重定向到userList.do的控制器,进行成功页面跳转:
2-1.userList页面中使用了shiro:hasPermission标签做权限控制,页面有三个shiro:hasPermission权限标签,则会执行三次自定义Realm里的授权方法doGetAuthorizationInfo:
2-2.具体源码没怎么看,但主要和验证差不多,都是先从缓存取授权的info对象,没有再跑自定义Realm的授权方法去获取info对象。我想大概就是shiro:hasPermission标签的值和查询数据库获取的权限列表perms进行匹配,不涉及加密,匹配成功则显示标签内容:
代码示例
代码地址:
https://github.com/OooooOz/SpringBoot-Shiro
前台登陆页面:
<body>
<h2>登录页面</h2>
<form action="login.do" method="post" name="loginForm">
用户名称:<input type="text" name="username" value=""><br>
用户密码:<input type="password" name="password" value=""><br>
<input type="submit" name="log" value="登录"/>
<input type="checkbox" name="rememberMe" />记住我<br/>
</form>
<a href="/unlockAccount.do">解锁admin用户</a></button>
<div th:text="${msg}"></div>
</body>
控制器Controller:
@Controller
public class LoginController {
@Resource
IUserService userService;
@RequestMapping("/toLogin.do")
public String toLogin() {
return "login";
}
@RequestMapping("/login.do")
public String login(String username, String password,boolean rememberMe, Model model) {
// 判断用户名密码是否为空
if (username != null && !"".equals(username) && password != null && !"".equals(password)) {
// 1.获取subject
Subject subject = SecurityUtils.getSubject();
// 2.创建验证用的令牌对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe);
try {
//3.登陆认证
subject.login(token);
boolean flag = subject.isAuthenticated(); //判断是否通过认证
//4.认证结果处理
if(flag){
System.out.println("登录成功!");
User user = (User)subject.getPrincipal();
model.addAttribute("user",user);
return "redirect:userList.do";
}else{
model.addAttribute("msg","登录认证失败!");
return "login";
}
} catch (Exception e) {
model.addAttribute("msg","登录认证失败!");
if (e instanceof LockedAccountException) {
model.addAttribute("msg","失败次数过多,用户已锁定,五分钟后再试");
}
}
}
return "login";
}
@RequestMapping("/userList.do")
//@ResponseBody
public String userList(Model model){
//查询所有的用户信息并且显示到页面上
Subject subject = SecurityUtils.getSubject();
System.out.println("==============是否通过认证:"+subject.isAuthenticated());
System.out.println("==============是否记住我:"+subject.isRemembered());
List<User> list = userService.findAll();
model.addAttribute("userList", list);
return "userList";
}
@RequestMapping("/unFunc.do")
public String noFunc(){
return "unFunc";
}
@RequestMapping("/logout.do")
public String logout(){
//清除session
return "login";
}
}
ShiroConfig的过滤器:
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证
shiroFilterFactoryBean.setLoginUrl("/toLogin.do");
//通过unauthorizedUrl指定没有权限操作时跳转页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unFunc.do");
//-----------------------------过虑器链定义------------------------------//
LinkedHashMap<String, String> perms = new LinkedHashMap<>();
perms.put("/login.do","anon");
//其他资源都需要认证 authc 表示需要认证才能进行访问 user表示配置记住我或认证通过可以访问的地址
perms.put("/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(perms); //把权限过滤map设置shiroFilterFactoryBean
return shiroFilterFactoryBean;
}
自定义Realm:
/**
* 认证方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证方法。。。。");
String username = token.getPrincipal().toString();
System.out.println("username:" + username);
//需要通过用户名查询用户密码
User user = userService.findByUsername(username);
//把user对象封装的AuthenticationInfo中返回
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()),"shiroRealm");
return authenticationInfo;
}
登陆成功跳转页面:
<table>
<caption>
<div shiro:hasPermission="function:add"><a href="/toAdd.do">添加</a></div>
<div shiro:hasPermission="function:update"><a href="/toUpdate.do">修改</a></div>
<div shiro:hasPermission="function:delete"><a href="/toDelete.do">删除</a></div>
</caption>
<tr>
<td>用户ID</td><td>用户名称</td><td>电话</td><td>email</td><td>状态</td><td>创建时间</td><td>备注</td>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${user.userId}"></td>
<td th:text="${user.userName}"></td>
<td th:text="${user.phone}"></td>
<td th:text="${user.email}"></td>
<td th:if="${user.status == 1 ?'正常':'异常'}"></td>
<td th:text="${user.createTime}"></td>
<td th:text="${user.note}"></td>
</tr>
</table>