shiro学习基础(一)之ee例子

文章目录

  • 前言
  • 什么是shiro?
    • 基础知识
  • 以下教程来源
  • 让配置文件作为安全数据源
  • 让数据库作为数据源
    • 说明
    • 遗留的问题
  • 加上盐值
    • 测试使用md5加密
    • 在数据库中使用一
    • 在数据库中使用二
      • 注意点


前言

这是shiro的初期学习笔记,如有错误,欢迎各位大佬指出错误!

什么是shiro?

引用自百度百科,如下:

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

其实就是一个权限管理的框架。。。。还支持其它的一些特色功能;

参考了 博文shiro详解-shiro史上最全学习笔记,如下:

Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro
可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。其基本功能点如下图所示:

shiro学习基础(一)之ee例子_第1张图片

各个功能具体的解释如下:

Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; Web Support:Web 支持,可以非常容易的集成到 Web 环境; Caching:缓存,比如用户登录后,其用户信息、拥有的角色权限不必每次去查,这样可以提高效率;

Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

基础知识

这个是外部访问shiro的流程
shiro学习基础(一)之ee例子_第2张图片
以下解释来自上面那篇博客,如下:

Subject:
主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject认为是一个门SecurityManager 才是实际的执行者;

SecurityManager:
安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

Realm:
域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager; 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。

以下教程来源

how2j学习教程

让配置文件作为安全数据源

创建一个项目,这个不赘述;
不用添加maven项目,添加如下两个依赖包,如下:
shiro学习基础(一)之ee例子_第3张图片
然后创建一个User类,如下:

package com.test;

public class User {
     
    private String name;
    private String password;

//    创建一个带两个参数的构造函数,为了后面方便

    public User(String name, String password) {
     
        this.name = name;
        this.password = password;
    }

    public String getName() {
     
        return name;
    }

    public void setName(String name) {
     
        this.name = name;
    }

    public String getPassword() {
     
        return password;
    }

    public void setPassword(String password) {
     
        this.password = password;
    }
}

再在这个User类的包下,创建一个shiro.ini的配置文件,如下:
shiro学习基础(一)之ee例子_第4张图片
其中shiro.ini配置文件信息,如下:

#定义用户
[users]
#用户名 zhang3  密码是 12345, 角色是 admin
zhang3 = 12345, admin
#用户名 li4  密码是 abcde, 角色是 产品经理
li4 = abcde,productManager
#定义角色
[roles]
#管理员什么都能做
admin = *
#产品经理只能做产品管理
productManager=addProduct,deleteProduct,editProduct,updateProduct,listProduct
#订单经理只能做订单管理
orderManager=addOrder,deleteOrder,editOrder,updateOrder,listOrder

然后创建一个实现用配置文件作为安全数据源的类,创建一个测试类TestShiro,如下:

package com.test;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

import java.util.ArrayList;
import java.util.List;

/**
 * 这是一个简易的交互模式:
 * 就是在实体类中定义好用户名和密码,角色,权限
 * 然后再在shiro中去认证是否能够匹配登录,和角色权限
 */
public class TestShiro {
     
    public static void main(String[] args) {
     
        /*
        * 设置实体类中的数据
        * */
        User zhang3=new User("zhang3","12345");
        User li4=new User("li4","abcde");
//        注意这个用户在shiro的配置文件中并不存在
        User wang5=new User("wang5","999");

//        将这些用户数据添加到一个List集合中,便于后面判断
        List<User> users=new ArrayList<>();
        users.add(zhang3);
        users.add(li4);
        users.add(wang5);

//        创建实体类中的角色
        List<String> roles=new ArrayList<>();
        roles.add("admin");
        roles.add("productManager");

//        创建实体类中角色的权限
        List<String> permits=new ArrayList<>();
        permits.add("addProduct");
        permits.add("addOrder");

//        登录每一个用户,判断该用户是否已经在shiro中
        for (User user : users) {
     
            if(login(user)){
     
                System.out.printf("%s \t登录成功,用的密码是%s\t",
                        user.getName(),user.getPassword());
                System.out.println();
            }else {
     
                System.out.printf("%s \t登录失败,用的密码是%s\t",
                        user.getName(),user.getPassword());
                System.out.println();
            }
        }
        System.out.println("------------------------------------------------------");

//        判断登录成功的是否具有某种角色
        for (User user : users) {
     
//            首先得获取到登录的
            for ( String role : roles) {
     
                if (login(user)){
     
//                    再判断角色
                    if(isRole(role)){
     
                        System.out.printf("%s\t 拥有角色: %s\t%n",
                                user.getName(),role);
                    }else {
     
                        System.out.printf("%s\t 不拥有角色: %s\t%n",user.getName(),role);
                    }
                }
            }
        }

        System.out.println("------------------------------------------------------");

//        判断登录成功的是否具有某种权限

        for (User user : users) {
     
//            首先得获取到登录的
            for ( String permit : permits) {
     
                if (login(user)){
     
//                    再判断权限
                    if(isPermitted(permit)){
     
                        System.out.printf("%s\t 拥有权限: %s\t%n",
                                user.getName(),permit);
                    }else {
     
                        System.out.printf("%s\t 不拥有权限: %s\t%n",user.getName(),permit);
                    }
                }
            }
        }
    }

