Shiro - Shiro の紹介; Shiro と Spring Security の違い; Spring Boot は Shiro を統合します

一、Shiro

以下百度百科事典より引用

shiro (Java セキュリティ フレームワーク) - 百度百科事典

Apache Shiro は、認証、承認、パスワード、およびセッション管理を実行する、強力で使いやすい Java セキュリティ フレームワークです。Shiro のわかりやすい API を使用すると、最小のモバイル アプリケーションから最大の Web およびエンタープライズ アプリケーションまで、あらゆるアプリケーションをすばやく簡単に取得できます。

(1) 主な機能

3 つのコア コンポーネント: Subject、SecurityManager、および Realms

1、件名:

つまり、「現在の操作ユーザー」です。ただし、Shiro では、サブジェクトの概念は人だけを指すのではなく、サード パーティのプロセス、バックグラウンド アカウント (デーモン アカウント) などを指す場合もあります。それは単に「現在ソフトウェアと対話しているもの」を意味します。

サブジェクトは現在のユーザーのセキュリティ操作を表し、SecurityManager はすべてのユーザーのセキュリティ操作を管理します。

2、セキュリティマネージャー:

典型的な Facade モードである Shiro フレームワークのコアであり、Shiro は SecurityManager を通じて内部コンポーネント インスタンスを管理し、それを通じてセキュリティ管理のためのさまざまなサービスを提供します。

3、レルム:

Realm は、Shiro とアプリケーション セキュリティ データ間の「ブリッジ」または「コネクタ」として機能します。つまり、ユーザーに対して認証 (ログイン) と承認 (アクセス制御) の検証を実行するとき、Shiro はアプリケーションによって構成されたレルムからユーザーとその許可情報を検索します。

この意味で、Realm は基本的にセキュリティ関連の DAO です。Realm はデータ ソースの接続の詳細をカプセル化し、必要に応じて関連データを Shiro に提供します。Shiro を構成するときは、認証および/または承認用に少なくとも 1 つのレルムを指定する必要があります。複数のレルムを構成することは可能ですが、少なくとも 1 つ必要です。

Shiro には、LDAP、リレーショナル データベース (JDBC)、INI に似たテキスト構成リソース、プロパティ ファイルなど、多数の安全なデータ ソース (別名ディレクトリ) に接続できる組み込みの Realm があります。デフォルトの Realm がニーズを満たさない場合は、カスタム データ ソースを表す独自の Realm 実装をプラグインすることもできます。

(2)基本機能のポイント

  • 認証: ID 認証/ログイン、ユーザーが対応する ID を持っているかどうかを確認します。
  • 認可: 認可、つまりパーミッションの検証は、認証されたユーザーが特定のパーミッションを持っているかどうかを検証します; つまり、ユーザーが何かを実行できるかどうかを判断します: 特定のユーザーが特定の役割を持っているかどうかを検証するなど。または、ユーザーが特定のリソースに対して特定の権限を持っているかどうかをきめ細かく検証します。
  • セッション 管理: セッション管理、つまり、ユーザーがログインした後のセッションであり、そのすべての情報はログアウト前のセッションにあります; セッションは通常の JavaSE 環境または Web 環境にあります。
  • 暗号化: データのセキュリティを保護するための暗号化。たとえば、平文ではなくパスワードを暗号化してデータベースに保存します。
  • Web サポート: Web サポートは、Web 環境に簡単に統合できます。
  • キャッシング: キャッシング。たとえば、ユーザーがログインした後、そのユーザー情報、役割/権限を毎回チェックする必要がないため、効率が向上します。
  • 並行性: Shiro は、マルチスレッド アプリケーションの並行検証をサポートしています。つまり、1 つのスレッドで別のスレッドが開かれている場合、アクセス許可は自動的に伝達されます。
  • テスト: テストのサポートを提供します。
  • Run As : ユーザーが別のユーザーになりすますことを許可します (許可されている場合)。
  • 次回からログインする必要はありません

(3)特徴

1. わかりやすい Java Security API

2. シンプルな ID 認証 (ログイン)、複数のデータ ソース (LDAP、JDBC、Kerberos、ActiveDirectory など) のサポート

3. 役割の単純な署名権 (アクセス制御)、きめ細かい署名権をサポート

4.アプリケーションのパフォーマンスを向上させるために一次キャッシュをサポート

5. Webおよび非Web環境に適した組み込みのPOJOベースのエンタープライズセッション管理

6. 異種クライアント セッション アクセス

7. 非常にシンプルな暗号化 API

8. フレームワークやコンテナにバンドルされておらず、独立して実行できます

2.ShiroとSpring Securityの違い

(一)スプリングセキュリティ

Spring Security は、Spring ベースの Java Web アプリケーションを保護するための柔軟で強力な認証およびアクセス制御フレームワークです。

Spring Security は、Spring ベースのアプリケーションの認証と承認のサポートを保証する重量級のセキュリティ フレームワークです。Spring MVC とうまく統合されており、一般的なセキュリティ アルゴリズムの実装がバンドルされています。

