第六章 Realm及相关对象(四) AuthorizationInfo

本文介绍了Shiro框架中AuthorizationInfo接口的功能及其实现类SimpleAuthorizationInfo的用途。该接口定义了获取角色和权限的方法,用于身份验证成功后的授权过程。

AuthorizationInfo用于聚合授权信息的:

public interface AuthorizationInfo extends Serializable {
	Collection<String> getRoles(); //获取角色字符串信息
	Collection<String> getStringPermissions(); //获取权限字符串信息
	Collection<Permission> getObjectPermissions(); //获取Permission对象信息
}

当我们使用AuthorizingRealm 时,如果身份验证成功,在进行授权时就通过doGetAuthorizationInfo方法获取角色/权限信息用于授权验证。

Shiro 提供了一个实现SimpleAuthorizationInfo,大多数时候使用这个即可。

对于Account 及SimpleAccount,之前的【6.3 AuthenticationInfo】已经介绍过了,用于SimpleAccountRealm子类,实现动态角色/权限维护的。

<think>我们需要创建一个新的Realm类来处理新用户表的认证和授权。由于新用户表没有对应的权限表,我们将为这些用户分配默认权限。 同时,我们需要确保原有的Realm仍然处理原有用户表的认证和授权。 步骤: 1. 创建新的Realm类(例如NewUserRealm)继承AuthorizingRealm,并实现认证和授权方法。 2. 在认证方法中,从新用户表查询用户并验证。 3. 在授权方法中,为所有新用户返回一个默认的角色或权限(例如:"new_user"角色和"new_user:read"权限)。 4. 在Shiro配置中注册这个新的Realm,并设置支持相应的Token(这里我们使用自定义Token来区分用户类型,但也可以使用不同的Token)。 5. 修改登录逻辑,根据用户类型选择不同的Realm进行认证。 但是,由于我们之前已经设计了一个MultiUserRealm来路由,那么在新的需求下,我们可以选择两种方式: 方案一:继续使用MultiUserRealm,并在其中扩展新用户表的处理(如之前的设计)。 方案二:为新的用户表单独创建一个Realm,然后使用ModularRealmAuthenticator配合多个Realm。 这里,根据问题要求,我们采用方案二:创建新的Realm,并配置多个Realm。 然而,问题要求不改变原来的登录方式,并且新添加用户表。所以我们需要同时支持两种用户表,并且登录接口需要能够区分。 具体实现: 1. 定义一个新的Realm(NewUserRealm)用于新用户表。 2. 修改Shiro配置,使用多个Realm(原有Realm和新的NewUserRealm)。 3. 使用自定义的Authenticator(ModularRealmAuthenticator)并设置认证策略(例如:AtLeastOneSuccessfulStrategy)。 4. 在登录控制器中,根据用户类型,在登录时生成不同的Token(例如:原用户使用JwtToken,新用户使用NewUserToken)以让Shiro知道用哪个Realm来认证。 但是,我们之前已经使用了JWT,并且希望统一使用JWT Token。因此,我们可以通过Token中的某个字段(如前面设计的userType)来区分。这样,我们可以继续使用一个支持多用户的Realm(如MultiUserRealm)或者使用多个Realm并让每个Realm只处理特定类型的Token。 这里我们采用多个Realm的方式,每个Realm只处理自己对应的Token类型。 设计: 自定义两种Token: - OriginalUserToken:用于原用户表 - NewUserToken:用于新用户表 然后,让每个Realm的supports方法只支持对应的Token。 步骤: 1. 创建NewUserToken类(与OriginalUserToken类似,但可能包含不同的字段,但为了统一,我们也可以使用同一个Token类,然后通过一个字段区分,但为了清晰,这里我们创建两个不同的Token类)。 2. 创建NewUserRealm: - 在supports方法中,只支持NewUserToken。 - 在doGetAuthenticationInfo方法中,实现新用户表的认证。 - 在doGetAuthorizationInfo方法中,为新用户分配默认权限。 3. 修改原有Realm(假设为UserRealm)的supports方法,使其只支持OriginalUserToken。 4. 在Shiro配置中,设置多个Realm,并配置认证器。 5. 在登录控制器中,根据用户类型创建不同的Token。 但是,由于我们使用JWT,在Token生成后,后续的请求中携带的都是JWT字符串。因此,我们需要在JwtFilter中根据某些条件(比如用户类型)来创建不同的Token(OriginalUserToken或NewUserToken)。这个条件可以通过JWT的payload中的某个字段(例如userType)来区分。 所以,我们可以在JwtFilter中解析JWT,然后根据userType字段创建不同的Token。 具体代码: 1. 定义两种Token(它们都包含token字符串,但类型不同): ```java // 原用户Token public class OriginalUserToken implements AuthenticationToken { private String token; public OriginalUserToken(String token) { this.token = token; } // ... getter } // 新用户Token public class NewUserToken implements AuthenticationToken { private String token; public NewUserToken(String token) { this.token = token; } // ... getter } ``` 2. 创建NewUserRealm: ```java public class NewUserRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { return token instanceof NewUserToken; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String jwt = (String) token.getPrincipal(); // 解析JWT,获取用户名 String username = JwtUtils.getUsername(jwt); // 查询新用户表 NewUser user = newUserService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户不存在"); } // 验证token有效性(例如签名、过期时间等)可以由JwtUtils完成 if (!JwtUtils.verify(jwt, username, user.getPassword())) { throw new IncorrectCredentialsException("无效的token"); } return new SimpleAuthenticationInfo(user, jwt, getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加默认角色 info.addRole("new_user"); // 添加默认权限 info.addStringPermission("new_user:read"); // 可以添加更多默认权限 return info; } } ``` 3. 原有UserRealm修改supports方法: ```java public class UserRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { return token instanceof OriginalUserToken; } // ... 原有的认证和授权方法 } ``` 4. 修改JwtFilter:在createToken方法中根据解析出的userType创建不同的Token ```java public class JwtFilter extends AuthenticatingFilter { // ... @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { // 从请求中获取token String jwt = getRequestToken(request); if (StringUtils.isBlank(jwt)) { return null; } // 解析JWT,获取userType String userType = JwtUtils.getUserType(jwt); // 假设JwtUtils可以解析出userType字段 if ("original".equals(userType)) { return new OriginalUserToken(jwt); } else if ("new".equals(userType)) { return new NewUserToken(jwt); } else { throw new AuthenticationException("不支持的userType"); } } // ... } ``` 5. Shiro配置多个Realm和认证器: ```java @Configuration public class ShiroConfig { @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置多个Realm securityManager.setAuthenticator(modularRealmAuthenticator()); securityManager.setRealms(Arrays.asList(userRealm(), newUserRealm())); return securityManager; } @Bean public ModularRealmAuthenticator modularRealmAuthenticator() { ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); // 设置认证策略,这里使用AtLeastOneSuccessfulStrategy(任意一个Realm认证成功即可) authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); return authenticator; } @Bean public UserRealm userRealm() { return new UserRealm(); } @Bean public NewUserRealm newUserRealm() { return new NewUserRealm(); } } ``` 6. 登录控制器中,根据用户类型生成JWT时,需要将userType放入JWT的payload中: ```java @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest request) { String userType = request.getUserType(); // ... 验证用户 String jwt; if ("original".equals(userType)) { // 验证原用户表 User user = userService.authenticate(request.getUsername(), request.getPassword()); jwt = JwtUtils.generateToken(user.getUsername(), user.getPassword(), "original"); } else if ("new".equals(userType)) { NewUser user = newUserService.authenticate(request.getUsername(), request.getPassword()); jwt = JwtUtils.generateToken(user.getUsername(), user.getPassword(), "new"); } else { // ... } return ResponseEntity.ok(new JwtResponse(jwt)); } ``` 这样,我们就实现了两种用户表的登录,并且为新的用户表分配了默认权限。 注意:在JwtUtils中生成和解析token时,需要处理userType字段。 这种方式的优点: - 清晰分离了两种用户表的认证和授权逻辑。 - 扩展性较好,如果需要添加第三种用户表,只需要再创建一个Realm和Token类型,并在JwtFilter中添加判断即可。 缺点: - 需要维护多个Realm,配置稍微复杂。 但是,如果新用户表没有权限表,我们也可以考虑使用同一个Realm,然后通过用户类型分支处理。两种方式都是可行的,具体选择取决于项目规模和扩展需求。 由于问题要求不改变原来的登录方式,并且新添加用户表,我们这里采用多个Realm的方式,这样原有Realm的代码不需要修改,只需要增加新的Realm。 注意:在授权时,新用户表返回的是固定的默认权限。如果后续需要为不同的新用户分配不同的权限,可以扩展NewUserRealm的授权方法,从数据库或其他地方获取权限。 最后,确保在Shiro的权限配置中,为新用户的默认角色("new_user")配置相应的访问权限。 §§相关问题§§: 1. 在Shiro中使用多个Realm时,如何配置认证策略(AuthenticationStrategy)? 2. 如何设计JWT的payload以支持多种用户类型? 3. 如果新用户表需要动态权限(从数据库获取),如何修改NewUserRealm的授权方法? 4. 在Shiro中,如何实现多个Realm的异常处理?</think># 自定义新用户表的Realm实现方案 在Spring Boot + Shiro + JWT架构中,为新用户表创建自定义Realm是扩展认证授权的关键步骤。以下是完整的实现方案: ## 核心实现代码 ### 1. 创建新用户表专属Realm ```java /** * 新用户表专属Realm */ public class NewUserRealm extends AuthorizingRealm { @Autowired private NewUserService newUserService; // 声明支持新用户Token @Override public boolean supports(AuthenticationToken token) { return token instanceof NewUserToken; } // 认证逻辑 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { NewUserToken userToken = (NewUserToken) token; String username = userToken.getUsername(); // 从新用户表查询用户 NewUser newUser = newUserService.findByUsername(username); if (newUser == null) { throw new UnknownAccountException("用户不存在"); } // 验证密码(示例使用BCrypt加密) if (!BCrypt.checkpw(new String(userToken.getPassword()), newUser.getPassword())) { throw new IncorrectCredentialsException("密码错误"); } // 返回认证信息 return new SimpleAuthenticationInfo( newUser, // 用户对象 newUser.getPassword(), // 密码凭证 ByteSource.Util.bytes(newUser.getSalt()), // 盐值 getName() // Realm名称 ); } // 授权逻辑(新用户表无权限表,使用默认权限) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加默认角色 info.addRole("new_user_default_role"); // 添加默认权限 info.addStringPermission("new_user:read"); info.addStringPermission("new_user:write"); return info; } } ``` ### 2. 创建新用户专属Token类 ```java /** * 新用户表专属Token */ public class NewUserToken extends UsernamePasswordToken { private String userType; // 用户类型标识 public NewUserToken(String username, char[] password, String userType) { super(username, password); this.userType = userType; } public String getUserType() { return userType; } } ``` ### 3. 更新Shiro配置类 ```java @Configuration public class ShiroConfig { @Bean public SecurityManager securityManager( @Qualifier("originalRealm") OriginalRealm originalRealm, @Qualifier("newUserRealm") NewUserRealm newUserRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置多个Realm securityManager.setRealms(Arrays.asList(originalRealm, newUserRealm)); // 配置自定义认证器 ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); securityManager.setAuthenticator(authenticator); return securityManager; } @Bean public NewUserRealm newUserRealm() { NewUserRealm realm = new NewUserRealm(); // 设置凭证匹配器(使用BCrypt加密) HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("BCrypt"); matcher.setHashIterations(10); realm.setCredentialsMatcher(matcher); return realm; } @Bean public OriginalRealm originalRealm() { // 原有Realm配置保持不变 return new OriginalRealm(); } // 配置JWT Filter @Bean public FilterRegistrationBean<JwtFilter> jwtFilterRegistration(JwtFilter filter) { FilterRegistrationBean<JwtFilter> registration = new FilterRegistrationBean<>(filter); registration.setEnabled(false); // 禁用自动注册,由Shiro管理 return registration; } } ``` ### 4. 实现新用户登录控制器 ```java @RestController @RequestMapping("/auth") public class AuthController { @Autowired private NewUserService newUserService; @PostMapping("/login/new") public ResponseEntity<?> loginNewUser(@RequestBody NewUserLoginRequest request) { // 创建专属Token NewUserToken token = new NewUserToken( request.getUsername(), request.getPassword().toCharArray(), "new_user" ); // 获取当前Subject Subject subject = SecurityUtils.getSubject(); try { // 执行登录 subject.login(token); // 生成JWT NewUser newUser = (NewUser) subject.getPrincipal(); String jwt = JwtUtils.generateToken( newUser.getId().toString(), newUser.getUsername(), "new_user" ); return ResponseEntity.ok(new JwtResponse(jwt, "new_user")); } catch (AuthenticationException e) { return ResponseEntity.status(401).body("登录失败: " + e.getMessage()); } } } ``` ### 5. 新用户服务实现 ```java @Service public class NewUserService { @Autowired private NewUserRepository newUserRepository; public NewUser findByUsername(String username) { return newUserRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在")); } public NewUser createUser(NewUser newUser) { // 生成盐值 String salt = new SecureRandomNumberGenerator().nextBytes(16).toBase64(); newUser.setSalt(salt); // 加密密码 String hashedPassword = new SimpleHash( "BCrypt", newUser.getPassword(), ByteSource.Util.bytes(salt), 10 ).toHex(); newUser.setPassword(hashedPassword); return newUserRepository.save(newUser); } } ``` ## 关键设计解释 1. **专属Token设计**: - `NewUserToken`继承自`UsernamePasswordToken`,添加`userType`字段 - 确保Shiro能正确路由到对应的Realm 2. **多Realm配置**: - 使用`ModularRealmAuthenticator`支持多个Realm - 配置`AtLeastOneSuccessfulStrategy`认证策略 - 每个Realm只处理自己支持的Token类型 3. **默认权限实现**: - 在`doGetAuthorizationInfo`中直接返回预设权限 - 使用`SimpleAuthorizationInfo`添加默认角色和权限 - 权限字符串格式为`资源:操作`(如`new_user:read`) 4. **密码安全处理**: - 使用BCrypt算法加密存储密码 - 每次注册时生成随机盐值 - 配置`HashedCredentialsMatcher`匹配加密算法 5. **JWT集成**: - 在Token中存储`userType`字段区分用户来源 - 登录成功后生成包含用户类型标识的JWT - 后续请求通过JWT中的用户类型路由到正确Realm ## 权限配置建议 在`shiro.ini`或配置类中添加新用户默认权限: ```properties [roles] new_user_default_role = new_user:read, new_user:write [urls] /api/new-user/** = authc, roles[new_user_default_role] ``` ## 优势与特点 1. **完全解耦**:新用户系统与原有系统完全分离 2. **无缝集成**:不影响原有用户登录流程 3. **灵活扩展**:支持未来添加更多用户类型 4. **安全可靠**:使用行业标准加密算法 5. **权限隔离**:不同用户类型有独立的权限体系
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值