工作流程:
浏览器将用户名、密码、是否记住登录等信息发送给登录controller ,
new UsernamePasswordToken()获取token,将用户名、加密后的密码、rememberMe,set到token中。SecurityUtils.getSubject();获取subject对象,执行subect.login(token)进行登录操作并捕获可能出现的账号密码错误等异常。
1.前端登录页面将用户名、密码、是否记住登录,传入到Controller
Controller登录方法:
//1.获取shiro中的subject对象
Subject subject = SecurityUtils.getSubject();
//2.对用户从页面输入的密码进行加密处理
password = new Md5Hash(password,username,1024).toString();
System.out.println("加密后的密码: " + password);
//3.创建shiro中的用户名和密码对象,将用户输入的用户名密码交给shiro管理
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//4.关于记住我的设置
System.out.println("是否记住我: " + rememberMe);
if(rememberMe){
token.setRememberMe(true);
}
//5.调用shiro的登录方法,调用后,shiro会自动执行Realm实现类===========
try {
subject.login(token);
System.out.println("开始登陆");
} catch (UnknownAccountException e) {
return new Result(1, "账号不存在");
}catch (IncorrectCredentialsException e){
return new Result(1,"密码错误");
}catch (AuthenticationException e){
System.out.println("其他异常");
return new Result(1,"其他异常");
}
return new Result(0, "登陆成功!");
/**
* 注销方法
* @return
*/
public Result logout(){
//1.获取subject对象
Subject subject = SecurityUtils.getSubject();
//2.返回注销方法
subject.logout();
//3.返回
return Result.ok("注销成功");
}
获取完数据后,shiro会自动执行Realm的实现类,实现类需要手动实现,如下:
/**
* projectName: myproject
* @author: xxx
* time: 2021/9/6 21:17
* description: 自定义realm对象
*
* 1.从数据库根据用户名,取出数据库中的用户名,密码交给shiro框架
* 2.根据用户名,到数据库取出用户对应的角色和权限对象交给shiro框架管理
*/
public class MyUserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private MenuService menuService;
@Autowired
private RoleService roleService;
/**
* 用户输入的用户名和密码输入正确,校验完成后进行赋值操作
* 根据用户名到数据库查询这个用户对应的角色和权限,交给shiro管理
* 调用时机:在需要访问资源的时候,需要角色和权限的视乎才会调用此方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("已登陆成功,授予登录用户权限,也就是赋予用户对应的角色和能够访问的菜单");
//1.获取当前登录的用户对象
SysUsers sysUSers = (SysUsers) principalCollection.getPrimaryPrincipal();
//2.获取当前登录用户的 用户id
int uid = sysUSers.getId();
//3.根据用户id,查询数据库中这个用户对应的角色集合和权限集合
Set<String> roleList = roleService.findRoleListByUid(uid);
Set<String> menuList = menuService.findMenuListbyUid(uid);
//4.创建shiro中的用户权限对象
SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
//5.将查询到的角色集合放入shiro的权限对象
auth.setRoles(roleList);
//6.将查询到的权限集合放入shiro的权限对象
auth.setStringPermissions(menuList);
//7.返回shiro权限对象
return auth;
}
/**
* 根据用户在页面输入的用户名,查询数据库中的用户名和密码,交给shiro框架
* 让shiro框架进行对比用户名,密码是否正确
* 调用时机:在controller调用subject.login(token);方法就会执行这个方法,进行用户名 密码校验
* @param auth
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
System.out.println("开始校验用户名和密码................");
//1.获取用户在浏览器输入的用户名
String userName = (String) auth.getPrincipal();
//2.根据用户输入的用户名,查询数据库的用户对象
String password = new String((char[]) auth.getCredentials());
//3.判断用户是否为空,为空则抛出异常
SysUsers sysUSer = userService.findUserbyName(userName);
if (sysUSer == null) {
throw new UnknownAccountException("账号不存在,请先注册,再登录!");
}
//4.对比数据库的密码和用户输入的密码是否一致
if(!password.equals(sysUSer.getPassword())){
throw new IncorrectCredentialsException("密码错误");
}
//5.判断用户状态,1正常,其他为锁定状态
if(sysUSer.getStatus() != 1){
throw new LockedAccountException("账号被锁定不允许登录");
}
//6.封装shiro中需要的权限对象,包括用户名 密码 以及当前用户对象交给shiro返回
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userName, password, this.getName());
return info;
}
}
其他Controller方法需要做权限验证时,可以在上面加注解@RequiresPermissions("sys:product:add")
括号里是权限字符串,用冒号连接
shiro注解用在Service和Controller层,但是如果Service层有事务注解,那么shiro注解要放在Controller层。因为两个代理对象在类型转换时会出现异常。
2、接口获取用户名,密码生成UsernamePasswordToken并提交认证:
@Controller
@RequestMapping("/api")
public class ApiController {
@Autowired
SysMenuService sysMenuService;
@RequestMapping("/login")
@ResponseBody
public ResponseVO login(String username, String password) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//获取当前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
// 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
// 每个Realm都能在必要时对提交的AuthenticationTokens作出反应
// 所以这一步在调用login(token)方法时,它会走到xxRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
currentUser.login(token);
//认证通过,将菜单信息返回给前端展示
if (currentUser.isAuthenticated()) {
MenuSystem menuSystem = sysMenuService.findMenuTree();
Gson gson = new Gson();
String menuJson = gson.toJson(menuSystem, MenuSystem.class);
return ResultUtil.success("登录成功!", menuJson);
}
} catch (UnknownAccountException e1) {
e1.printStackTrace();
return ResultUtil.error("用户名不存在!");
} catch (IncorrectCredentialsException e2) {
e2.printStackTrace();
return ResultUtil.error("密码输入错误!");
} catch (Exception e) {
e.printStackTrace();
return ResultUtil.error(e.getMessage());
}
return ResultUtil.error("登录失败!");
}
说明:这里的菜单数据并没有使用shiro标签来实现,因为layuimini的菜单渲染是通过json数据的形式来实现的,所以这里我也是将菜单数据转换成了json给前端。
如果认证通过,那么通过subject.isAuthenticated()返回的结果就会是true,我们就可以认为登录成功了,此时,可以就可以将对应的菜单信息传回给前端渲染展示了。
https://blog.51cto.com/wyait/2082803
https://www.w3cschool.cn/shiro/oibf1ifh.html