springboot整合shiro实现多realm不同数据表登陆

11 篇文章 0 订阅
6 篇文章 0 订阅

shiro是一个很好的登陆以及权限管理框架,但是默认是单realm单数据表,如果业务中用户分布在不同的数据表,单realm就很难实现登陆以及权限管理的功能,这篇博客就简单的介绍一个家长 学生 老师的账号分布在不同的数据表情况下,shiro的多realm登陆验证,使用springboot,mybatis mysql等相关技术,博客底部附上源码,有兴趣的可以去下载

 

1.项目pom依赖

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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>1.3.2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

2.用户数据表

CREATE DATABASE /*!32312 IF NOT EXISTS*/`springboot_shiro_mulit_relam` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `springboot_shiro_mulit_relam`;

/*Table structure for table `parent` */

DROP TABLE IF EXISTS `parent`;

CREATE TABLE `parent` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `account` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

/*Data for the table `parent` */

insert  into `parent`(`id`,`name`,`account`,`password`) values (1,'张家长','123','123');

/*Table structure for table `student` */

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `account` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

/*Data for the table `student` */

insert  into `student`(`id`,`name`,`account`,`password`) values (1,'王同学','456','456');

/*Table structure for table `teacher` */

DROP TABLE IF EXISTS `teacher`;

CREATE TABLE `teacher` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `account` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

/*Data for the table `teacher` */

insert  into `teacher`(`id`,`name`,`account`,`password`) values (1,'刘老师','789','789');

3.shiro 配置

实现多realm的关键在于继承shiro默认的UsernamePasswordToken,添加一个字段用于标识不同的realm,用户登录的时候带上标识的字段,shiro则根据字段去不同的realm去验证登陆,授权等.

3.1 继承UsernamePasswordToken添加loginType属性

public class UserToken extends UsernamePasswordToken {
	private String loginType;

    public UserToken() {}

    public UserToken(final String username, final String password, 
            final String loginType) {
        super(username, password);
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }
}

 3.2 登陆时使用继承后的UserToken,添加loginType属性

@RequestMapping("/studentLogin")
	public Student studentLogin(@RequestParam(value = "account") String account,
								@RequestParam(value = "password") String password, 
								HttpServletRequest request,
								HttpServletResponse response) {

		System.out.println("\n学生登陆");
		Student student = null;
		//设置永不过期
		SecurityUtils.getSubject().getSession().setTimeout(-1000L);
		Subject subject = SecurityUtils.getSubject();
		try {
			// 调用安全认证框架的登录方法
			subject.login(new UserToken(account, password, "Student"));
			student = studentService.getStudentByAccount(account);
		} catch (AuthenticationException ex) {
			System.out.println("登陆失败: " + ex.getMessage());
		}
		return student;

	}

3.3 获得AuthenticationToken,判断是单realm还是多realm,分别去不同的方法验证

public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
	
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        System.out.println("UserModularRealmAuthenticator:method doAuthenticate() execute ");
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();
        // 强制转换回自定义的CustomizedToken
        UserToken userToken = (UserToken) authenticationToken;
        // 登录类型
        String loginType = userToken.getLoginType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        List<Realm> typeRealms = new ArrayList<>();
        for (Realm realm : realms) {
            if (realm.getName().contains(loginType)) {
                typeRealms.add(realm);
            }
        }

        // 判断是单Realm还是多Realm
        if (typeRealms.size() == 1){
            System.out.println("doSingleRealmAuthentication() execute ");
            return doSingleRealmAuthentication(typeRealms.get(0), userToken);
        }
        else{
            System.out.println("doMultiRealmAuthentication() execute ");
            return doMultiRealmAuthentication(typeRealms, userToken);
        }
    }
}

3.4 配置明文密码登陆,方便测试

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
	
	 
	 @Override  
     public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {  
         UsernamePasswordToken token = (UsernamePasswordToken) authcToken;  

//       Object tokenCredentials = encrypt(String.valueOf(token.getPassword()));  
         //明文密码
         Object tokenCredentials = String.valueOf(token.getPassword());  
         Object accountCredentials = getCredentials(info);  
         //将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false  
         return equals(tokenCredentials, accountCredentials);  
     }  

     /**
      * @author Adam
      * @description 将传进来密码加密方法
      * @date 11:26 2018/9/2
      * @param [data]
      * @return java.lang.String
      */
     private String encrypt(String data) {
         //这里可以选择自己的密码验证方式 比如 md5或者sha256等
         String sha384Hex = new Sha384Hash(data).toBase64();
         return sha384Hex;  
     }  
}