    /*
    * 判断当前用户是否具有某种角色
    * */
    private static boolean isRole(String role){
     
       return getSubject().hasRole(role);
    }
    /*
    * 判断当前用户是否具有某种功能
    * */
    private static boolean isPermitted(String permit){
     
       return getSubject().isPermitted(permit);
    }
    /*
    * 获取到当前用户,注意这个对象所属的包是
    * org.apache.shiro.subject.Subject;
    * */
    private static Subject getSubject(){
     
//        创建默认的安全管理实例
        DefaultSecurityManager manager=new DefaultSecurityManager();
//        设置安全管理的配置文件,使用iniRealm去管理这个配置文件
        IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
//        设置该认证,也可以理解为域,安全数据源
        manager.setRealm(iniRealm);
//        将安全管理放入全局对象
        SecurityUtils.setSecurityManager(manager);
//        全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();
        return subject;
    }

    /*
    * 获取到当前用户,
    * 需要登录才知道是当前用户是谁
    * 返回值是:判断是否登录成功
    * */
    private static boolean login(User user){
     
        Subject subject = getSubject();
//        如果已经登录过了,就退出
        if(subject.isAuthenticated()){
     
            subject.logout();
        }
//        创建一个用户,设置用户名和密码
        UsernamePasswordToken upt=new UsernamePasswordToken(user.getName(),user.getPassword());
//        登录,最终是跟Realm中的配置文件对比,是否正确
        try {
     
            subject.login(upt);
        } catch (UnknownAccountException uae){
     
            System.out.println("用户名不存在");
        } catch (IncorrectCredentialsException ice){
     
            System.out.println("密码错误");
        } catch (LockedAccountException lae){
     
            System.out.println("用户被锁定,不能登录");
        } catch (AuthenticationException e){
     
            System.out.println("认证错误,登录失败");
            e.printStackTrace();
            return false;
        }
        return subject.isAuthenticated();
    }
}

运行之后,就能够看到实现简单的交互,如下:
shiro学习基础(一)之ee例子_第5张图片

让数据库作为数据源

上面的Realm数据源,取自在shiro.ini配置文件中来的;接下来的示例是将数据库中的存储数据源作为数据源;

既然是在数据库中取数据源,那就得在数据库中建库建表,并且其数据库的内容是跟原来在数据库中配置的一样的;

创建User表并且插入数据,表数据如下:
shiro学习基础(一)之ee例子_第6张图片
数据是跟原来在配置文件中写的一样的;值得注意的是,鉴于真实的项目考虑,其id的数据类型可以选择bigint,这样的数据范围更加大;

创建的角色表role,如下:
shiro学习基础(一)之ee例子_第7张图片
创建的权限表permission,如下:
shiro学习基础(一)之ee例子_第8张图片
为了让用户拥有的角色以及对应的权限,创建关系表,都是多对多关系,无论是用户还是角色,权限,都可以是多对多关系;

创建用户角色表user_role,如下:
在这里插入图片描述
创建角色权限表role_permission,如下:
shiro学习基础(一)之ee例子_第9张图片

说明

插入的关系,就是跟原配置文件中的关系是一样的,如下:

#定义用户
#[users]
#zhang3 = 12345, admin
#li4 = abcde,productManager
#[roles]
#admin = *
#productManager=addProduct,deleteProduct,editProduct,updateProduct,listProduct
#orderManager=addOrder,deleteOrder,editOrder,updateOrder,listOrder

