【Shiro】 一、身份认证

声明

博文的内容学习自B站UP主 编程不良人 (UID:352224540)的教程,感谢能有这么好的教程 

https://www.bilibili.com/video/BV1uz4y197Zm?p=9

 

Shiro

shiro是一个基于java的安全框架,主要提供认证与授权服务。

 

认证——概念

在shiro框架中,系统的访问者被称为 Subject (主体)

  • Subject  的用户名属于 Principal (身份信息)
  • Subject  的密码属于 Credential(凭证信息)
  • Token (令牌) =  Principal  +  Credential  +   ...

每次访问系统时,Subject  都会带着它的令牌给 Shiro 进行 Authentication (身份认证),

Shiro 会派出 SecurityManager(安全管理器) 来处理 Subject 的令牌,

SecurityManager  会到 Realm(数据域)中核实令牌的信息,如果核实通过,意味着认证成功了,则被允许进入系统

 

实操

这里用javaSE来使用shiro,创建maven工程

工程目录结构如下:

Shiro
--shiro-java
----src
------main
--------java
----------com
------------xilo
--------------shiro
----------------TestAuthenticator.java
--------resources
----------shiro.ini
----pom.xml(导入依赖)
--pom.xml

导入shiro依赖

<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.5.3</version>
</dependency>

编写数据文件shiro.ini(这是shiro最原始的数据配置文件,数据都是提前写好的,实际肯定极少用这种文件,要接数据库)

以键值对的形式记录:用户名 = 密码

[users]
jack=001
lucy=002

编写认证类 TestAuthenticator.java

package com.xilo.shiro;

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

public class TestAuthenticator {
    public static void main(String[] args) {
        //1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2、给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3、给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4、关键对象 Subject 主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("jack","001");

        //用户登录(进行验证)
        try{
            subject.login(token);
            System.out.println("认证状态:"+subject.isAuthenticated());
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

到此就可以运行工程了,运行结果:

认证状态:true

如果是填错误信息,会报什么错误呢?

//5、创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("jack001","001");

运行结果:

org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@573fd745] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - jack001, rememberMe=false].
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:184)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
	at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
	at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)
	at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
	at com.xilo.shiro.TestAuthenticator.main(TestAuthenticator.java:27)

可以看到会报出一个异常 UnknownAccountException ,这个异常就代表着用户不存在的异常

接下来我们再试试密码错误的情况

//5、创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("jack","003");

运行结果:

org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - jack, rememberMe=false] did not match the expected credentials.
	at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:603)
	at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:581)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
	at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
	at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)
	at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
	at com.xilo.shiro.TestAuthenticator.main(TestAuthenticator.java:27)

Process finished with exit code 0

可以看到会报出一个异常 IncorrectCredentialsException,这个异常就代表着密码错误的异常。

我们可以利用这两个异常把具体信息反馈给操作者

package com.xilo.shiro;

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.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;