3.5 shiroconfig 配置 (将三个用户realm 注入)

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/image/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/html/**", "anon");

        //测试开放的接口
        filterChainDefinitionMap.put("/login/**", "anon");

        filterChainDefinitionMap.put("/favicon.ico", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//        filterChainDefinitionMap.put("/**", "authc");
        filterChainDefinitionMap.put("/**", "anon");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login/unauth");
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    @Bean(name = "parentRealm")
    public ParentRealm parentRealm(){
        ParentRealm parentRealm = new ParentRealm();
        parentRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
        return parentRealm;
    }
    
    @Bean(name = "teacherRealm")
    public TeacherRealm teacherRealm(){
    	TeacherRealm teacherRealm = new TeacherRealm();
    	teacherRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
        return teacherRealm;
    }
    
    @Bean(name = "studentRealm")
    public StudentRealm studentRealm(){
    	StudentRealm studentRealm = new StudentRealm();
    	studentRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
        return studentRealm;
    }
    


    @Bean(name = "SecurityManager")
    public SecurityManager securityManager(){
    	
    	DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    	//设置realm.
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> realms = new ArrayList<>();
        //添加多个Realm
        realms.add(parentRealm());
        realms.add(teacherRealm());
        realms.add(studentRealm());
        securityManager.setRealms(realms);

        return securityManager;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        //数据库异常处理
        mappings.setProperty("DatabaseException", "databaseError");
        mappings.setProperty("UnauthorizedException","/403");
        r.setExceptionMappings(mappings);
        r.setDefaultErrorView("error");
        r.setExceptionAttribute("ex");
        return r;
    }
    
    /**
     * 系统自带的Realm管理,主要针对多realm
     * */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        //自己重写的ModularRealmAuthenticator
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

3.6 realm 注入service层,去数据库中查找数据,验证密码

public class ParentRealm extends AuthorizingRealm {
	
	@Autowired
	private ParentService parentService;
	
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return authorizationInfo;
    }

    /**
     * @Author Adam
     * @Description  主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
     * @Date 14:06 2018/9/28
     * @Param [token]
     * @return org.apache.shiro.authc.AuthenticationInfo
     **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String account = (String)token.getPrincipal();
        Object credentials = token.getCredentials();
		System.out.println("credentials="+credentials);
        //通过username从数据库中查找对象
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
		Parent parent = parentService.getParentByAccount(account);
        System.out.println("----->>parent="+parent);
        if(parent == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
        		parent,
        		parent.getPassword(),
                getName()
        );
        return authenticationInfo;
    }

}
public class StudentRealm extends AuthorizingRealm {
	
	@Autowired
    private StudentService studentService;

    /**
     * @Author Adam
     * @Description  权限配置
     * @Date 14:29 2018/9/28
     * @Param [principals]
     * @return org.apache.shiro.authz.AuthorizationInfo
     **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return authorizationInfo;
    }

    /**
     * @Author Adam
     * @Description  主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
     * @Date 14:24 2018/9/28
     * @Param [token]
     * @return org.apache.shiro.authc.AuthenticationInfo
     **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String account = (String)token.getPrincipal();
        Object credentials = token.getCredentials();
		System.out.println("credentials="+credentials);
        //通过username从数据库中查找对象
        Student student = studentService.getStudentByAccount(account);
        System.out.println("----->>student="+student);
        if(student == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
        		student,
        		student.getPassword(),
                getName()
        );
        return authenticationInfo;
    }

}
public class TeacherRealm extends AuthorizingRealm {
	
	@Autowired
    private TeacherService teacherService;

    /**
     * @Author Adam
     * @Description  权限配置
     * @Date 14:09 2018/9/28
     * @Param [principals]
     * @return org.apache.shiro.authz.AuthorizationInfo
     **/
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return authorizationInfo;
    }

    /**
     * @Author Adam
     * @Description  主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确
     * @Date 14:08 2018/9/28
     * @Param [token]
     * @return org.apache.shiro.authc.AuthenticationInfo
     **/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

    	System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
        //获取用户的输入的账号.
        String account = (String)token.getPrincipal();
        Object credentials = token.getCredentials();
        System.out.println("credentials="+credentials);
        //通过username从数据库中查找对象
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        Teacher teacher = teacherService.getTeacherByAccount(account);
        System.out.println("----->>teacher="+teacher);
        if(teacher == null){
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
        		teacher,
        		teacher.getPassword(),
                getName()
        );
        return authenticationInfo;
    }

}

4.实现效果

5.源码

https://github.com/Yanyf765/springboot-shiro-mulit-realm

  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值