因为要连接数据库,所以就要添加对数据库的依赖,如下:
shiro学习基础(一)之ee例子_第10张图片

然后在前面的User的基础上添加一个id字段,为了对应数据库中的id字段,如下:

ps:不加id也没事,没用到,我试过了,只不过加上更加好;

package com.test;

public class User {
     
    private int id;
    private String name;
    private String password;

    public int getId() {
     
        return id;
    }

    public void setId(int id) {
     
        this.id = id;
    }

	public User() {
     
    }
    //    创建一个带两个参数的构造函数,为了后面方便

    public User(String name, String password) {
     
        this.name = name;
        this.password = password;
    }

    public String getName() {
     
        return name;
    }

    public void setName(String name) {
     
        this.name = name;
    }

    public String getPassword() {
     
        return password;
    }

    public void setPassword(String password) {
     
        this.password = password;
    }
}

然后再创建一个连接数据库的DAO类,并且该类只做对数据库中的数据的查询,如下:

package com.test.dao;

import java.sql.*;
import java.util.HashSet;
import java.util.Set;

public class DAO {
     
//    在构造函数里面加载驱动,每次调用对象都会调用到
    public DAO(){
     
        try {
     
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        }
    }
//    连接数据库
    public Connection getConnection() throws SQLException {
     
        return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=utf-8",
                "root","admin");
    }

//    根据用户名字获取密码
    public String getPassword(String userName){
     
        String sql="select password from user where name= ?";
        try(
                Connection c=getConnection();
                PreparedStatement ps = c.prepareStatement(sql);
        ) {
     
            ps.setString(1,userName);
            ResultSet rs = ps.executeQuery();
            while(rs.next()){
     
                return rs.getString("password");
            }
        }catch(SQLException e){
     
            e.printStackTrace();
        }
        return null;
    }

//    根据用户名获取角色
    public Set<String> listRoles(String userName){
     
//        创建一个存储角色的集合
        Set<String> roles=new HashSet<>();
        String sql="select r.name from `user` u " +
                "left join user_role ur on u.id=ur.uid " +
                "left join role r on ur.rid=r.id " +
                "where u.name= ?";
        try (
                Connection c = getConnection();
                PreparedStatement ps=c.prepareStatement(sql);
                ){
     
            ps.setString(1,userName);
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
     
                roles.add(rs.getString(1));
            }
        }catch (SQLException e){
     
            e.printStackTrace();
        }
        return roles;
    }

//    根据用户名获取权限
    public Set<String> listPermissions(String userName){
     
        Set<String> permissions = new HashSet<>();
        String sql="select p.name from `user` u " +
                "left join user_role ur on u.id=ur.uid " +
                "left join role_permission rp on ur.rid=rp.rid " +
                "left join permission p on rp.pid=p.id " +
                "where u.name=? ";
        try(
                Connection c = getConnection();
                PreparedStatement ps=c.prepareStatement(sql);
                ){
     
            ps.setString(1,userName);
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
     
                permissions.add(rs.getString(1));
            }
        }catch(SQLException e){
     
            e.printStackTrace();
        }
        return permissions;
    }

    /*
    * 测试是否查询出数据
    * */
    public static void main(String[] args) {
     
        System.out.println(new DAO().listRoles("zhang3"));
        System.out.println(new DAO().listRoles("li4"));
        System.out.println(new DAO().listPermissions("zhang3"));
        System.out.println(new DAO().listPermissions("li4"));
    }
}

测试一下,是否可以查询出数据,如下:
在这里插入图片描述
然后就是集成一个类,这个类属于shiro的类,类似于HttpServlet,该类的两个方法,一个是身份验证doGetAuthenticationInfo,另一个是权限授予doGetAuthenticationInfo
如下:

package com.test;

import com.test.dao.DAO;
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 java.util.Set;

/**
 * 这个类继承了AuthorizingRealm
 * 类似于继承HttpServlet
 * 里面的方法是我们自己提供,但是调用是shiro去调用
 */
public class DataBaseRealm extends AuthorizingRealm {
     
