深入解析ApacheShiro授权认证体系原理,解决开发难题

程序员科技 2025-03-26 20:27:04

你在做互联网后端开发的时候,有没有遇到过这样的情况:用户登录系统后,不同用户对各种功能模块的访问权限管理起来特别麻烦。有的功能普通用户不能访问,只有管理员可以,可怎么准确无误地实现这种权限控制呢?这其实就是授权认证体系要解决的关键问题,而 Apache Shiro 在这方面堪称一把利器。

Apache Shiro介绍

Apache Shiro 是一个强大且易用的 Java 安全框架,它提供了身份验证、授权、加密和会话管理等功能。在众多互联网项目中,随着业务复杂度增加,对用户权限的精细化管理需求越来越高。传统的自己编写简单的权限控制代码,不仅容易出错,而且扩展性差。Apache Shiro 的出现,就是为了给开发者提供一套全面且易于集成的安全解决方案。它可以轻松集成到各种 Java Web 应用、企业级应用中,让开发者专注于业务逻辑,而不必在底层的安全认证和授权机制上耗费过多精力。

认证流程

当用户在前端输入用户名和密码登录时,这些信息会被封装成一个AuthenticationToken传递到后端。常见的AuthenticationToken实现类如UsernamePasswordToken,它携带了用户名和密码信息。在后端,Shiro 的Subject代表当前执行操作的用户,它调用login方法,将AuthenticationToken交给SecurityManager。

从源码角度来看,Subject的login方法实际会调用SecurityManager的login方法。在SecurityManager类中,关键代码如下:

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { // 这里开始认证流程 AuthenticationInfo info = authenticate(token); Subject loggedIn = createSubject(token, info, subject); onSuccessfulLogin(token, info, loggedIn); return loggedIn;}

SecurityManager会委托配置好的Realm去验证用户身份。Realm就像是一个数据仓库,它从数据库或者其他数据源中获取用户的真实身份信息和密码等凭证。比如,如果用户名为 “admin”,Realm会去数据库中查询 “admin” 对应的密码。在实际应用中,为了保证安全性,密码通常不会以明文形式存储,而是经过加密处理,如使用 SHA - 256 等加密算法。

自定义Realm时,通常会继承AuthorizingRealm抽象类,并重写doGetAuthenticationInfo方法,示例代码如下:

public CustomRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 授权相关逻辑,这里先不展开 return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); // 从数据库查询用户信息,这里假设使用JDBC Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DriverManager.getConnection(url, username, password); String sql = "SELECT password FROM users WHERE username =?"; ps = conn.prepareStatement(sql); ps.setString(1, username); rs = ps.executeQuery(); if (rs.next()) { String encryptedPassword = rs.getString("password"); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo( username, encryptedPassword, getName()); return info; } else { throw new UnknownAccountException("用户不存在"); } } catch (SQLException e) { e.printStackTrace(); throw new AuthenticationException("数据库查询异常"); } finally { // 关闭资源 try { if (rs!= null) rs.close(); if (ps!= null) ps.close(); if (conn!= null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }}

Realm从数据库中获取到加密后的密码,然后将前端传来的密码也按照相同的加密算法进行加密,再比对两者是否一致。如果一致,认证就通过了。

在这个过程中,还可能涉及到一些额外的验证环节,例如多因素认证。假设系统开启了短信验证码功能,当用户输入用户名和密码后,Realm在验证密码的同时,还会检查用户输入的短信验证码是否正确,只有两者都匹配,认证才会通过。这时候可以在doGetAuthenticationInfo方法中添加对短信验证码的验证逻辑。

授权流程

用户认证通过后,就涉及到授权。比如,普通用户不能访问系统的用户管理模块,只有管理员可以。Shiro 的SecurityManager会根据用户的身份信息,从Realm中获取用户所拥有的角色和权限信息。

在 Shiro 中,权限的定义非常灵活,可以是简单的字符串表示,如 “user:manage” 表示对用户管理功能的操作权限,也可以是更复杂的基于资源和操作的权限描述。例如,“document:read:123” 表示对编号为 123 的文档有读取权限。

从源码层面分析,当判断用户是否具有某个权限时,会调用SecurityManager的isPermitted方法,该方法最终会委托给Authorizer进行权限判断。在AuthorizingRealm类中,与授权相关的关键方法是doGetAuthorizationInfo,当需要判断用户权限时会调用此方法来获取用户的角色和权限信息。

@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); // 从数据库查询用户角色和权限,这里假设使用JDBC Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); try { conn = DriverManager.getConnection(url, username, password); // 查询用户角色 String roleSql = "SELECT role_name FROM user_roles WHERE username =?"; ps = conn.prepareStatement(roleSql); ps.setString(1, username); rs = ps.executeQuery(); List<String> roleList = new ArrayList<>(); while (rs.next()) { String role = rs.getString("role_name"); roleList.add(role); } info.setRoles(new HashSet<>(roleList)); // 查询用户权限 String permissionSql = "SELECT permission FROM role_permissions WHERE role_name IN ("; for (int i = 0; i < roleList.size(); i++) { if (i > 0) { permissionSql += ", "; } permissionSql += "?"; } permissionSql += ")"; ps = conn.prepareStatement(permissionSql); for (int i = 0; i < roleList.size(); i++) { ps.setString(i + 1, roleList.get(i)); } rs = ps.executeQuery(); List<String> permissionList = new ArrayList<>(); while (rs.next()) { String permission = rs.getString("permission"); permissionList.add(permission); } info.setStringPermissions(new HashSet<>(permissionList)); return info; } catch (SQLException e) { e.printStackTrace(); throw new AuthorizationException("数据库查询异常"); } finally { // 关闭资源 try { if (rs!= null) rs.close(); if (ps!= null) ps.close(); if (conn!= null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }}

Realm从数据库或者其他数据源中获取用户对应的角色和权限信息。数据库表结构通常会设计用户表、角色表、用户角色关联表以及权限表、角色权限关联表。当用户尝试访问某个功能时,Shiro 会检查该用户是否具有相应的权限。比如用户尝试访问用户管理页面,Shiro 会从Realm获取该用户的权限列表,判断其中是否包含 “user:manage” 权限,如果没有,就会阻止访问,并返回权限不足的提示。

而且,Shiro 还支持权限继承和组合。比如,一个高级管理员角色可能继承了普通管理员角色的所有权限,同时还拥有一些额外的权限。在进行权限检查时,Shiro 会递归地检查用户所拥有的角色及其继承角色的所有权限,确保授权判断的准确性。在Authorizer的实现类中,会有相应的逻辑来处理权限继承和组合的情况,这里不再详细展开源码分析。

总结

Apache Shiro 的授权认证体系原理虽然看起来有些复杂,但一旦理解并应用到项目中,会极大地提升系统的安全性和可维护性。各位后端开发的伙伴们,不妨在自己的下一个项目中尝试引入 Apache Shiro,体验它带来的便捷。如果你在使用过程中有任何问题或者心得,欢迎在评论区留言分享,让我们一起在开发的道路上不断进步。

0 阅读:0

程序员科技

简介:感谢大家的关注