public class TestAuthenticator {
    public static void main(String[] args) {
        //1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2、给安全管理器设置realm
        securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //3、给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4、关键对象 Subject 主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("jack","001");

        //用户登录(进行验证)
        try{
            subject.login(token);
            System.out.println("登录成功");
        }catch (UnknownAccountException e){
            System.out.println("用户"+token.getUsername()+"不存在");
        }catch (IncorrectCredentialsException e){
            System.out.println("用户"+token.getUsername()+"密码错误");
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

 

自定义Realm

在上面的例子中,我们采用了shiro.ini文件的形式作为shiro的Realm,完成对用户的认证,但在实际的开发中是极少用到这种形式的,一般会采用数据库来记录我们的用户信息,所以我们要编写自定义Realm,让Realm去数据库查询相关的数据,完成对用户的信息认证。

下面放出改动后的目录结构

Shiro
--shiro-java
----src
------main
--------java
----------com
------------xilo
--------------realm
----------------CustomerRealm.java(自定义Realm)
--------------shiro
----------------TestAuthenticator.java
----------------TestCustomerRealmAuthenticator.java(使用自定义Realm的shiro)
--------resources
----------shiro.ini
----pom.xml
--pom.xml

实现自定义的Realm,就要继承AuthorizingRealm,重载两个重要的方法:doGetAuthenticationInfo(用于认证) 和 doGetAuthorizationInfo(用于授权,这个后面再说)

CustomerRealm.java

package com.xilo.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //认证操作
        return null;
    }
}

TestCustomerRealmAuthenticator.java

package com.xilo.shiro;

import com.xilo.realm.CustomerRealm;
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.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestCustomerRealmAuthenticator {
    public static void main(String[] args) {
        //1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2、给安全管理器设置【自定义realm】
        securityManager.setRealm(new CustomerRealm());
        //3、给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //4、关键对象 Subject 主体
        Subject subject = SecurityUtils.getSubject();
        //5、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("jack","001");

        //用户登录(进行验证)
        try{
            subject.login(token);
            System.out.println("认证状态:"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            System.out.println("用户"+token.getUsername()+"不存在");
        }catch (IncorrectCredentialsException e){
            System.out.println("用户"+token.getUsername()+"密码错误");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

这时候,我们写的自定义Realm的认证内容是空的,返回null

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //认证操作
        return null;
    }

当我们仍然可以运行一下 TestCustomerRealmAuthenticator.java ,看看结果怎么样

用户jack不存在

预料之内,没有找到用户的相关信息,我们可以验证一下 doGetAuthenticationInfo 是否真的做了认证的工作。

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String)token.getPrincipal();
        System.out.println("doGetAuthenticationInfo---用户名:"+principal);
        return null;
    }

运行之后可以看到 doGetAuthenticationInfo 拿到了用户登录的token

doGetAuthenticationInfo---用户名:jack
用户jack不存在

我们可以在 doGetAuthenticationInfo 中用token去验证数据库中的数据(利用jdbc、jpa、Mybatis等)

在这里,我们先用 SimpleAuthenticationInfo 模拟数据库里的用户信息,完成密码的校验

Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String)token.getPrincipal();
        System.out.println("doGetAuthenticationInfo---用户名:"+principal);
        if("jack".equals(principal)){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","123",this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }

运行之后:

doGetAuthenticationInfo---用户名:jack
用户jack密码错误

贴出CustomerRealm.java 完整代码

package com.xilo.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String)token.getPrincipal();
        System.out.println("doGetAuthenticationInfo---用户名:"+principal);
        if("jack".equals(principal)){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","123",this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

 

密码加密处理

在上面我们简单模拟了用shiro认证用户的操作,但其中有个地方是与实际不太切合的,就是密码的存储是以明文的形式存储,这样会造成很大的安全隐患,所以我们要考虑给密码进行加密处理,这里推荐用MD5算法

MD5 算法:

是不可逆的加密算法,只能明文转成密文,不能密文转成明文,而且相同内容的明文会转成相同的密文。

密文是一个16进制的32位长度的字符串。

可以对任何文件的内容进行加密。

经过了MD5加密之后,得到的密文确实安全一点,但为了让密文更复杂,我们可以通过加Salt(盐) 到密码明文中,然后再进行MD5加密,当然在用户注册的同时也要记录下Salt的值

Salt 加盐:

在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。

试用MD5(下面使用MD5Hash,是具有hash散列算法的MD5,也是shiro推荐使用的)

package com;

import org.apache.shiro.crypto.hash.Md5Hash;

public class TestMD5Hash {
    public static void main(String[] args) {

        //MD5加密
        Md5Hash md5Hash = new Md5Hash("001");
        System.out.println(md5Hash.toHex());

        //MD5 + salt 加密(默认散列1次)
        md5Hash = new Md5Hash("001","01*1334px");
        System.out.println(md5Hash.toHex());

        //MD5 + salt + hash(散列1024次) 加密
        md5Hash = new Md5Hash("001","01*1334px",1024);
        System.out.println(md5Hash.toHex());
    }
}

 运行结果

dc5c7986daef50c1e02ab09b442ee34f
fd5382bdb301d6e2bc916e06f6455607
923391846b52b2391ba3a3cfca311ab9

保留上面的结果和盐值,下面还要用 

 

自定义带MD5的Realm

下面我们就把MD5的密码验证加入到shiro中。

改动后的目录结构:

Shiro
--shiro-java
----src
------main
--------java
----------com
------------xilo
--------------realm
----------------CustomerRealm.java
----------------CustomerMD5Realm.java(MD5Realm)
--------------shiro
----------------TestAuthenticator.java
----------------TestCustomerRealmAuthenticator.java
----------------TestCustomerMD5RealmAuthenticator.java(使用自定义MD5Realm的shiro)
--------resources
----------shiro.ini
----pom.xml
--pom.xml

CustomerMD5Realm.java (注意下面的密码是经过MD5处理过的

package com.xilo.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

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

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String principal = (String)token.getPrincipal();
        System.out.println("doGetAuthenticationInfo---用户名:"+principal);
        if("jack".equals(principal)){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","dc5c7986daef50c1e02ab09b442ee34f",this.getName());
            return simpleAuthenticationInfo;
        }
        return null;
    }
}

TestCustomerMD5RealmAuthenticator.java 

package com.xilo.shiro;

import com.xilo.realm.CustomerMD5Realm;
import com.xilo.realm.CustomerRealm;
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.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;

public class TestCustomerMD5RealmAuthenticator {
    public static void main(String[] args) {
        //1、创建安全管理器对象
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        //2、创建Realm
        CustomerMD5Realm realm = new CustomerMD5Realm();

        //3、设置Realm使用hash凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        realm.setCredentialsMatcher(credentialsMatcher);

        //4、给安全管理器设置【自定义realm】
        securityManager.setRealm(realm);
        //5、给全局安全工具类设置安全管理器
        SecurityUtils.setSecurityManager(securityManager);
        //6、关键对象 Subject 主体
        Subject subject = SecurityUtils.getSubject();
        //7、创建令牌
        UsernamePasswordToken token = new UsernamePasswordToken("jack","001");

        //用户登录(进行验证)
        try{
            subject.login(token);
            System.out.println("认证状态:"+subject.isAuthenticated());
        }catch (UnknownAccountException e){
            System.out.println("用户"+token.getUsername()+"不存在");
        }catch (IncorrectCredentialsException e){
            System.out.println("用户"+token.getUsername()+"密码错误");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

运行:

doGetAuthenticationInfo---用户名:jack
认证状态:true

认证通过,下面再试试加盐验证,要修改一下 CustomerMD5Realm.java (修改密码,添加盐)

    if("jack".equals(principal)){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","fd5382bdb301d6e2bc916e06f6455607", ByteSource.Util.bytes("01*1334px"),this.getName());
            return simpleAuthenticationInfo;
        }

运行之后仍然成功(这里就不贴结果了),那再加上散列呢?

修改一下 CustomerMD5Realm.java (修改密码)

    if("jack".equals(principal)){
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("jack","923391846b52b2391ba3a3cfca311ab9", ByteSource.Util.bytes("01*1334px"),this.getName());
            return simpleAuthenticationInfo;
        }

还要修改一下 TestCustomerMD5RealmAuthenticator.java (设置散列次数)

        //3、设置Realm使用hash凭证匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        //散列1024次
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);

运行之后仍然成功(这里就不贴结果了)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值