    /*
     * 授权,即权限验证
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principalCollection) {
     
        /*
         * PrincipalCollection是一个身份集合
         * */
//        获取通过doGetAuthenticationInfo认证的用户名
        String userName = (String) principalCollection.getPrimaryPrincipal();
//        根据认证的用户名,在DAO类中获取角色和权限
        Set<String> permissions = new DAO().listPermissions(userName);
        Set<String> roles = new DAO().listRoles(userName);
//        将获取到的角色和权限,放到能够授权的类中,即授权对象
        SimpleAuthorizationInfo sai=new SimpleAuthorizationInfo();
        sai.setStringPermissions(permissions);
        sai.setRoles(roles);
        return sai;
    }


    /*
     * 获取
     * 身份认证功能,也就是登录
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
     
//        获取账号密码
        UsernamePasswordToken t= (UsernamePasswordToken) authenticationToken;
        String userName = t.getPrincipal().toString();
        String password= new String( t.getPassword());

//        获取数据库中的密码
        String passwordDB = new DAO().getPassword(userName);
//        为空账号不存在,不相同的话密码错误,抛出异常,但是不报具体的错误
        if(null==passwordDB||!password.equals(passwordDB)){
     
            throw  new AuthenticationException();
        }
//       将通过认证的账号密码存放到认证信息中,即SimpleAuthenticationInfo
//        getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
        SimpleAuthenticationInfo sai = new
                SimpleAuthenticationInfo(userName, password, getName());
        return sai;
    }
}

在这两个方法中,分别有两个参数,其为类:
AuthenticationToken :用户提交的身份和凭据,如账号密码
PrincipalCollection:用户的身份集合

其中AuthenticationToken参考的博文为:
第六章 Realm及相关对象(二) AuthenticationToken

PrincipalCollection类参考的博文为:
第六章 Realm及相关对象(四) PrincipalCollection

之后再配置shiro.ini的文件,让shiro取Realm的数据,在数据库中去取,如下:

#数据库作为数据源内容
#指定数据源,默认是情况是找IniRealm中的信息
[main]
databaseRealm=com.test.DataBaseRealm
securityManager.realms=$databaseRealm

遗留的问题

最后在TestShiro中,要修改一个方法。。。。
老实说,应该不用修改的!!!但是我一直运行不出来,百度了好久也没有解决。。。只能修改方法,即获取当前用户这个方法,原有的方法,如下:

 /*
    * 获取到当前用户,注意这个对象所属的包是
    * org.apache.shiro.subject.Subject;
    * */
    private static Subject getSubject(){
     
//        创建默认的安全管理实例
        DefaultSecurityManager manager=new DefaultSecurityManager();
//        设置安全管理的配置文件,使用iniRealm去管理这个配置文件
        Realm iniRealm=new IniRealm("classpath:shiro.ini");
//        设置该认证,也可以理解为域,安全数据源
        manager.setRealm(iniRealm);
//        将安全管理放入全局对象
        SecurityUtils.setSecurityManager(manager);
//        全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();
        return subject;
    }

这个方法照理来说,没有问题的,像之前的例子都没有问题。。。。。但是用这个方法,就是无法读取到shiro.ini的数据直指向数据库。。。也不晓得为啥。。。
然后换个工厂方法获取配置文件,如下:

private static Subject getSubject() {
     
        //加载配置文件,并获取工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //获取安全管理者实例
        SecurityManager sm = factory.getInstance();
        //将安全管理者放入全局对象
        SecurityUtils.setSecurityManager(sm);
        //全局对象通过安全管理者生成Subject对象
        Subject subject = SecurityUtils.getSubject();
        return subject;
    }

之后再运行TestShiro,因为跟原来的数据源配置一样,所以没有变化,如下:
shiro学习基础(一)之ee例子_第11张图片

加上盐值

测试使用md5加密

shiro作为一个安全管理的框架,最为重要的就是加密了;
一般都是使用md5的加密方式;注意了,使用md5是一种不可逆的加密方式,也就是说加密之后,无法变为原样。。。。教程是这样,但是世界之大无奇不有,到底能不能变回来,也不一定,我觉得可以使用穷举法。。。算了,这里不展开;