Spring Security は、主に Authentication (認証、あなたが誰であるかを解決する) と Access Control (アクセス制御、つまり、何を許可されているか、Authorization とも呼ばれます) を実装します。Spring Security は、認証と承認をアーキテクチャ的に分離し、拡張ポイントを提供します。「認証」とは、ユーザー、デバイス、またはシステムのいずれかであるユーザーの要求された役割を確立するプロセスです。「認証」とは、ユーザーがアプリケーションでアクションを実行できることを指します。役割は、承認の決定に至る前の認証プロセス中に確立されます。

特徴

Shiro が達成できること、Spring Security は基本的に Spring システムに依存して達成できますが、Spring ファミリー バケットのメンバーであり、統合がより適しているという利点があります。シロ(でも普通はシロで十分)

(2) ShiroとSpring Securityの違い

1. Shiro は Spring Security よりも使いやすい、つまり実装が簡単であり、基本的な認可と認証には Shiro で十分です。

2. Spring Security コミュニティの方がサポート度が高い (ただし、Spring Security のインストールが難しい) Spring コミュニティは、サポートと更新の保守に利点があり、Spring との組み合わせに適しています。

3. Shiro は強力で、シンプルで、柔軟です。Apache の下のプロジェクトはより信頼性が高く、フレームワークやコンテナーにバインドされておらず、独立して実行できます。

個人的な理解

Shiro が最初の選択肢です。すぐに使用でき、十分に使用でき、自由度が高く、Spring Security の一部もあります。Shiro も基本的にはそれを持っています (このプロジェクトでは Spring を使用していません。心配する必要はありません。Shiro に行ってください)。

開発プロジェクトがSpringを使用する場合は、Spring Securityを使用した方が適切かもしれません.Spring Securityはより複雑ですが、Springファミリーとの結合力が強く、安心して選択できるフレームワーク構造です.

3.Spring Boot は Shiro を統合します

(1) Shiroの環境を作る

1. 共通の SpringBoot Web プロジェクトを作成する

2. Shiro の依存関係を追加する

<!-- Shiro 依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>

完全な pom.xml ファイル

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

    <groupId>com.company</groupId>
    <artifactId>shiro-boot</artifactId>
    <version>1.0.0</version>

    <name>shiro-boot</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <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>

        <!-- Shiro 依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.8.0</version>
        </dependency>

    </dependencies>

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

(2) シロの設定

1. 構成クラス ShiroConfig を定義する

/**
 * /当前类是一个Spring的配置类,用于模拟Spring的配置文件
 *
 * @Configuration: 表示当前类作为配置文件使用(配置容器,放在类的上面;等同 xml 配置文件,可以在其中声明 bean )
 * @Bean: 将对象注入到Spring容器中(类似<bean>标签,放在方法上面);不指定对象的名称,默认是方法名是 id
 */
@Configuration
public class ShiroConfig {
    /**
     * 1、Subject:
     * 即“当前操作用户”。它仅仅意味着“当前跟软件交互的东西”。
     * Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
     *
     * 2、SecurityManager:
     * 它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
     *
     * 3、Realm:
     * Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
     */

    /**
     * 配置Shiro的安全管理器
     * @Bean: 将对象注入到Spring容器中(类似<bean>标签,放在方法上面);不指定对象的名称,默认是方法名是 id
     */
    @Bean
    public SecurityManager securityManager(Realm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置一个Realm,这个Realm是最终用于完成我们的认证号和授权操作的具体对象
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    /**
     * 配置一个自定义的Realm的bean,最终将使用这个bean返回的对象来完成我们的认证和授权
     */
    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    /**
     * 配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
     * 如什么样的请求可以访问,什么样的请求不可以访问等等
     */
    @Bean
    public ShiroFilterFactoryBean  shiroFilterFactoryBean(SecurityManager securityManager){
        //创建Shiro的拦截的拦截器 ,用于拦截我们的用户请求
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();

        //设置Shiro的安全管理,设置管理的同时也会指定某个Realm 用来完成我们权限分配
        shiroFilter.setSecurityManager(securityManager);
        //用于设置一个登录的请求地址,这个地址可以是一个html或jsp的访问路径,也可以是一个控制器的路径
        //作用是用于通知Shiro我们可以使用这里路径转向到登录页面,但Shiro判断到我们当前的用户没有登录时就会自动转换到这个路径
        //要求用户完成成功
        shiroFilter.setLoginUrl("/");
        //登录成功后转向页面,由于用户的登录后期需要交给Shiro完成,因此就需要通知Shiro登录成功之后返回到那个位置
        shiroFilter.setSuccessUrl("/success");
        //用于指定没有权限的页面,当用户访问某个功能是如果Shiro判断这个用户没有对应的操作权限,那么Shiro就会将请求
        //转向到这个位置,用于提示用户没有操作权限
        shiroFilter.setUnauthorizedUrl("/noPermission");

        //定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问,什么样的请求不可以访问
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>();

        // /login 表示某个请求的名字;anon 表示可以使用游客进行登录(这个请求不需要登录)
        filterChainMap.put("/login","anon");

        //我们可以在这里配置所有的权限规则,这列数据需要从数据库中读取出来

        //或者在控制器中添加Shiro的注解
        /**
         /admin/**  表示一个请求名字的通配, 以admin开头的任意子路径下的所有请求
         authc 表示这个请求需要进行认证(登录),只有认证(登录)通过才能访问
         注:
            ** 表示任意子路径
            *  表示任意的一个路径
            ? 表示 任意的一个字符
         */
        filterChainMap.put("/admin/**","authc");
        filterChainMap.put("/user/**","authc");

        //表示所有的请求路径全部都需要被拦截登录,这个必须必须写在Map集合的最后面,这个选项是可选的
        //如果没有指定/** 那么如果某个请求不符合上面的拦截规则Shiro将方行这个请求
        filterChainMap.put("/**","authc");

        shiroFilter.setFilterChainDefinitionMap(filterChainMap);

        return shiroFilter;
    }
}

2. MyRealm クラスをカスタマイズする

/**
 * 自定义Realm,用来实现用户的认证和授权
 * Realm:父类抽象类
 */
public class MyRealm implements Realm {

