Shiro之多Realm篇
1、功能实现
定义两个Realm(User、Admin),然后登录账号,分别认证、授权
2、shiro10 子工程

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.yzm</groupId><artifactId>shiro</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --></parent><artifactId>shiro10</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>shiro10</name><description>Demo project for Spring Boot</description><dependencies><dependency><groupId>com.yzm</groupId><artifactId>common</artifactId><version>0.0.1-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
application.yml
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.192.128:3306/testdb2?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaiusername: rootpassword: 1234mybatis-plus:mapper-locations: classpath:/mapper/*Mapper.xmltype-aliases-package: com.yzm.shiro10.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、定义UserRealm、AdminRealm
这两个Realm实现的方法内容大致一样,主要通过日志打印查看认证和授权的时候分别调用了什么Realm
package com.yzm.shiro10.config;import com.yzm.shiro10.entity.Permissions;
import com.yzm.shiro10.entity.Role;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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 java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** 自定义UserRealm */
@Slf4j
public class UserRealm extends AuthorizingRealm {private final UserService userService;private final RoleService roleService;private final PermissionsService permissionsService;public UserRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {// 设置Realm名称setName("UserRealm");this.userService = userService;this.roleService = roleService;this.permissionsService = permissionsService;}@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof CustomToken;}/*** 授权*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {log.info("User 授权");String username = (String) principalCollection.getPrimaryPrincipal();// 查询用户,获取角色idsUser user = userService.lambdaQuery().eq(User::getUsername, username).one();List<Integer> roleIds = Arrays.stream(user.getRIds().split(",")).map(Integer::parseInt).collect(Collectors.toList());// 查询角色,获取角色名、权限idsList<Role> roles = roleService.listByIds(roleIds);Set<String> roleNames = new HashSet<>(roles.size());Set<Integer> permIds = new HashSet<>();roles.forEach(role -> {roleNames.add(role.getRName());Set<Integer> collect = Arrays.stream(role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());permIds.addAll(collect);});// 获取权限名称List<Permissions> permissions = permissionsService.listByIds(permIds);List<String> permNames = permissions.stream().map(Permissions::getPName).collect(Collectors.toList());SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.addRoles(roleNames);authorizationInfo.addStringPermissions(permNames);return authorizationInfo;}/*** 认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {log.info("User 认证");// 获取用户名跟密码UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;String username = usernamePasswordToken.getUsername();// 查询用户是否存在User user = userService.lambdaQuery().eq(User::getUsername, username).one();if (user == null) {throw new UnknownAccountException();}return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),// 用户名 + 盐ByteSource.Util.bytes(user.getUsername() + user.getSalt()),getName());}
}
package com.yzm.shiro10.config;import com.yzm.shiro10.entity.Permissions;
import com.yzm.shiro10.entity.Role;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
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 java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;/*** 自定义AdminRealm */
@Slf4j
public class AdminRealm extends AuthorizingRealm {private final UserService userService;private final RoleService roleService;private final PermissionsService permissionsService;public AdminRealm(UserService userService, RoleService roleService, PermissionsService permissionsService) {setName("AdminRealm");this.userService = userService;this.roleService = roleService;this.permissionsService = permissionsService;}@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof CustomToken;}/*** 授权*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {log.info("Admin 授权");String username = (String) principalCollection.getPrimaryPrincipal();// 查询用户,获取角色idsUser user = userService.lambdaQuery().eq(User::getUsername, username).one();List<Integer> roleIds = Arrays.stream(user.getRIds().split(",")).map(Integer::parseInt).collect(Collectors.toList());// 查询角色,获取角色名、权限idsList<Role> roles = roleService.listByIds(roleIds);Set<String> roleNames = new HashSet<>(roles.size());Set<Integer> permIds = new HashSet<>();roles.forEach(role -> {roleNames.add(role.getRName());Set<Integer> collect = Arrays.stream(role.getPIds().split(",")).map(Integer::parseInt).collect(Collectors.toSet());permIds.addAll(collect);});// 获取权限名称List<Permissions> permissions = permissionsService.listByIds(permIds);List<String> permNames = permissions.stream().map(Permissions::getPName).collect(Collectors.toList());SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();authorizationInfo.addRoles(roleNames);authorizationInfo.addStringPermissions(permNames);return authorizationInfo;}/*** 认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {log.info("Admin 认证");// 获取用户名跟密码String username = (String) authenticationToken.getPrincipal();//String password = new String((char[]) authenticationToken.getCredentials());// 查询用户是否存在User user = userService.lambdaQuery().eq(User::getUsername, username).one();if (user == null) {throw new UnknownAccountException();}return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),// 用户名 + 盐ByteSource.Util.bytes(user.getUsername() + user.getSalt()),getName());}
}
CustomToken 不同角色登录时,通过loginType来区分
package com.yzm.shiro10.config;import org.apache.shiro.authc.UsernamePasswordToken;public class CustomToken extends UsernamePasswordToken {private static final long serialVersionUID = 2496490278578779482L;private String loginType;public CustomToken(final String username, final String password) {super(username, password);}public CustomToken(final String username, final String password, String loginType) {super(username, password);this.loginType = loginType;}public String getLoginType() {return loginType;}public void setLoginType(String loginType) {this.loginType = loginType;}
}
LoginType 枚举
package com.yzm.shiro10.config;public enum LoginType {USER("User"), ADMIN("Admin");private final String type;LoginType(String type) {this.type = type;}public String type() {return this.type;}
}
4、登录接口
修改登录接口,通过角色来决定不同的认证授权
package com.yzm.shiro10.controller;import com.yzm.shiro10.config.CustomToken;
import com.yzm.shiro10.config.LoginType;
import com.yzm.shiro10.entity.User;
import com.yzm.shiro10.service.UserService;
import com.yzm.shiro10.utils.EncryptUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;@Controller
public class HomeController {private final UserService userService;public HomeController(UserService userService) {this.userService = userService;}@GetMapping(value = {"/", "/home"})public String home(ModelMap map) {Subject subject = SecurityUtils.getSubject();map.addAttribute("subject", subject.getPrincipals());return "home";}@GetMapping("login")public String login() {return "login";}@GetMapping("401")public Object notRole() {return "401";}@PostMapping("register")public Object register(ModelMap map, @RequestParam String username, @RequestParam String password) {User user = new User();user.setUsername(username);user.setPassword(password);// 密码加密EncryptUtils.encryptPassword(user);userService.save(user);map.addAttribute("user", user);return "home";}@PostMapping("login")public Object login(@RequestParam String username, @RequestParam String password, boolean rememberMe) {UsernamePasswordToken token;if ("admin".equals(username)) {token = new CustomToken(username, password, LoginType.ADMIN.type());} else {token = new CustomToken(username, password, LoginType.USER.type());}token.setRememberMe(rememberMe);String url = "/home";try {Subject subject = SecurityUtils.getSubject();subject.login(token);} catch (IncorrectCredentialsException e) {url = "/login?failure";} catch (UnknownAccountException e) {url = "/login";}return "redirect:" + url;}@PostMapping("/logout")public Object logout() {Subject subject = SecurityUtils.getSubject();if (subject.isAuthenticated() || subject.isRemembered()) {subject.logout();}return "redirect:/login";}
}
5、多Realm认证
自定义MultiRealmAuthenticator,重写doAuthenticate
package com.yzm.shiro10.config;import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;import java.util.ArrayList;
import java.util.Collection;/*** 自定义多领域认证器*/
public class MultiRealmAuthenticator extends ModularRealmAuthenticator {@Overrideprotected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {if (authenticationToken instanceof CustomToken) {assertRealmsConfigured();CustomToken customToken = (CustomToken) authenticationToken;// 根据登录类型选择对应的RealmCollection<Realm> typeRealms = new ArrayList<>();for (Realm realm : getRealms()) {if (realm.getName().contains(customToken.getLoginType())) typeRealms.add(realm);}return typeRealms.size() == 1 ?this.doSingleRealmAuthentication(typeRealms.iterator().next(), authenticationToken) :this.doMultiRealmAuthentication(typeRealms, authenticationToken);}return super.doAuthenticate(authenticationToken);}}
6、多Realm授权
自定义MultiRealmAuthorizer,重写hasRole、isPermitted
package com.yzm.shiro10.config;import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;import java.util.Set;/*** 自定义多领域授权器*/
public class MultiRealmAuthorizer extends ModularRealmAuthorizer {@Overridepublic boolean hasRole(PrincipalCollection principals, String roleIdentifier) {assertRealmsConfigured();// 当前登录人认证时使用的Realm名Set<String> realmNames = principals.getRealmNames();String realmName = realmNames.iterator().next();for (Realm realm : getRealms()) {if (!(realm instanceof Authorizer)) continue;// adminif (realmName.contains(LoginType.ADMIN.type()) && realm instanceof AdminRealm) {return ((AdminRealm) realm).hasRole(principals, roleIdentifier);}// userif (realmName.contains(LoginType.USER.type()) && realm instanceof UserRealm) {return ((UserRealm) realm).hasRole(principals, roleIdentifier);}}return false;}@Overridepublic boolean isPermitted(PrincipalCollection principals, String permission) {assertRealmsConfigured();// 当前登录人认证时使用的Realm名Set<String> realmNames = principals.getRealmNames();String realmName = realmNames.iterator().next();for (Realm realm : getRealms()) {if (!(realm instanceof Authorizer)) continue;// adminif (realmName.contains(LoginType.ADMIN.type()) && realm instanceof AdminRealm) {return ((AdminRealm) realm).isPermitted(principals, permission);}// userif (realmName.contains(LoginType.USER.type()) && realm instanceof UserRealm) {return ((UserRealm) realm).isPermitted(principals, permission);}}return false;}
}
7、ShiroConfig 配置类
package com.yzm.shiro10.config;import com.yzm.shiro10.service.PermissionsService;
import com.yzm.shiro10.service.RoleService;
import com.yzm.shiro10.service.UserService;
import com.yzm.shiro10.utils.EncryptUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;import java.util.Arrays;
import java.util.Properties;@Configuration
public class ShiroConfig {private final UserService userService;private final RoleService roleService;private final PermissionsService permissionsService;public ShiroConfig(UserService userService, RoleService roleService, PermissionsService permissionsService) {this.userService = userService;this.roleService = roleService;this.permissionsService = permissionsService;}/*** 凭证匹配器*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName(EncryptUtils.ALGORITHM_NAME);hashedCredentialsMatcher.setHashIterations(EncryptUtils.HASH_ITERATIONS);return hashedCredentialsMatcher;}/*** UserRealm*/@Beanpublic UserRealm userRealm() {UserRealm userRealm = new UserRealm(userService, roleService, permissionsService);userRealm.setCredentialsMatcher(hashedCredentialsMatcher());return userRealm;}/*** AdminRealm*/@Beanpublic AdminRealm adminRealm() {AdminRealm adminRealm = new AdminRealm(userService, roleService, permissionsService);adminRealm.setCredentialsMatcher(hashedCredentialsMatcher());return adminRealm;}/*** 安全管理*/@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 自定义认证器、认证策略// FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;// AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;// AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。ModularRealmAuthenticator multiRealmAuthenticator = new MultiRealmAuthenticator();multiRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());securityManager.setAuthenticator(multiRealmAuthenticator);// 自定义授权器ModularRealmAuthorizer multiRealmAuthorizer = new MultiRealmAuthorizer();securityManager.setAuthorizer(multiRealmAuthorizer);// 多个RealmsecurityManager.setRealms(Arrays.asList(userRealm(), adminRealm()));return securityManager;}/*** 开启注解*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();defaultAAP.setProxyTargetClass(true);return defaultAAP;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());return authorizationAttributeSourceAdvisor;}@Beanpublic ShiroFilterFactoryBean shiroFilter() {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager());return shiroFilterFactoryBean;}/*** 问题:未登录不会自动跳转到登录页、无权访问页面不跳转* 原因:Shiro注解模式下,登录失败与没有权限都是通过抛出异常,并且默认并没有去处理或者捕获这些异常。* 解决:通过在SpringMVC下配置捕获相应异常来通知用户信息*/@Beanpublic SimpleMappingExceptionResolver simpleMappingExceptionResolver() {SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();Properties properties = new Properties();// 未登录访问接口跳转到/login、登录后没有权限跳转到/401properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "redirect:/login");properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "redirect:/401");simpleMappingExceptionResolver.setExceptionMappings(properties);return simpleMappingExceptionResolver;}
}
8、测试
登录yzm,访问/user/**
登录admin,也是一样的
相关链接
首页
上一篇:整合JWT篇
下一篇:认证流程篇
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!