上代码,使用md5生成不可逆的密码,如下:

 /*
    * 使用md5生成不可逆的密码
    * */
    @Test
    public void testMd5(){
     
        String password="123";
        String encodedPassword = new Md5Hash(password).toString();
//        将加密后的md5密码,再次进行加密
        String encodedPassword2 = new Md5Hash(encodedPassword).toString();
        System.out.println("使用shiro中的md5加密一次之后: "+encodedPassword);//结果是202cb962ac59075b964b07152d234b70
        System.out.println("使用shiro中的md5加密两次之后: "+encodedPassword2);//结果是d9b1d7db4cd6e70935368a1efb10e377
    }

结果如下:
在这里插入图片描述

正如上面所说的,不可逆也不一定;我还是找到了一个网站,去解密这个字符串,并且成功了。。。。并且支持其它类型的解密,该CMD5解密如下:
shiro学习基础(一)之ee例子_第12张图片

shiro学习基础(一)之ee例子_第13张图片

无论是加密一次的还是两次的都可以,三次的也可以。。但是需要注册,到这就没有折腾了。。。。

shiro,作为一个安全管理框架,当然不值那一点功能,不然要它何用,其还可以加盐。。。。盐是啥?
盐就是一个随机数。。。。

如下代码:

 @Test
    public void testMd5Salt(){
     
//        定义原始密码
        String password="123";
//        生成一个盐,也就是随机数。。。
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
//        定义加密的次数
        int times=2;
//        定义加密的方式
        String algorithmName="md5";
//        加密.可以打开这个类,构造方法有四种
        String encodedPassword = new SimpleHash(algorithmName, password, salt, times).toString();
        System.out.printf("原始密码是: %s\n加的盐是: %s\n" +
                "加密的次数是: %s\n加密后的密码是: %s\n",password,salt,times,encodedPassword);
    }

运行如下:
shiro学习基础(一)之ee例子_第14张图片
注意这个加入随机数后的密码,使用上面提到的网站是无法破解的;

在数据库中使用一

最终还是得跟数据库交互的;

先在数据库中添加一个字段,就是盐,其实还可以添加一个加密次数的,但是这里未添加,添加语句如下:

alter table `user`
add salt varchar(100);

然后在DAO中添加一个方法,该方法注册用户的同时将用户的密码进行md5加盐的加密,如下:

  public boolean createUser(String name,String password){
     
        String sql="insert into user values(null,?,?,?)";
//        生成一个随机数,也就是盐
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
//        定义加密方式
        String algorithmName="md5";
//        定义加密的次数
        int times=2;
//        将注册的密码加密,使用md5算法
        String encodedPassword = new SimpleHash(algorithmName, password,
                salt, times).toString();
        boolean execute=false;
        try(
                Connection c = getConnection();
                PreparedStatement ps = c.prepareStatement(sql);
                ){
     
            ps.setString(1,name);
            ps.setString(2,encodedPassword);
            ps.setString(3,salt);
            execute= ps.execute();
        }catch (SQLException e){
     
            e.printStackTrace();
        }
        return execute;
    }

还要在DAO中添加一个根据用户名获取用户信息的方法,为了让在DataBaseRealm类中登录的时候,验证账号密码用,如下:

//    获取到用户信息
    public User getUser(String userName){
     
        User user=null;
        String sql="select * from user where name=? ";
        try(
                Connection c = getConnection();
                PreparedStatement ps = c.prepareStatement(sql);
                ){
     
            ps.setString(1,userName);
            ResultSet rs = ps.executeQuery();
            if(rs.next()){
     
                user=new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setPassword(rs.getString("password"));
                user.setSalt(rs.getString("salt"));
            }
        }catch (SQLException e){
     
            e.printStackTrace();
        }
        return user;
    }

之后修改在DataBaseRealm中的身份认证功能方法,将获取到的登录密码,以及配合数据库中的盐值进行加盐比较,判断是否跟数据库中存储的密码相等,如下:


    /*
     * 获取
     * 身份认证功能,也就是登录
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
     
//        获取登录的账号密码
        UsernamePasswordToken t= (UsernamePasswordToken) authenticationToken;
        String userName = t.getPrincipal().toString();
        String password= new String(t.getPassword());

        /*
        * 未加密的密码验证
        * */
//        获取数据库中的密码
//        String passwordDB = new DAO().getPassword(userName);

        /*
        * 已加密的密码验证
        * */
//        获取数据库中的密码
        String passwordDB = new DAO().getPassword(userName);
        User user = new DAO().getUser(userName);
        String salt = user.getSalt();