    @Override
    public String getName() {
        return null;
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return false;
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}

3. UserController クラスを定義する

@Controller
public class UserController {
    @RequestMapping("/")
    public String index(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        return "redirect:/success";
    }

    @RequestMapping("/success")
    public String success(){
        return "success";
    }

    @RequestMapping("/noPermission")
    public String noPermission(){
        return "noPermission";
    }

    @RequestMapping("/user/test")
    public @ResponseBody
    String userTest(){
        return "这是userTest请求";
    }

    @RequestMapping("/admin/test")
    public @ResponseBody String adminTest(){
        return "这是adminTest请求";
    }

    @RequestMapping("/admin/add")
    public @ResponseBody String adminAdd(){
        Subject subject= SecurityUtils.getSubject();
        return "这是adminAdd请求";
    }
}

4. login.html、nopermission.html、success.html を定義します。

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>

<form action="login" method="post">
    账号<input type="text" name="username"><br>
    密码<input type="text" name="password" id="password"><br>
    <input type="submit" value="登录" id="loginBut">
</form>
<span style="color: red" th:text="${errorMessage}"></span>

</body>

</html>

success.html 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>

<h1>登录成功</h1>

</body>

</html>

nopermission.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>

<h1>对不起!您没有权限操作!</h1>

</body>

</html>

