zoukankan      html  css  js  c++  java
  • Shiro-身份验证

    身份验证

    身份验证,即在应用中谁能证明他就是本人,一般提供如他们的身份ID一些标识信息来表明他就是本人,如提供用户名/密码来证明。

    在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:

    prinipals:身份,即主体的标识属性,可以是任何东西,如用户名,邮箱等,维一即可。一个主体可以有多个principals,但是只有一个primary principals,一般是用户名/密码/手机号。

    credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

    最长久的principals和credentials组合就是用户名/密码了。

    另外两个相关的概念是之前提到的Subject以及Realm,分别是主体及验证主体的数据源。

    环境准备:

    新建Maven工程->添加Maven依赖:

    <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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>20171119</groupId>
      <artifactId>Shiro</artifactId>
      <version>0.0.1-SNAPSHOT</version>
       
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>1.3.2.RELEASE</version>
          <relativePath></relativePath>
      </parent>
     <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
        
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-nop</artifactId>
        <version>1.7.6</version>
    </dependency>
    </dependencies>
    </project>

    登录/退出

    1.首先准备一些用户身份/凭据(shiro.ini)

    [users]
    zhang=123
    wang=123

    此处使用ini配置文件,通过[users]指定了两个主体:zhang/123,wang/123.

    2.测试用例

    LoginLogoutTest.java

    package com.fpc.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.Factory;
    import org.junit.Test;
    
    import junit.framework.Assert;
    
    public class LoginLogoutTest {
        @Test
        public void testHelloworld() {
            //1.获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
            Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            //2.得到SecurityManager实例后并且绑定给SecurityUtils
            org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            //3.得到Subject以及创建用户名/密码身份验证Token(即用户身份/凭证)
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wang","123");
            try {
                //4.登录,即验证身份
                subject.login(token);
                System.out.println("登录成功");
            } catch(AuthenticationException e) {
                //抛出异常则,表明验证失败
                System.out.println("登录失败");
            }
            
            Assert.assertEquals(true,subject.isAuthenticated());
            subject.logout();
        }
    }

    2.1、首先通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;

    2.2、接着获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;

    2.3、通过SecurityUtils得到Subject,其会自动绑定到当前线程;如果在web环境在请求结束时需要解除绑定;然后获取身份验证的Token,如用户名/密码;

    2.4、调用subject.login方法进行登录,其会自动委托给SecurityManager.login方法进行登录;

    2.5、如果身份验证失败请捕获AuthenticationException或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

    2.6、最后可以调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出。

    验证步骤

    1、收集用户身份/凭证,即如用户名/密码;

    2、调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功;

    3、最后调用Subject.logout进行退出操作。

    如上测试的几个问题

    1、用户名/密码硬编码在ini配置文件,以后需要改成如数据库存储,且密码需要加密存储;

    2、用户身份Token可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/邮箱/手机号同时登录。

    Realm

    Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。如我们之前的ini配置方式将使用org.apache.shiro.realm.text.IniRealm。

    Realm接口的一些方法:

        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
            // TODO Auto-generated method stub
            return null;
        }
    
        public String getName() {
            // TODO Auto-generated method stub
            return null;
        }
    
        public boolean supports(AuthenticationToken arg0) {
            // TODO Auto-generated method stub
            return false;
        }

    1.单Realm配置

    自定义Realm实现:CusRealm.java

    package com.fpc.realm;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.realm.Realm;
    
    public class CusRealm implements Realm {
    
        @Override
        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
            // TODO Auto-generated method stub
            String username = (String)arg0.getPrincipal();//得到用户名
            String password = new String((char[])arg0.getCredentials());//得到密码
            if( !username.equals("zhang")) {
                throw new UnknownAccountException();//用户名错误
            } 
            if (!password.equals("123")) {
                throw new IncorrectCredentialsException();//密码错误
            }
            //如果不抛出异常,则身份验证成功,返回一个AuthenticationInfo实现
            return new SimpleAuthenticationInfo(username,password,getName());
        }
    
        @Override
        public String getName() {
            // TODO Auto-generated method stub
            return "CusRealm";
        }
    
        @Override
        public boolean supports(AuthenticationToken arg0) {
            // TODO Auto-generated method stub
            //仅支持UsernamePasswordToken类型的Token
            return arg0 instanceof UsernamePasswordToken;
        }
    
    }

    ini配置文件指定自定义Realm实现(shiro-realm.ini),通过$name来引入之前的realm定义

    [main]
    CusRealm=com.fpc.realm.CusRealm
    securityManager.realms=$CusRealm

    测试用例参考上面的LoginLogoutTest.java:把之前的shiro.ini替换成shiro-realm.ini即可。

    @Test
        public void testCustomRealm() {
            Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
            org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("wang","123");
            try {
                subject.login(token);
                System.out.println("登录成功");
            } catch (AuthenticationException e) {
    //            e.printStackTrace();
                System.out.println("登录失败");
            }
        }

    2.多Realm配置

    1.ini配置文件(shiro-multi-realm.ini)

    [main]
    CusRealm1=com.fpc.realm.CusRealm1
    CusRealm2=com.fpc.realm.CusRealm2
    securityManager.realms=$CusRealm1,$CusRealm2

    securityManager会按照realms指定的顺序进行身份验证。此处我们使用显示指定顺序的方式指定了Realm的顺序,如果删除“securityManager.realms=$CusRealm1,$CusRealm2”,那么securityManager会按照realm声明的顺序进行使用(即无需设置realms属性,其会自动发现),当我们显示指定的realm后,其他没有指定的realm也不会被忽略,如果“securityManager.realms=$CusRealm1”,那么CusRealm2也会被自动设置进去。

    当我们设置shiro-multi-realm.ini为下面这样时:

    [main]
    CusRealm1=com.fpc.realm.CusRealm1
    CusRealm1=com.fpc.realm.CusRealm2
    securityManager.realms=$CusRealm1

    我们的CusRealm1和CusRealm2中的内容如下:

    CusRealm1.java:

    package com.fpc.realm;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.realm.Realm;
    
    public class CusRealm1 implements Realm {
    
        @Override
        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
            // TODO Auto-generated method stub
            String username = (String)arg0.getPrincipal();//得到用户名
            String password = new String((char[])arg0.getCredentials());//得到密码
            
            if( !username.equals("wang")) {
                throw new UnknownAccountException();//用户名错误
            } 
            if (!password.equals("123")) {
                throw new IncorrectCredentialsException();//密码错误
            }
            //如果不抛出异常,则身份验证成功,返回一个AuthenticationInfo实现
            return new SimpleAuthenticationInfo(username,password,getName());
        }
    
        @Override
        public String getName() {
            // TODO Auto-generated method stub
            return "CusRealm";
        }
    
        @Override
        public boolean supports(AuthenticationToken arg0) {
            // TODO Auto-generated method stub
            //仅支持UsernamePasswordToken类型的Token
            return arg0 instanceof UsernamePasswordToken;
        }
    
    }

    CusRealm2.java:

    package com.fpc.realm;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.Realm;
    
    public class CusRealm2 implements Realm{
    
        @Override
        public String getName() {
            // TODO Auto-generated method stub
            return "CusRealm2";
        }
    
        @Override
        public boolean supports(AuthenticationToken token) {
            // TODO Auto-generated method stub
            return token instanceof UsernamePasswordToken;
        }
    
        @Override
        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // TODO Auto-generated method stub
            String username = (String) token.getPrincipal();
            String password = new String((char[])token.getCredentials());
            if (!username.equals("zhang")) {
                throw new UnknownAccountException();
            }
            if ( !password.equals("123")) {
                throw new IncorrectCredentialsException();
            }
            return new SimpleAuthenticationInfo(username,password,getName());
        }
    
    }

    由上面的代码可知,能通过CusRealm1验证的只有wang/123,能通过CusRealm2验证的只有zhang/123。再看看我们的测试类中的代码:

    @Override
        public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // TODO Auto-generated method stub
            String username = (String) token.getPrincipal();
            String password = new String((char[])token.getCredentials());
            if (!username.equals("zhang")) {
                throw new UnknownAccountException();
            }
            if ( !password.equals("123")) {
                throw new IncorrectCredentialsException();
            }
            return new SimpleAuthenticationInfo(username,password,getName());
        }

    我们测试的用户token是zhang/123,那么我们运行看一下到的是否会登录成功?

    也就是说,只要再ini文件中声明的Realm,即使没有指定,其也不会被忽略。

    那么我们再尝试将CusRealm2在ini文件中的声明删除?,ini文件如下:

    [main]
    CusRealm1=com.fpc.realm.CusRealm1
    securityManager.realms=$CusRealm1

    再次运行LoginLogoutTest.java

    这也证明了,Realm能否被识别,只跟它在ini文件中有没有被声明有关系,跟它有没有被指定没关系。

    JDBC Realm使用

    1.使用mysql数据库和druid连接池;要使用mysql,需要在Maven项目的pom.xml中添加mysql连接所需的依赖:

    <dependency>  
        <groupId>mysql</groupId>  
        <artifactId>mysql-connector-java</artifactId>  
        <version>5.1.25</version>  
    </dependency>  
    <dependency>  
        <groupId>com.alibaba</groupId>  
        <artifactId>druid</artifactId>  
        <version>0.2.23</version>  
    </dependency>

    2.到数据库shiro下建三张表:user(用户名/密码),user_roles(用户/角色),roles_permissions(角色/权限):

    DROP DATABASE IF EXISTS shiro;
    CREATE DATABASE shiro;
    USE shiro;
    
    CREATE TABLE users(
        id BIGINT AUTO_INCREMENT,
        username VARCHAR(100),
        PASSWORD VARCHAR(100),
        password_salt VARCHAR(100),
        CONSTRAINT pk_users PRIMARY KEY(id)
    );
    
    CREATE UNIQUE INDEX idx_users_username ON users(username);
    
    CREATE TABLE user_roles(
     id BIGINT AUTO_INCREMENT,
     username VARCHAR(100),
     role_name VARCHAR(100),
     CONSTRAINT pk_user_roles PRIMARY KEY(id)
    );
    
    CREATE UNIQUE INDEX idx_user_roles ON user_roles(username, role_name);
    
    CREATE TABLE roles_permissions(
      id BIGINT AUTO_INCREMENT,
      role_name VARCHAR(100),
      permission VARCHAR(100),
      CONSTRAINT pk_roles_permissions PRIMARY KEY(id)
    ) CHARSET=utf8 ENGINE=INNODB;
    CREATE UNIQUE INDEX idx_roles_permissions ON roles_permissions(role_name, permission);
    
    INSERT INTO users(username,PASSWORD)VALUES('zhang','123');

    3.ini配置(shiro-jdbc-realm.ini)

    jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
    dataSource=com.alibaba.druid.pool.DruidDataSource
    dataSource.driverClassName=com.mysql.jdbc.Driver
    dataSource.url=jdbc:mysql://10.0.20.252:3306/shiro
    dataSource.username=root
    dataSource.password=Free-Wi11
    jdbcRealm.dataSource=$dataSource
    securityManager.realms=$jdbcRealm

    1.变量名=全限定类名会自动创建一个类实例

    2.变量名.属性=值 自动调用相应的setter方法进行赋值

    3.$变量名 引用之前的一个对象实例

    4.编写测试方法:

    @Test
        public void testJdbcRealm() {
            Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");
            org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("zhang","123");
            try {
                subject.login(token);
                System.out.println("登录成功");
            } catch (AuthenticationException e) {
    //            e.printStackTrace();
                System.out.println("登录失败");
            }
            Assert.assertEquals(true,subject.isAuthenticated());
            subject.logout();
        }

    运行结果:

    如果更改数据库中的密码再运行呢?

  • 相关阅读:
    jQuery EasyUI API 中文文档 可调整尺寸
    jQuery EasyUI API 中文文档 链接按钮(LinkButton)
    jQuery EasyUI API 中文文档 手风琴(Accordion)
    jQuery EasyUI API 中文文档 表单(Form)
    jQuery EasyUI API 中文文档 组合(Combo)
    jQuery EasyUI API 中文文档 布局(Layout)
    jQuery EasyUI API 中文文档 拆分按钮(SplitButton)
    jQuery EasyUI API 中文文档 菜单按钮(MenuButton)
    jQuery EasyUI API 中文文档 搜索框
    jQuery EasyUI API 中文文档 验证框(ValidateBox)
  • 原文地址:https://www.cnblogs.com/fangpengchengbupter/p/7871942.html
Copyright © 2011-2022 走看看