Shiro的使用(五)—SpringBoot整合shiro实现多Realm控制

0、场景介绍

场景:假设在我们的系统中 有两个角色 用户 和 管理员,用户有100W,管理员只有 2个

如果设计到一张表 ,那么查询管理员的时候,最坏情况需要遍历 100W+ 次,所以适合设置在两张表中。

现在的管理员登陆:通过用户名查询 用户数据(100W+)

如果是我们能够将这两个角色的数据设计到两张表里面

管理员的表,用户的表 假设我们都使用shiro来进行认证 ? 怎么办? 设计思路?

​ 就是在用户登陆时候 设置一个 登陆类型

​ 重写 UserNameAndPasswordToken 在执行正式的登陆的时候,根据登陆的类型选择 我们要使用的Realm, 最终去进行认证 那么就可以实现这个功能

存在两个问题

​ 1:在哪里去做选择 执行哪一个realm?

​ 思路:ModularRealmAuthenticator.java类进行拓展(直接和realm打交道的)

​ 2:我们的登陆类型如何来定

​ 思路:可以在控制器中确定

1、基本环境搭建

1.1、导入依赖
	<dependencies>
        <!-- druid连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <!-- SpringBoot和shiro的整合 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
1.2、配置 application.properties
server.port=8080
spring.profiles.active=dev
spring.application.name=springboot
# mybatis配置
mybatis.type-aliases-package=com.fu.shiro.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
# druid连接池配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///demo
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# thymeleaf 配置
spring.thymeleaf.cache=false
1.3、编写实体类

提前建好数据库和表,按照表创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -5599110107028997228L;

    private Integer id;
    private String name;
    private String password;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin implements Serializable {
    private static final long serialVersionUID = -947346458740614718L;

    private Integer id;
    private String adminname;
    private String pasword;
}
1.4、编写service和mapper

User

@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserByName(String name) {
        return userMapper.getUserByName(name);
    }
}
public interface UserMapper {

    @Select("select * from user where name = #{name}")
    User getUserByName(String name);
}

Admin

@Service
@Transactional
public class AdminServiceImpl implements AdminService {

    @Autowired
    private AdminMapper adminMapper;

    @Override
    public Admin getAdminByName(String name) {
        return adminMapper.getAdminByName(name);
    }
}
public interface AdminMapper {

    @Select("select * from admin where adminname = #{name}")
    public Admin getAdminByName(String name);
}
1.5、创建Controller
@Controller
public class UserController {
    private Logger logger = LoggerFactory.getLogger(UserController.class);

    @RequestMapping("userLogin")
    public String userLogin(User user, Model model) {
        logger.info("进入到 userLogin 控制器...");

        return "user-index";
    }
}
@Controller
public class AdminController {

    private Logger logger = LoggerFactory.getLogger(AdminController.class);

    @RequestMapping("adminLogin")
    public String adminLogin(Admin admin, Model model) {
        logger.info("进入到 adminLogin 控制器...");

        return "admin-index";
    }
}
// 做页面跳转使用
@Controller
public class AppController {

    @RequestMapping("toLogin")
    public String toLogin() {
        return "login";
    }
}
1.6、创建 realm
public class UserRealm extends AuthorizingRealm {
    @Override
    public String getName() {
        return "UserRealm";
    }
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}
public class AdminRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "AdminRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        return null;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}
1.7、编写配置类
@SpringBootConfiguration
@ComponentScan("com.fu.shiro")
@MapperScan("com.fu.shiro.mapper")
public class Application {
}

shiro

@SpringBootConfiguration
public class ShiroConfig {

    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     *  UserRealm
     */
    @Bean
    public UserRealm userRealm() {
        logger.info("UserRealm 执行了。。。");
        return new UserRealm();
    }
    /**
     * AdminRealm
     */
    @Bean
    public AdminRealm adminRealm() {
        logger.info("AdminRealm 执行了。。。");
        return new AdminRealm();
    }
    /**
     *  配置安全管理器
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        Collection<Realm> realms = new ArrayList<>();
        realms.add(userRealm());
        realms.add(adminRealm());
        defaultWebSecurityManager.setRealms(realms);
        logger.info("SecurityManager 执行了。。。");
        return defaultWebSecurityManager;
    }
    /**
     * 配置shiro的过滤器
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("/toLogin", "anon");
        map.put("/userLogin", "anon");
        map.put("/adminLogin", "anon");
        map.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        logger.info("ShiroFilterFactoryBean 执行了。。。");
        return shiroFilterFactoryBean;
    }
}
1.8、编写页面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录窗口</title>
</head>
<body>
    <h3>普通用户登录</h3>
    <form action="/userLogin" method="post">
        用户名:<input type="text" name="name"><br>
        密码:<input type="text" name="password"><br>
        <input type="submit" value="用户登录">
    </form>
    <hr>
    <h3>管理员登录</h3>
    <form action="/adminLogin" method="post">
        用户名:<input type="text" name="adminname"><br>
        密码:<input type="text" name="password"><br>
        <input type="submit" value="管理员登录">
    </form>
</body>
</html>

user-index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>用户登录成功</h1>
</body>
</html>

admin-index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>管理员登录成功</h1>
</body>
</html>

2、编写核心代码

2.1、创建 LoginType
public enum LoginType {
    USER("User"), ADMIN("Admin");

    // 登录类型
    private String loginType;
    LoginType(String loginType) {
        this.loginType = loginType;
    }
    @Override
    public String toString() {
        return this.loginType;
    }
}
2.2、创建 CustomToken
public class CustomToken extends UsernamePasswordToken {
    private static final long serialVersionUID = 9203340807922471069L;

    private String loginType;

    public CustomToken(String name, String password, String loginType) {
        super(name, password);
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }
}
2.3、创建 CustomModularRealmAuthenticator
  • 编写 自定义 realm 身份验证器(CustomModularRealmAuthenticator),继承于 ModularRealmAuthenticator ,因为在 shiro 的源码中,只有此类直接与 选择realm 有关联,详情参见 shiro源码详解—使用shiro进行登录验证 ,我们重写 doAuthenticate 方法,实现根据需求选择 realm 进行认证。
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 做Realm的校验,校验realm是否为空
        assertRealmsConfigured();
        // 获取前端传递过来的token(经过自定token修饰)
        CustomToken customToken = (CustomToken) authenticationToken;
        // 获取登录类型
        String loginType = customToken.getLoginType();
        // 根据类型判断选择哪一个realm
        // 获取所有的realm
        Collection<Realm> realms = getRealms();
        // 将登录类型所对应的所有realm 获取到
        Collection<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            // 将realm类型和现在登录的类型做一个对比
            if (realm.getName().contains(loginType)) {
                typeRealms.add(realm);
            }
        }
        if (typeRealms.size() == 1) {
            return doSingleRealmAuthentication(typeRealms.iterator().next(), customToken);
        } else {
            return doMultiRealmAuthentication(typeRealms, customToken);
        }
    }
}
2.4、配置自定义认证器

在 ShiroConfig.java 中配置

	/**
     *  配置自定义认证器
     */
    @Bean
    public CustomModularRealmAuthenticator authenticator() {
        logger.info("CustomModularRealmAuthenticator 自定义认证器执行了...");
        return new CustomModularRealmAuthenticator();
    }