 5. テストを開始し、  ブラウザにhttp://localhost:8080/と入力します

http://localhost:8080/success と入力しても、クリックしてログインしても応答しません。 

訪問しても返事がない

ここで、ShiroConfig クラスをコメントアウトすると、 

//表示所有的请求路径全部都需要被拦截登录,这个必须必须写在Map集合的最后面,这个选项是可选的
//如果没有指定/** 那么如果某个请求不符合上面的拦截规则Shiro将方行这个请求
//filterChainMap.put("/**","authc");

 

再度 localhost:8080 にアクセスして、成功ページにジャンプします 

(3) Shiro認証アカウントの設定

1. MyAuthenticatingRealm クラスをカスタマイズする

/**
 * 自定义Realm,用来实现用户的认证和授权
 * AuthenticatingRealm 只负责认证(登录)的Realm父类
 */
public class MyAuthenticatingRealm extends AuthenticatingRealm {
    /**
     * Shiro的认证方法我们需要在这个方法中来获取用户的信息(从数据库中)
     * @param authenticationToken   用户登录时的Token(令牌),这个对象中将存放着我们用户在浏览器中存放的账号和密码
     * @return 返回一个AuthenticationInfo 对象,这个返回以后Shiro会调用这个对象中的一些方法来完成对密码的验证 密码是由Shiro进行验证是否合法
     * @throws AuthenticationException   如果认证失败,Shiro就会抛出AuthenticationException 也可以手动抛出这个AuthenticationException
     *      以及它的任意子异常类,不同的异常类型对应认证过程中的不同错误情况,我们需要根据异常类型来为用户返回特定的响应数据
     * AuthenticationException  异常的子类  可以自己抛出
     *      AccountException  账号异常  可以自己抛出
     *      UnknownAccountException  账号不存在的异常  可以自己抛出
     *      LockedAccountException   账号异常锁定异常  可以自己抛出
     *      IncorrectCredentialsException  密码错误异常(这个异常会在Shiro进行密码验证时抛出)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //将AuthenticationToken强转成UsernamePasswordToken 这样获取账号和密码更加的方便
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //获取用户在浏览器中输入的账号
        String userName = token.getUsername();

        //认证账号,正常情况我们需要这里从数据库中获取账号的信息,以及其他关键数据,例如账号是否被冻结等等
        String dbUserName = userName;

        if(!"admin".equals(dbUserName) && !"zhangsan".equals(dbUserName)){//判断用户账号是否正确
            throw  new UnknownAccountException("账号错误");
        }
        if("zhangsan".equals(userName)){
            throw  new LockedAccountException("账号被锁定");
        }
        //定义一个密码(这个密码应该来自数据库)
        String dbpassword = "123456";

        /**
         * 创建密码认证对象,由Shiro自动认证密码
         * 参数1  数据库中的账号(页面账号也可)
         * 参数2  数据库中的密码
         * 参数3  当前Relam的名字
         * 如果密码认证成功,会返回一个用户身份对象;如果密码验证失败则抛出异常
         */
        //认证密码是否正确
        return new SimpleAuthenticationInfo(dbUserName,dbpassword,getName());
    }
}

2. UserController クラスを変更します。

@RequestMapping("/login")
    public String login(String username, String password, Model model){
        //创建一个shiro的Subject对象,利用这个对象来完成用户的登录认证
        Subject subject = SecurityUtils.getSubject();

        //判断当前用户是否已经认证过,如果已经认证过着不需要认证;如果没有认证过则完成认证
        if(!subject.isAuthenticated()){
            //创建一个用户账号和密码的Token对象,并设置用户输入的账号和密码
            //这个对象将在Shiro中被获取
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            try {
                //如账号不存在或密码错误等等,需要根据不同的异常类型来判断用户的登录状态,并给予友好的信息提示
                //调用login后,Shiro就会自动执行自定义的Realm中的认证方法
                subject.login(token);
            } catch (UnknownAccountException e) {
                //表示用户的账号错误,这个异常是在后台抛出
                System.out.println("---------------账号不存在");
                model.addAttribute("errorMessage","账号不存在");
                return "login";
            }catch (LockedAccountException e){
                //表示用户的账号被锁定,这个异常是在后台抛出
                System.out.println("===============账号被锁定");
                model.addAttribute("errorMessage","账号被冻结");
                return "login";
            }catch (IncorrectCredentialsException e){
                //表示用户的密码错误,这个异常是shiro在认证密码时抛出
                System.out.println("***************密码不匹配");
                model.addAttribute("errorMessage","密码错误");
                return "login";
            }
        }
        return "redirect:/success";
    }

(4) 認証キャッシュ

一度ログインに成功したら、ここで任意のアカウントとパスワードを入力して「戻る」をクリックしますが、どのような情報を入力しても、何も入力せずに「ログイン」ボタンを直接クリックしても、Shiro は認証成功と見なします。これは、Shiro がログインに成功した後、Shiro のキャッシュにデータが書き込まれてしまうため、ログイン要求のコントローラーで、認証されたかどうかを判断する前にログアウト操作を追加し、キャッシュがクリアされるようにしました。ログインは繰り返しテストできます

ユーザーコントローラーを変更する 

/创建一个shiro的Subject对象,利用这个对象来完成用户的登录认证
Subject subject= SecurityUtils.getSubject();

//登出方法调用,用于清空登录时的缓存信息,否则无法重复登录
subject.logout();

ログアウトする前に、subject.isAuthenticated() ステータスが true であることを確認してください

ログアウト後に subject.isAuthenticated() ステータスが false であることを確認する

(5) パスワード暗号化

1. シンプルなバックエンド暗号化

1. MyRealm クラスを変更する

/**
 * 自定义Realm,用来实现用户的认证和授权
 * AuthenticatingRealm 只负责认证(登录)的Realm父类
 */
public class MyAuthRealm extends AuthenticatingRealm {

/**
     * AuthenticatingRealm
     * Shiro的认证方法我们需要在这个方法中来获取用户的信息(从数据库中)
     * @param authenticationToken   用户登录时的Token(令牌),这个对象中将存放着我们用户在浏览器中存放的账号和密码
     * @return 返回一个AuthenticationInfo 对象,这个返回以后Shiro会调用这个对象中的一些方法来完成对密码的验证 密码是由Shiro进行验证是否合法
     * @throws AuthenticationException   如果认证失败,Shiro就会抛出AuthenticationException 也可以手动抛出这个AuthenticationException
     *      以及它的任意子异常类,不同的异常类型对应认证过程中的不同错误情况,我们需要根据异常类型来为用户返回特定的响应数据
     * AuthenticationException  异常的子类  可以自己抛出
     *      AccountException  账号异常  可以自己抛出
     *      UnknownAccountException  账号不存在的异常  可以自己抛出
     *      LockedAccountException   账号异常锁定异常  可以自己抛出
     *      IncorrectCredentialsException  密码错误异常(这个异常会在Shiro进行密码验证时抛出)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //将AuthenticationToken强转成UsernamePasswordToken 这样获取账号和密码更加的方便
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //获取用户在浏览器中输入的账号
        String userName = token.getUsername();
        String password = new String(token.getPassword());//获取页面中的用户密码实际工作中基本不需要获取
        System.out.println("账号:" + userName + " ----- 密码:" + password);

        //认证账号,正常情况我们需要这里从数据库中获取账号的信息,以及其他关键数据,例如账号是否被冻结等等
        String dbUserName = userName;

        if(!"admin".equals(dbUserName) && !"zhangsan".equals(dbUserName)){//判断用户账号是否正确
            throw  new UnknownAccountException("账号错误");
        }
        if("zhangsan".equals(userName)){
            throw  new LockedAccountException("账号被锁定");
        }
        //定义一个密码(这个密码应该来自数据库)
        String dbpassword = "123456";

        /**
         * 数据密码加密主要是防止数据在浏览器访问后台服务器之间进行数据传递时被篡改或被截获,因此应该在前端到后台的过程中
         *      进行加密,而这里的加密方式是将浏览器中获取后台的明码加密和对数据库中的数据进行加密
         * 这就丢失了数据加密的意义 因此不建议在这里进行加密,应该在页面传递传递时进行加密
         * 注:
         *   建议浏览器传递数据时就加密数据,数据库中存在的数据也是加密数据,必须保证前端传递的数据
         *      和数据主库中存放的数据加密次数以及盐规则都是完全相同的,否则认证失败
         */
        //设置让当前登录用户中的密码数据进行加密
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1);
        this.setCredentialsMatcher(credentialsMatcher);

