shiro的SecurityUtils.getSubject() 使用说明
SecurityUtils.getSubject()
是 Apache Shiro 框架中获取当前用户安全上下文(Subject)的核心方法。这个方法允许你在应用的任何地方访问当前用户的认证状态、权限信息和执行安全操作。
一、SecurityUtils.getSubject()
作用
Subject
代表当前与应用交互的用户:
- 对于 Web 应用,通常是一个 HTTP 请求的会话
- 包含用户的身份信息(如用户名、角色)
- 提供认证(登录 / 登出)和授权(权限检查)方法
- 可以存储会话数据(类似
HttpSession
)
二、核心方法与用法
1. 获取当前用户的 Subject
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;// 在任何地方获取当前用户的Subject
Subject currentUser = SecurityUtils.getSubject();
2. 认证操作(登录 / 登出)
// 登录
UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
try {currentUser.login(token); // 验证用户名密码System.out.println("登录成功");
} catch (AuthenticationException e) {System.out.println("登录失败: " + e.getMessage());
}// 登出
currentUser.logout(); // 清除身份信息和会话
3. 授权操作(权限检查)
// 检查是否有角色
boolean hasAdminRole = currentUser.hasRole("admin");// 检查是否有权限
boolean canEdit = currentUser.isPermitted("user:edit");// 强制权限检查(无权限时抛出异常)
currentUser.checkPermission("user:delete");
4. 获取用户身份信息
// 获取主身份(通常是用户名或用户ID)
Object principal = currentUser.getPrincipal(); // 返回在Realm中认证时设置的principal// 判断用户是否已认证(通过登录验证)
boolean isAuthenticated = currentUser.isAuthenticated();// 判断用户是否通过"记住我"功能登录
boolean isRemembered = currentUser.isRemembered();
5. 会话操作
// 获取会话(类似HttpSession,但与Servlet无关)
Session session = currentUser.getSession();
session.setAttribute("key", "value"); // 存储会话数据
Object value = session.getAttribute("key");
三、在 Web 应用中的使用场景
1. Controller 中获取当前用户
@RestController
@RequestMapping("/api")
public class UserController {@GetMapping("/user/info")public ResponseEntity<UserInfo> getUserInfo() {Subject currentUser = SecurityUtils.getSubject();String username = (String) currentUser.getPrincipal(); // 获取用户名// 根据用户名查询用户信息UserInfo userInfo = userService.getUserInfo(username);return ResponseEntity.ok(userInfo);}
}
2. 自定义 Realm 中验证用户
public class MyRealm extends AuthorizingRealm {// 授权方法@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();String username = (String) principals.getPrimaryPrincipal();// 根据用户名查询角色和权限Set<String> roles = userService.getRoles(username);Set<String> permissions = userService.getPermissions(username);info.setRoles(roles);info.setStringPermissions(permissions);return info;}// 认证方法@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken upToken = (UsernamePasswordToken) token;String username = upToken.getUsername();// 查询用户信息User user = userService.getUserByUsername(username);if (user == null) {throw new UnknownAccountException("用户不存在");}// 返回认证信息(用户名、密码哈希、盐值)return new SimpleAuthenticationInfo(user.getUsername(), // 主身份user.getPassword(), // 密码(已加密)ByteSource.Util.bytes(user.getSalt()), // 盐值getName() // Realm名称);}
}
在密码学和信息安全领域,“盐值”(Salt)有一些常见的同义或近似概念,不同场景下可能会使用不同的名称,主要包括:
- 盐:最简洁的简称,与 “盐值” 含义完全一致,在技术文档中更常用(例如 “给密码加盐”)。
- 随机盐:强调盐值的随机性,突出其 “随机生成” 的特性(例如 “每个用户密码需对应一个随机盐”)。
- 加密盐:在加密场景中使用时的称呼,强调其用于增强加密 / 哈希安全性的作用。
- 盐字符串:因盐值通常以字符串形式存储或处理,有时会明确其数据类型(例如 “将盐字符串与密码拼接后哈希”)。
四、常见问题与注意事项
1. 在非 Web 环境中使用
- Shiro 默认通过
ThreadContext
绑定 Subject 到当前线程 - 在多线程环境中需要手动传递 Subject(使用
Subject.associateWith()
)
2. NullPointerException 问题
- 如果在 Shiro 初始化前调用
SecurityUtils.getSubject()
,可能返回空 - 确保 Shiro 过滤器链已正确配置(如 Spring Boot 中的
ShiroFilterFactoryBean
)
3. 与 Spring Boot 集成
- 在 Spring Boot 中,通常通过
@Autowired
注入SecurityManager
而不是直接使用SecurityUtils
- 但
SecurityUtils.getSubject()
仍然可用,且更简洁
4. RememberMe 功能
- 如果用户通过 "记住我" 登录,
isRemembered()
返回true
,但isAuthenticated()
返回false
- 在需要强认证的场景(如支付),建议检查
isAuthenticated()
- 用户在关闭浏览器后仍然保持登录状态,直到会话过期或手动注销。
五、总结
SecurityUtils.getSubject()
是 Shiro 的核心 API,提供了:
- 用户认证(登录 / 登出)
- 权限检查(角色 / 权限验证)
- 会话管理
- 身份信息获取
合理使用这个方法,可以在应用的任何层次(Controller、Service、工具类)安全地访问当前用户信息,实现细粒度的权限控制。
六、一点说明
1)RememberMe失效,不一定是配制的问题。
2)启用的时候报Bean 'rememberMeManager' of type [org.apache.shiro.web.mgt.CookieRememberMeManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)也不一定是失败的原因。
Bean 'userRealm' of type [xxx.xxxx.config.UserRealm] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
21:20:56.716 [restartedMain] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'rememberMeManager' of type [org.apache.shiro.web.mgt.CookieRememberMeManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
21:20:56.731 [restartedMain] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'securityManager' of type [org.apache.shiro.web.mgt.DefaultWebSecurityManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
21:20:56.750 [restartedMain] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'authorizationAttributeSourceAdvisor' of type [org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
上面的提示并不会影响我们正常的运行。
3)根本原因:map.put("/**", "authc"); 所有的用户一定要通过登录页面进行认证后才可以导致。
将上面的authc改成user,就可以成功通过RememberMe的用户关了浏览器,再次打开以后也可以成功的打开首页。
这样可能会导致一些安全问题,所以还是建议,关了浏览器就要求再次登录系统。