//        获取到登录密码加密后的结果
        String encodedPassword = new SimpleHash("md5", password, salt, 2).toString();

//        为空账号不存在,不相同的话密码错误,抛出异常,但是不报具体的错误
        if(null==user||!encodedPassword.equals(passwordDB)){
     
            throw  new AuthenticationException();
        }
//       将通过认证的账号密码存放到认证信息中,即SimpleAuthenticationInfo
//        getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
        SimpleAuthenticationInfo sai = new
                SimpleAuthenticationInfo(userName, password, getName());
        return sai;
    }

之后在TestShiro中测试,是否能够登录成功;
当然得先注册一个用户,如下:

 public static void main(String[] args) {
     
//        注册一个用户
       /* new DAO().createUser("wang5","123");
        System.out.println("注册成功");*/

//        然后再验证是否能够登录
        User u=new User("wang5","123");
        if(login(u)){
     
            System.out.println("登录成功");
        }else{
     
            System.out.println("登录失败");
        }

    }

测试结果如下:
在这里插入图片描述
其中,在数据库中已经用了数据,如下:
shiro学习基础(一)之ee例子_第15张图片

在数据库中使用二

前面的将登录的账户密码跟数据库中的账号密码的对比,也就是认证是由我们自己实现的,在DataBaseRealm中自己实现的,可以让shiro帮我们实现,其实现密码的认证使用到的是CredentialsMatcher接口,在我现在使用的1.3.2的shiro中,实现这个接口有三个:SimpleCredentialsMatcher;
HashedCredentialsMatcher;这个类是SimpleCredentialsMatcher子类
PasswordMatcher;
作为一个初学者。。。。。

使用的是HashedCredentialsMatcher;

至于这三个拿来干嘛的,以及关系,这里不深究;

有一篇是说:
SimpleCredentialsMatcher和HashedCredentialsMatcher的区别关系的,可参考;

话不多说,修改上面的例子,将DataBaseRealm中的身份认证方法,修改,如下:

protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws AuthenticationException {
     
//        获取登录的账号密码
        UsernamePasswordToken t= (UsernamePasswordToken) authenticationToken;
        String userName = t.getPrincipal().toString();
        /*
        * 未加密的密码验证
        * */
//        获取数据库中的密码
//        String passwordDB = new DAO().getPassword(userName);

        /*
        * 已加密的密码验证方式二
        * 通过使用shrio.ini里面配置的
        * HashedCredentialsMatcher替我们验证
         * */
//        获取数据库中的用户信息
        User user = new DAO().getUser(userName);
        String passwordDB = user.getPassword();
        String salt = user.getSalt();
//       将通过认证的账号密码存放到认证信息中,即SimpleAuthenticationInfo
//        getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
        SimpleAuthenticationInfo sai = new
                SimpleAuthenticationInfo(userName, passwordDB, ByteSource.Util.bytes(salt),getName());
        return sai;
    }

也就是传递参数给SimpleAuthenticationInfo 这个类,其具体的实现是要在shiro.ini配置的;
我们先对比一下,不使用的shiro帮我们认证的时候,传递给SimpleAuthenticationInfo 这个类的差别,如下:

//不使用,如下:
SimpleAuthenticationInfo sai = new
                SimpleAuthenticationInfo(userName, password, getName());

//使用,如下:
 SimpleAuthenticationInfo sai = new
                SimpleAuthenticationInfo(userName, passwordDB, ByteSource.Util.bytes(salt),getName());

多给了其一个参数,盐值

之后配置shiro.ini,如下:

# 使用shiro自动帮我们验证密码
[main]
#指定Hashed认证配置的名字
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#加密方式
credentialsMatcher.hashAlgorithmName=md5
#加密次数
credentialsMatcher.hashIterations=2
#存储散列后的密码是否为16进制
credentialsMatcher.storedCredentialsHexEncoded=true

databaseRealm=com.test.DataBaseRealm
databaseRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$databaseRealm

然后运行,TestShiro类,其结果还是不变的,如下:
shiro学习基础(一)之ee例子_第16张图片

注意点

配置HashedCredentialsMatcher这个类,有三种方式,具体参考博客如下:
Shiro 之 HashedCredentialsMatcher 认证匹配

你可能感兴趣的:(权限管理,shiro,java,安全)