        /**
         * 密码加密
         * 参数 1 为加密算法 我们选择MD5加密
         * 参数 2 为被加密的数据的数据
         * 参数 3 为加密时的盐值 ,用于改变加密后数据结果
         *     通常这个盐值需要选择一个表中唯一的数据,如表中的账号
         * 参数 4 为需要对数据使用指定的算法加密多少次
         */
        //对数据库中的密码进行加密
        Object obj = new SimpleHash("MD5",dbpassword,"",1);

        /**
         * 创建密码认证对象,由Shiro自动认证密码
         * 参数1  数据库中的账号(页面账号也可)
         * 参数2  数据库中的密码
         * 参数3  当前Relam的名字
         * 如果密码认证成功,会返回一个用户身份对象;如果密码验证失败则抛出异常
         */
        //认证密码是否正确
        return new SimpleAuthenticationInfo(dbUserName,obj.toString()/*dbpassword*/,getName());
    }



public static void main(String[] args) {
        //使用Shiro提供的工具类对数据进行加密
        //参数 1 为加密算法名 我们使用MD5这是一个不可逆的加密算法
        //参数 2 为需要加密的数据
        //参数 3 加密的盐值 用于改变加密结果的 不同的盐加密的数据是不一致的
        //参数 4 为加密的次数
        Object obj = new SimpleHash("MD5","123456","",1);
        System.out.println("123456使用MD5加密1次----   "+obj);
        Object obj2 = new SimpleHash("MD5","123456","",2);
        System.out.println("123456使用MD5加密2次----   "+obj2);
        Object obj3 = new SimpleHash("MD5","e10adc3949ba59abbe56e057f20f883e","",1);
        System.out.println("123456使用MD5加密1次后在对这个数据加密1次----   "+obj3);

        Object obj4 = new SimpleHash("MD5","123456","admin",1);
        System.out.println("123456使用MD5 加盐admin 加密1次----   "+obj4);
        Object obj5 = new SimpleHash("MD5","123456","admin1",1);
        System.out.println("123456使用MD5 加盐admin1 加密1次----   "+obj5);
    }
}

2. フロントエンドとバックエンドの暗号化 

1. MyRealm クラスを変更する

/**
 * 自定义Realm,用来实现用户的认证和授权
 * AuthenticatingRealm 只负责认证(登录)的Realm父类
 * AuthorizingRealm    负责认证(登录)和授权 的Realm父类
 */
public class MyAuthRealm extends AuthenticatingRealm/*AuthorizingRealm*/ {
    /**
     * AuthenticatingRealm
     * Shiro的认证方法我们需要在这个方法中来获取用户的信息(从数据库中)
     * @param authenticationToken   用户登录时的Token(令牌),这个对象中将存放着我们用户在浏览器中存放的账号和密码
     * @return 返回一个AuthenticationInfo 对象,这个返回以后Shiro会调用这个对象中的一些方法来完成对密码的验证 密码是由Shiro进行验证是否合法
     * @throws AuthenticationException   如果认证失败,Shiro就会抛出AuthenticationException 也可以手动抛出这个AuthenticationException
     *      以及它的任意子异常类,不同的异常类型对应认证过程中的不同错误情况,我们需要根据异常类型来为用户返回特定的响应数据
     * AuthenticationException  异常的子类  可以自己抛出
     *      AccountException  账号异常  可以自己抛出
     *      UnknownAccountException  账号不存在的异常  可以自己抛出
     *      LockedAccountException   账号异常锁定异常  可以自己抛出
     *      IncorrectCredentialsException  密码错误异常(这个异常会在Shiro进行密码验证时抛出)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //将AuthenticationToken强转成UsernamePasswordToken 这样获取账号和密码更加的方便
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        //获取用户在浏览器中输入的账号
        String userName = token.getUsername();
        String password = new String(token.getPassword());//获取页面中的用户密码实际工作中基本不需要获取
        System.out.println("账号:" + userName + " ----- 密码:" + password);

        //认证账号,正常情况我们需要这里从数据库中获取账号的信息,以及其他关键数据,例如账号是否被冻结等等
        String dbUserName = userName;

        if(!"admin".equals(dbUserName) && !"zhangsan".equals(dbUserName)){//判断用户账号是否正确
            throw  new UnknownAccountException("账号错误");
        }
        if("zhangsan".equals(userName)){
            throw  new LockedAccountException("账号被锁定");
        }
        //定义一个密码(这个密码应该来自数据库)
        //String dbpassword = "123456";

        /**
         * 数据密码加密主要是防止数据在浏览器访问后台服务器之间进行数据传递时被篡改或被截获,因此应该在前端到后台的过程中
         *      进行加密,而这里的加密方式是将浏览器中获取后台的明码加密和对数据库中的数据进行加密
         * 这就丢失了数据加密的意义 因此不建议在这里进行加密,应该在页面传递传递时进行加密
         * 注:
         *   建议浏览器传递数据时就加密数据,数据库中存在的数据也是加密数据,必须保证前端传递的数据
         *      和数据主库中存放的数据加密次数以及盐规则都是完全相同的,否则认证失败
         */
        //设置让当前登录用户中的密码数据进行加密
        /*HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(1);
        this.setCredentialsMatcher(credentialsMatcher);*/

