realm是一个shiro提供的接口用中文翻译是'领域',当我们的表单post请求进行登录验证时,就会到自定义(之前还会有一个Filter)的这个realm,在这个类 我们查询出用户的信息(包括权限信息),然后交给credentialsMatcher进行验证(凭证匹配器,下一篇会讲到)
package com.hzq.system.service.realm;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.hzq.system.entity.ShiroUser;
import com.hzq.system.entity.SysPermission;
import com.hzq.system.entity.SysRole;
import com.hzq.system.entity.SysUser;
import com.hzq.system.service.PermissionService;
import com.hzq.system.service.SysRoleService;
import com.hzq.system.service.SysUserService;
import com.hzq.system.util.PermissionUtil;
@Service
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
public void setName(String name) {
super.setName("shiroRealm");
}
/**
* 认证回调函数,登录时调用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
// token是用户输入的用户名和密码
// 从token中取出用户名
String username = (String) authcToken.getPrincipal();
SysUser sysUser=userService.findUserByUsername(username);
if(sysUser==null){
return null;
}
if("1".equals(sysUser.getState())){
throw new LockedAccountException();
}
ShiroUser shiroUser=new ShiroUser(sysUser.getId(),sysUser.getUsername(), sysUser.getName(),sysUser.getPhone());
List<SysRole> roles=roleService.getRolesByUserId(sysUser.getId());
shiroUser.setRoles(roles);
List<SysPermission> permissions=new ArrayList<SysPermission>();
if(roles.size()!=0){
permissions=permissionService.getPermissionBySysRoles(roles);
}
shiroUser.setMenus(PermissionUtil.getMenus(permissions));
shiroUser.setPermissions(PermissionUtil.getPermissions(permissions));
String salt=sysUser.getSalt();
return new SimpleAuthenticationInfo(shiroUser,sysUser.getPassword(),ByteSource.Util.bytes(salt),getName());
}
/**
* 授权查询回调函数, 进行授权但缓存中无用户的授权信息时调用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// for (SysRole role : shiroUser.getRoles()) {
// //基于Role的权限信息(与代码绑定太多)
// info.addRole(role.getAuthorityname());
// }
for (SysPermission permission : shiroUser.getPermissions()) {
//基于Permission的权限信息
String auth=permission.getAuth();
if(auth!=null){
info.addStringPermission(auth.trim());
}
}
return info;
};
}
(1): doGetAuthenticationInfo
shiro是利用过滤器进行登录拦截的,当我们没登录时访问任何页面,都会跳到配置文件定义的ShiroFilter里面的loginUrl,然后在进行表单提交时,跳转到这里进行验证(其实之前还会有一个登录表单的过滤器,下下篇文章会介绍),若验证失败(这个方法返回null 或者抛出异常)则会跳到LoginUrl对应的Action里面(这个Action并不处理登录成功,切记只有登录失败才会跳转)
这个主要方法是查找用户名密码信息(在此我也查出了用户所具有的权限信息,这样下面需要认证时就不用再从数据库查了)
详细介绍:
参数authcToken 储存了用户表单提交过来的用户名和密码(也包括了是否记住密码,我这边没有用)
返回值 SimpleAuthenticationInfo
第一个参数是Object,为了储存更多的信息,我自定义了一个ShiroUser来存储 在方法(2)中通过
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal() 可以得到ShiroUser
sysUser.getState()是bean的一个字段,若为1则用户禁用,此时抛出异常(这个异常是shiro内部自定义的)前面有提到,抛出异常则会跳转到LoginUrl对应的Action,看下那个Action
/**
* 此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value="login",method=RequestMethod.POST)
public String index(HttpServletRequest request) throws Exception{
//如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根据shiro返回的异常类路径判断,抛出指定异常信息
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
// request.setAttribute(IndexErrorKey, "账号不存在");
//避免被恶意扫描账号库
request.setAttribute(IndexErrorKey, "用户名/密码错误");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
request.setAttribute(IndexErrorKey, "用户名/密码错误");
} else if("validataCodeError".equals(exceptionClassName)){
request.setAttribute(IndexErrorKey, "验证码错误");
} else if("usernameValidError".equals(exceptionClassName)) {
request.setAttribute(IndexErrorKey, "用户名长度不合法");
}else if("passwordValidError".equals(exceptionClassName)){
request.setAttribute(IndexErrorKey, "密码长度不合法");
}else if(ExcessiveAttemptsException.class.getName().equals(exceptionClassName)){
request.setAttribute(IndexErrorKey, "验证失败次数过多,5分钟后重试");
} else if(LockedAccountException.class.getName().equals(exceptionClassName)){
request.setAttribute(IndexErrorKey, "账号被锁定,请联系管理员");
}else if (AuthenticationException.class.getName().equals(exceptionClassName)){
request.setAttribute(IndexErrorKey, "用户名或密码错误");
}else
request.setAttribute(IndexErrorKey, "未知错误");
}
//如果认证错误 返回login页面(显示错误信息)
return "login";
}
//登陆失败回退首页(会自动跳转到登录页面)
//若一个已经登录的用户发送get请求/login实际上无论怎么输入表单数据都会跳转到index页面
return "redirect:/";
}
(2): doGetAuthorizationInfo
这是权限认证,前面在登录认证时,我已经把权限信息放在ShiroUser里面了,我们只要取出其中的permission按照我这样返回就好了,我这边数据库存的是 类似于 "user:query"表示用户查询的权限,然后在方法上进行拦截,jsp页面用shiro标签进行过滤即可