	/**
     *  配置安全管理器
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        // 设置身份验证器,一定要设置在realm之前,否则获取不到realm,getRealms() 为空
        defaultWebSecurityManager.setAuthenticator(authenticator());

        Collection<Realm> realms = new ArrayList<>();
        realms.add(userRealm());
        realms.add(adminRealm());
        defaultWebSecurityManager.setRealms(realms);
        logger.info("SecurityManager 执行了。。。");
        return defaultWebSecurityManager;
    }
2.5、修改 controller

UserController.java

@Controller
public class UserController {

    //用户登陆的类型
    private static final String LOGIN_TYPE= LoginType.USER.toString();

    private Logger logger = LoggerFactory.getLogger(UserController.class);

    @RequestMapping("userLogin")
    public String userLogin(User user) {
        logger.info("进入到 userLogin 控制器...");
        // 封装请求对象为token
        CustomToken customToken = new CustomToken(user.getName(), user.getPassword(), LOGIN_TYPE);
        // 获取登录主体
        Subject subject = SecurityUtils.getSubject();
        // 登录
        try {
            subject.login(customToken);
            if (subject.isAuthenticated()) {
               // 如果认证成功
               return "user-index";
            }
        } catch (UnknownAccountException e) {
            logger.info("用户名不正确");
        } catch (IncorrectCredentialsException e) {
            logger.info("密码不正确");
        } catch (Exception e) {
            logger.info("其他错误造成了登陆错误");
        }
        return "login";
    }
}

AdminController.java

@Controller
public class AdminController {

    private Logger logger = LoggerFactory.getLogger(AdminController.class);

    private static final String LOGIN_TYPE = LoginType.ADMIN.toString();

    @RequestMapping("adminLogin")
    public String adminLogin(Admin admin) {
        logger.info("进入到 adminLogin 控制器...");
        // 封装前端传递的数据到 token
        CustomToken customToken = new CustomToken(admin.getAdminname(), admin.getPassword(), LOGIN_TYPE);
        // 获取登录主体
        Subject subject = SecurityUtils.getSubject();
        // 登录
        try {
            subject.login(customToken);
            if (subject.isAuthenticated()) {
                return "admin-index";
            }
        } catch (UnknownAccountException e) {
            logger.info("用户名不正确");
        } catch (IncorrectCredentialsException e) {
            logger.info("密码不正确");
        } catch (Exception e) {
            logger.info("其他错误造成了登陆错误");
        }
        return "login";
    }
}
2.6、修改 realm

UserRealm.java

public class UserRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private UserService userService;

    @Override
    public String getName() {
        return "UserRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        logger.info("UserRealm 执行了...");
        String userName = (String) authenticationToken.getPrincipal();
        User user = userService.getUserByName(userName);
        if (null == user) {
            return null;
        }
        return new SimpleAuthenticationInfo(user.getName(), user.getPassword(), getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

AdminRealm.java

public class AdminRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(AdminRealm.class);

    @Autowired
    private AdminService adminService;

    @Override
    public String getName() {
        return "AdminRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        logger.info("AdminRealm 执行了...");
        // 获取用户名
        String adminname = (String) authenticationToken.getPrincipal();
        Admin admin = adminService.getAdminByName(adminname);
        if (null == admin) {
            return null;
        }
        return new SimpleAuthenticationInfo(admin.getAdminname(), admin.getPassword(), getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

3、总结

  1. 注意编写完自定义认证器后,需要在 ShiroConfig 中进行配置。
  2. 在安全管理中设置 身份验证器 时,一定要设置在realm之前,否则获取不到realm,getRealms() 为空。
  3. 多 realm 的控制,主要是通过对 ModularRealmAuthenticator 类中 doAuthenticate方法 的重写,按照我们自己需要的逻辑来进行配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值