        /**
         * 密码加密
         * 参数 1 为加密算法 我们选择MD5加密
         * 参数 2 为被加密的数据的数据
         * 参数 3 为加密时的盐值 ,用于改变加密后数据结果
         *     通常这个盐值需要选择一个表中唯一的数据,如表中的账号
         * 参数 4 为需要对数据使用指定的算法加密多少次
         */
        //对数据库中的密码进行加密
        //Object obj = new SimpleHash("MD5",dbpassword,"",1);

        //数据库中 MD5 加密之后密码
        String dbMD5Password = "e10adc3949ba59abbe56e057f20f883e";

        /**
         * 创建密码认证对象,由Shiro自动认证密码
         * 参数1  数据库中的账号(页面账号也可)
         * 参数2  数据库中的密码
         * 参数3  当前Relam的名字
         * 如果密码认证成功,会返回一个用户身份对象;如果密码验证失败则抛出异常
         */
        //认证密码是否正确
        return new SimpleAuthenticationInfo(dbUserName,dbMD5Password/*obj.toString()*//*dbpassword*/,getName());
    }


    public static void main(String[] args) {
        //使用Shiro提供的工具类对数据进行加密
        //参数 1 为加密算法名 我们使用MD5这是一个不可逆的加密算法
        //参数 2 为需要加密的数据
        //参数 3 加密的盐值 用于改变加密结果的 不同的盐加密的数据是不一致的
        //参数 4 为加密的次数
        Object obj = new SimpleHash("MD5","123456","",1);
        System.out.println("123456使用MD5加密1次----   "+obj);
        Object obj2 = new SimpleHash("MD5","123456","",2);
        System.out.println("123456使用MD5加密2次----   "+obj2);
        Object obj3 = new SimpleHash("MD5","e10adc3949ba59abbe56e057f20f883e","",1);
        System.out.println("123456使用MD5加密1次后在对这个数据加密1次----   "+obj3);

        Object obj4 = new SimpleHash("MD5","123456","admin",1);
        System.out.println("123456使用MD5 加盐admin 加密1次----   "+obj4);
        Object obj5 = new SimpleHash("MD5","123456","admin1",1);
        System.out.println("123456使用MD5 加盐admin1 加密1次----   "+obj5);
    }
}

2.login.htmlを修正

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script th:src="@{|/js/jquery-1.11.3.min.js|}"></script>
    <script th:src="@{|/js/jQuery.md5.js|}"></script>
    <script>
        $(function(){
            $("#loginBut").bind("click",function(){
                var v_md5password=$.md5($("#password").val());
                alert(v_md5password)
                $("#md5Password").val(v_md5password)
            })
        })
    </script>
</head>
<body>
<form action="login" method="post">
    账号<input type="text" name="username" id="username"><br>
<!--    密码<input type="text"name="password" id="password"><br>-->
    密码<input type="text" id="password"><br>
    <input type="hidden" name="password" id="md5Password">
    <input type="submit" value="登录" id="loginBut">
</form>
<span style="color: red" th:text="${errorMessage}"></span>

</body>
</html>

ノート:

1. 通常、データベースに保存されるパスワード データは、プレーン コード 123456 ではなく、暗号化されたデータであるべきです。

For example, e10adc3949ba59abbe56e057f20f883e, this is 123456 after using MD5 encryption. データベースのパスワードがすでに暗号化されている場合は、ここで暗号化しないことを選択できます。

2. データベース内のパスワードが暗号化されている場合、ページでデータを送信する前にパスワードを暗号化する必要があります。そうしないと、ログインが失敗する可能性があります

3. 暗号化された送信が選択されている場合、ページとデータベースのパスワード暗号化時間とソルトは同じである必要があります。そうでない場合、ログインは失敗する必要があります

(6) 権限の分配

ユーザーのアクセス許可を使用できるかどうかを判断するには、アクセス許可を割り当てる前にまずユーザーにアクセス許可を割り当てる必要があります. MyRealm クラスで構成し、継承された親クラスを変更する必要があります.

1. MyRealm クラスから継承した親クラスを AuthorizingRealm クラスに変更し、抽象メソッド doGetAuthorizationInfo を実装する

/**
 * 自定义Realm,用来实现用户的认证和授权
 * AuthenticatingRealm 只负责认证(登录)的Realm父类
 * AuthorizingRealm    负责认证(登录)和授权 的Realm父类
 */
public class MyAuthRealm extends AuthorizingRealm {

    /**
     * AuthorizingRealm
     * Shiro用户授权的回调方法
     * @param principalCollection
     * @return
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //从Shiro中获取用户名
        Object username = principalCollection.getPrimaryPrincipal();
        //创建一个SimpleAuthorizationInfo类的对象,利用这个对象需要设置当前用户的权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        //创建角色信息的集合
        Set<String> roles = new HashSet<String>();

        //根据账号到数据库中获取用户所对应的所有角色信息,并初始化到roles集合中
        if("admin".equals(username)){
            roles.add("admin");
            roles.add("user");
        }else if ("zhangsan".equals(username)){
            roles.add("user");
        }
        Set<String>psermission = new HashSet<String>();
        if("admin".equals(username)){
            psermission.add("admin:add");
        }
        //设置角色信息
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(psermission);
        return simpleAuthorizationInfo;
    }
}

2.ShiroConfigの修正

    /**
     * 配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
     * 如什么样的请求可以访问,什么样的请求不可以访问等等
     */
    @Bean
    public ShiroFilterFactoryBean  shiroFilterFactoryBean(SecurityManager securityManager){
        //创建Shiro的拦截的拦截器 ,用于拦截我们的用户请求
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();

        //设置Shiro的安全管理,设置管理的同时也会指定某个Realm 用来完成我们权限分配
        shiroFilter.setSecurityManager(securityManager);
        //用于设置一个登录的请求地址,这个地址可以是一个html或jsp的访问路径,也可以是一个控制器的路径
        //作用是用于通知Shiro我们可以使用这里路径转向到登录页面,但Shiro判断到我们当前的用户没有登录时就会自动转换到这个路径
        //要求用户完成成功
        shiroFilter.setLoginUrl("/");
        //登录成功后转向页面,由于用户的登录后期需要交给Shiro完成,因此就需要通知Shiro登录成功之后返回到那个位置
        shiroFilter.setSuccessUrl("/success");
        //用于指定没有权限的页面,当用户访问某个功能是如果Shiro判断这个用户没有对应的操作权限,那么Shiro就会将请求
        //转向到这个位置,用于提示用户没有操作权限
        shiroFilter.setUnauthorizedUrl("/noPermission");

        //定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问,什么样的请求不可以访问
        Map<String,String> filterChainMap = new LinkedHashMap<String,String>();

        // /login 表示某个请求的名字;anon 表示可以使用游客进行登录(这个请求不需要登录)
        filterChainMap.put("/login","anon");

        //我们可以在这里配置所有的权限规则,这列数据需要从数据库中读取出来

        //或者在控制器中添加Shiro的注解
        /**
         1、 /admin/**  表示一个请求名字的通配, 以admin开头的任意子路径下的所有请求
         authc 表示这个请求需要进行认证(登录),只有认证(登录)通过才能访问
         注:
            ** 表示任意子路径
            *  表示任意的一个路径
            ? 表示 任意的一个字符
         2、roles[admin] 表示 以/admin/**开头的请求需要拥有admin角色才可以访问否   则返回没有权限的页面
            perms[admin:add] 表示 /admin/test的请求需要拥有 admin:add权限才可访问
         注:admin:add仅仅是一个普通的字符串用于标记某个权限功能
         */
        filterChainMap.put("/admin/test","authc,perms[admin:add]");
        filterChainMap.put("/admin/**","authc,roles[admin]");
        filterChainMap.put("/user/**","authc,roles[user]");

        //表示所有的请求路径全部都需要被拦截登录,这个必须必须写在Map集合的最后面,这个选项是可选的
        //如果没有指定/** 那么如果某个请求不符合上面的拦截规则Shiro将方行这个请求
        //filterChainMap.put("/**","authc");

        shiroFilter.setFilterChainDefinitionMap(filterChainMap);

        return shiroFilter;
    }
}

ユーザーが役割と権限を取得したら、Shiro の権限ルールを構成する必要があります

(7) アノテーションによる権限制御

1.ShiroConfigを修正、追加

/**
     * 开启Shiro注解支持(例如@RequiresRoles()和@RequiresPermissions())
     * shiro的注解需要借助Spring的AOP来实现
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启AOP的支持
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

ノート:

アノテーションのパーミッション制御を開始したら、Shiro 構成クラスのパーミッション インターセプトの構成ルールを削除する必要があります。

filterChainMap.put("/admin/test","authc,perms[admin:add]");
filterChainMap.put("/admin/**","authc,roles[admin]");
filterChainMap.put("/user/**","authc,roles[user]");

2. UserController を変更する 

@RequiresRoles(value = {"user"})
    @RequestMapping("/user/test")
    public @ResponseBody String userTest(){
        return "这个userTest请求";
    }

    //RequiresRoles  Shiro的注解 表示访问这功能必须要拥有 admin角色
    //注:如果需要支持多个角色,就直接填写多个角色名称即可;如 "admin","user"
    //RequiresRoles 属性 logical 用于在拥有多个角色时使用 取值为Logical.AND 表示并且的意思必须同时拥有多个角色 或
    //               Logical.OR 或者的意思,只要拥有多个角色中的其中一个即可
    //注:使用了注解以后需要配置Spring声明式异常捕获,否则将在浏览器中直接看到Shiro的错误信息而不是友好的信息提示
    @RequiresRoles(value = {"admin"})
    @RequestMapping("/admin/test")
    public @ResponseBody String adminTest(){
        return "这个adminTest请求";
    }

    //@RequiresPermissions 注解用于指定当前请求必须要拥有指定的权限名字为 admin:add才能访问
    //admin:add 只是一个普通的权限名称字符串,表示admin下的add功能
    @RequiresPermissions(value = {"admin:add"})
    @RequestMapping("/admin/add")
    public @ResponseBody String adminAdd(){
        Subject subject = SecurityUtils.getSubject();
        //验证当前用户是否拥有这个权限
        //subject.checkPermission();
        //验证当前用户是否拥有这个角色
        //subject.checkRole();
        return "这个adminAdd请求";
    }

    //配置一个Spring的异常监控,当工程抛出了value所指定的所以异常类型以后将直接进入到当前方法中
    @ExceptionHandler(value = {Exception.class})
    public String myError(Throwable throwable){
        //获取异常的类型,应该根据不同的异常类型进入到不通的页面显示不同提示信息
        System.out.println(throwable.getClass());
        System.out.println("---------------------------------");
        return "noPermission";
    }

ノート:

Shiro 認証が失敗すると例外がスローされるため、この時点で Spring 例外監視メソッド myError を構成する必要があります。そうしないと、現在の Shiro 権限認証が失敗した後にエラー ページにリダイレクトできなくなります。

(8)シロラベル

Shiro タグと Thymeleaf の統合

1.maven の依存関係を追加する

        <!-- 使用Thymeleaf整合Shiro标签 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2. ShiroConfig は、Bean、Shiro タグ、および Thymeleaf 統合を構成します

/**
     * 配置Shiro标签与Thymeleaf的集成
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

3. 成功はネームスペースを導入する

xmlns: shiro ="http://www.pollix.at/thymeleaf/shiro"

Shiro タグの構文 

属性コントロールとして
< button   type = "button" shiro :authenticated = "true" >
    アクセス制御
</ button >
ラベルとして
< shiro :hasRole name = "admin" >
    < button type = "button" >
        アクセス制御
    </ボタン>
</ shiro :hasRole >

共通のラベル指示

ゲストタグ
< shiro :guest >
    </ shiro :guest >
  ユーザーが本人確認を行っていない場合、該当する情報、つまり訪問者アクセス情報が表示されます。

user tag
< shiro :user >
    </ shiro :user >
  ユーザーが認証された後に対応する情報を表示します/ログインを記憶します。

認証済みタグ
< shiro :authenticated >
    </ shiro :authenticated >
  ユーザーが認証されました。つまり、Subject.login ログインが成功し、ログインすることを覚えていません。


notAuthenticatedタグ
< shiro :notAuthenticated >
    </ shiro :notAuthenticated >
  ユーザーは認証されました。つまり、ユーザーは Subject.login を呼び出してログインしていません。私の自動ログインも認証されていないことを思い出してください。

principalタグ

< shiro :principalプロパティ= "ユーザー名" >

< shiro :principal / >
  は ((User)Subject.getPrincipals()).getUsername() と同等です。

LoosesPermissionタグ
< shiro :lacksPermission name ="org:create" >
    </ shiro :lacksPermission >
  現在のサブジェクトに権限がない場合、本文の内容が表示されます。

hasRoleタグ
< shiro :hasRole name = "admin" >
    </ shiro :hasRole >
 現在のサブジェクトにロールがある場合、本文の内容が表示されます。

hasAnyRolesタグ
< shiro :hasAnyRoles name ="admin,user" >
     <shiro :hasAnyRoles >
  現在の Subject に何らかの役割 (または関係) がある場合、本文の内容が表示されます。

LooseRoleタグ
< shiro :lacksRole name ="abc" >
    </ shiro :lacksRole >
  現在のサブジェクトにロールがない場合、本文の内容が表示されます。

hasPermissionタグ
< shiro :hasPermission name ="user:create" >
    </ shiro :hasPermission >
  現在のサブジェクトが権限を持っている場合、本文の内容

< shiro :hasAnyPermissions name ="admin:add,admin:update" >

</ shiro :hasAnyPermissions >

現在の Subject に何らかの権限 (または関係) がある場合、本文の内容が表示されます。

< shiro :hasAllRoles name ="" ></ shiro :hasAllRoles >

Select All ロールが指定されている必要があります

< shiro :hasAllPermissions name ="" ></ shiro :hasAllRoles >

すべて選択権限が指定されている必要があります

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/126414384#comments_26193191