Shiro - Introduction à Shiro ; Différence entre Shiro et Spring Security ; Spring Boot intègre Shiro

Un, Shiro

Ce qui suit est extrait de l'Encyclopédie Baidu

shiro (cadre de sécurité Java) - Encyclopédie Baidu

Apache Shiro est un cadre de sécurité Java puissant et facile à utiliser qui effectue l'authentification, l'autorisation, la gestion des mots de passe et des sessions. Grâce à l'API facile à comprendre de Shiro, n'importe quelle application, de la plus petite application mobile à la plus grande application Web et d'entreprise, peut être obtenue rapidement et facilement.

(1) Fonctions principales

Trois composants principaux : Objet, SecurityManager et Domaines

1、Objet:

C'est-à-dire "l'utilisateur d'exploitation actuel". Cependant, dans Shiro, le concept de sujet ne fait pas seulement référence à des personnes, il peut également s'agir d'un processus tiers, d'un compte d'arrière-plan (compte démon) ou d'autres choses similaires. Cela signifie simplement "quelque chose qui interagit actuellement avec le logiciel".

Le sujet représente les opérations de sécurité de l'utilisateur actuel et SecurityManager gère les opérations de sécurité de tous les utilisateurs.

2、SecurityManager:

C'est le cœur du framework Shiro, un mode façade typique, Shiro gère les instances de composants internes via SecurityManager et fournit divers services pour la gestion de la sécurité via celui-ci.

3、Royaume:

Realm agit comme un "pont" ou un "connecteur" entre Shiro et les données de sécurité des applications. C'est-à-dire que lors de la vérification de l'authentification (connexion) et de l'autorisation (contrôle d'accès) sur un utilisateur, Shiro recherchera l'utilisateur et ses informations d'autorisation à partir du domaine configuré par l'application.

En ce sens, Realm est essentiellement un DAO lié à la sécurité : il encapsule les détails de connexion des sources de données et fournit des données pertinentes à Shiro en cas de besoin. Lors de la configuration de Shiro, vous devez spécifier au moins un domaine pour l'authentification et/ou l'autorisation. Il est possible de configurer plusieurs royaumes, mais au moins un est requis.

Shiro a un domaine intégré qui peut se connecter à un grand nombre de sources de données sécurisées (c'est-à-dire des répertoires), telles que LDAP, des bases de données relationnelles (JDBC), des ressources de configuration de texte de type INI et des fichiers de propriétés. Si le domaine par défaut ne répond pas à vos besoins, vous pouvez également brancher votre propre implémentation de domaine qui représente une source de données personnalisée.

(2) Points de fonction de base

  • Authentification : authentification d'identité/login, vérifier si l'utilisateur possède l'identité correspondante ;
  • Autorisation : l'autorisation, c'est-à-dire la vérification des autorisations, vérifie si un utilisateur authentifié dispose d'une certaine autorisation ; c'est-à-dire, juge si l'utilisateur peut faire des choses, telles que : vérifier si un certain utilisateur a un certain rôle. Ou une vérification précise du fait qu'un utilisateur dispose d'une certaine autorisation pour une certaine ressource ;
  • Gestion de session  : gestion de session, c'est-à-dire une session après la connexion de l'utilisateur, et toutes ses informations sont dans la session avant la déconnexion ; la session peut être dans un environnement JavaSE normal ou dans un environnement Web ;
  • Cryptographie : chiffrement pour protéger la sécurité des données, comme le chiffrement et le stockage des mots de passe dans la base de données au lieu du texte en clair ;
  • Support Web : support Web, s'intègre facilement dans l'environnement Web ;
  • Mise en cache : la mise en cache, par exemple, après la connexion d'un utilisateur, ses informations d'utilisateur, ses rôles/autorisations n'ont pas besoin d'être vérifiées à chaque fois, ce qui peut améliorer l'efficacité ;
  • Concurrence : Shiro prend en charge la vérification simultanée des applications multithreads, c'est-à-dire que si un autre thread est ouvert dans un thread, les autorisations peuvent être propagées automatiquement ;
  • Tests : fournir un support de test ;
  • Exécuter en tant que : permet à un utilisateur de se faire passer pour un autre utilisateur (s'il le permet) ;
  • Se souvenir de moi : Se souvenir de moi, c'est une fonction très courante, c'est-à-dire qu'après vous être connecté une fois, vous n'avez pas besoin de vous connecter la prochaine fois

(3) Caractéristiques

1. API de sécurité Java facile à comprendre

2. Authentification d'identité simple (login), prise en charge de plusieurs sources de données (LDAP, JDBC, Kerberos, ActiveDirectory, etc.)

3. Droits de signature simples (contrôle d'accès) pour les rôles, prenant en charge des droits de signature précis

4. Prise en charge du cache de premier niveau pour améliorer les performances de l'application

5. Gestion de session d'entreprise basée sur POJO intégrée, adaptée aux environnements Web et non Web

6. Accès aux sessions client hétérogènes

7. API de chiffrement très simple

8. Il n'est fourni avec aucun framework ou conteneur et peut fonctionner indépendamment

2. La différence entre Shiro et Spring Security

(一)Sécurité du printemps

Spring Security est une infrastructure d'authentification et de contrôle d'accès flexible et puissante pour sécuriser les applications Web Java basées sur Spring.

Spring Security est une infrastructure de sécurité lourde qui assure la prise en charge de l'authentification et de l'autorisation pour les applications basées sur Spring. Il est bien intégré à Spring MVC et est livré avec des implémentations d'algorithmes de sécurité populaires.

Spring Security implémente principalement l'authentification (authentification, résolution de qui êtes-vous ?) et le contrôle d'accès (contrôle d'accès, c'est-à-dire qu'êtes-vous autorisé à faire ?, également appelé autorisation). Spring Security sépare l'authentification et l'autorisation de manière architecturale et fournit des points d'extension. "L'authentification" est le processus d'établissement d'un rôle revendiqué pour un utilisateur, qui peut être un utilisateur, un appareil ou un système. "L'authentification" fait référence à la possibilité pour un utilisateur d'effectuer une action dans votre application. Les rôles sont établis au cours du processus d'authentification avant de prendre une décision d'autorisation.

caractéristiques

Ce que Shiro peut réaliser, Spring Security peut le faire en s'appuyant sur le système Spring, mais l'avantage est qu'il fait partie de la famille Spring et que l'intégration est plus appropriée. En termes d'utilisation, il est légèrement plus puissant que Shiro (mais généralement Shiro suffit)

(2) La différence entre Shiro et Spring Security

1. Shiro est plus facile à utiliser que Spring Security, c'est-à-dire qu'il est plus simple à mettre en œuvre, et Shiro est fondamentalement suffisant pour l'autorisation et l'authentification de base

2. La communauté Spring Security a un degré de support plus élevé (mais il est difficile d'installer Spring Security) La communauté Spring a des avantages dans le support et la maintenance des mises à jour, et elle est mieux combinée avec Spring.

3. Shiro est puissant, simple et flexible. Le projet sous Apache est plus fiable, et il n'est lié à aucun framework ou conteneur, et peut s'exécuter indépendamment

compréhension personnelle

Shiro est le premier choix, rapide à utiliser, assez à utiliser, haut degré de liberté, certains dans Spring Security, Shiro l'a aussi fondamentalement (le projet n'utilise pas Spring, ne vous inquiétez pas, allez simplement à Shiro)

Si le projet de développement utilise Spring, il peut être plus approprié d'utiliser Spring Security ; bien que Spring Security soit plus compliqué, il a une plus grande capacité à se combiner avec la famille Spring, et c'est une structure de cadre qui peut être sélectionnée en toute confiance

3. Spring Boot intègre Shiro

(1) Créer un environnement Shiro

1. Créer un projet Web SpringBoot commun

2. Ajouter la dépendance Shiro

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

Compléter le fichier 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) Configurer Shiro

1. Définir la classe de configuration 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. Personnalisez la classe 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. Définir la classe 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. Définissez login.html, nopermission.html, success.html

connexion.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>

succès.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. Lancez le test, entrez   http://localhost:8080/ dans le navigateur

Cliquer pour se connecter ne répond pas, même si vous entrez  http://localhost:8080/success

Il n'y a pas de réponse à la visite

À ce stade, si vous commentez la classe ShiroConfig 

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

 

Visitez à nouveau localhost:8080 pour accéder à la page de réussite 

(3) Configurer le compte d'authentification Shiro

1. Personnalisez la classe 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. Modifier la classe 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) Cache d'authentification

Après s'être connecté avec succès une fois, nous cliquons sur Retour et entrons n'importe quel compte et mot de passe à ce moment.Quelles que soient les informations saisies, même si vous n'entrez rien et cliquez directement sur le bouton "Connexion", Shiro considérera l'authentification comme réussie. En effet, une fois que Shiro s'est connecté avec succès, les données seront écrites dans le cache de Shiro, donc une opération de déconnexion doit être ajoutée dans le contrôleur de la demande de connexion avant de juger si elle a été authentifiée, et le cache a été effacé de sorte que la connexion peut être testée à plusieurs reprises

Modifier le contrôleur utilisateur 

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

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

Avant de vous déconnecter, vérifiez que le statut subject.isAuthenticated() est vrai

Vérifier que l'état de subject.isAuthenticated() est faux après la déconnexion

(5) Cryptage du mot de passe

1. Cryptage back-end simple

1. Modifier la classe 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. Cryptage front-end et back-end 

1. Modifier la classe 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. Modifier 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>

Note:

1. Normalement, les données de mot de passe stockées dans la base de données ne doivent pas être le code en clair 123456, mais des données cryptées.

Par exemple, e10adc3949ba59abbe56e057f20f883e, après utilisation du cryptage MD5, il s'agit de 123456. Si le mot de passe dans la base de données est déjà crypté, vous pouvez choisir de ne pas le crypter ici.

2. Si le mot de passe dans la base de données a été crypté, le mot de passe doit être crypté avant que les données ne soient transmises sur la page, sinon la connexion peut échouer

3. Si la transmission cryptée est sélectionnée, les temps de cryptage du mot de passe et le sel dans la page et la base de données doivent être les mêmes, sinon la connexion doit échouer

(6) Répartition des pouvoirs

Pour déterminer si l'autorisation d'un utilisateur peut être utilisée, nous devons d'abord attribuer une autorisation à l'utilisateur avant de pouvoir l'attribuer. Elle doit être configurée dans la classe MyRealm et modifier la classe parente héritée.

1. Modifiez la classe parent héritée par la classe MyRealm en classe AuthorizingRealm et implémentez la méthode abstraite 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. Modifier 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;
    }
}

Une fois que les utilisateurs ont des rôles et des autorisations, ils doivent configurer les règles d'autorisation de Shiro

(7) Contrôle des autorisations basé sur les annotations

1. Modifier ShiroConfig, ajouter

/**
     * 开启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;
    }

Note:

Après avoir démarré le contrôle des autorisations des annotations, vous devez supprimer les règles de configuration pour l'interception des autorisations dans la classe de configuration Shiro

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

2. Modifier le contrôleur utilisateur 

@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";
    }

Note:

Une exception sera levée après l'échec de l'authentification Shiro, vous devez donc configurer une méthode de surveillance des exceptions Spring myError à ce moment, sinon vous ne pourrez pas rediriger vers la page d'erreur après l'échec de l'authentification actuelle de l'autorité Shiro

(8) Étiquette Shiro

Intégration des balises Shiro avec Thymeleaf

1. Ajouter une dépendance maven

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

2. ShiroConfig configure les beans, les balises Shiro et l'intégration Thymeleaf

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

3. Success introduit l'espace de noms

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

Syntaxe de la balise Shiro 

En tant que contrôle d'attribut
< type de bouton   = "bouton" shiro :authenticated = "true" > Contrôle d'accès </ bouton > En tant qu'étiquette < shiro :hasRole name = "admin" > < type de bouton = "bouton" > Contrôle d'accès </ bouton > </ shiro :hasRole >
    



    
        
    

Instructions communes sur l'étiquette

Balise d'invité <
shiro : guest >
    </ shiro :guest >
  Lorsque l'utilisateur n'a pas de vérification d'identité, les informations correspondantes sont affichées, c'est-à-dire les informations d'accès du visiteur.

user tag
< shiro :user >
    </ shiro :user >
  Affiche les informations correspondantes après que l'utilisateur a été authentifié/se souvenir de moi connecté. La balise

authentifiée < shiro :authenticated > </ shiro :authenticated >   L'utilisateur a été authentifié, c'est-à-dire que la connexion Subject.login a réussi, sans me souvenir de me connecter.

    


balise notAuthenticated
< shiro :notAuthenticated >
    </ shiro :notAuthenticated >
  L'utilisateur a été authentifié, c'est-à-dire que l'utilisateur n'a pas appelé Subject.login pour se connecter, y compris en se souvenant que ma connexion automatique n'est pas non plus authentifiée. balise

principale < shiro : propriété principale = "nom d'utilisateur" >

< shiro :principal / >
  est équivalent à ((User)Subject.getPrincipals()).getUsername(). balise

LacksPermission < shiro :lacksPermission name ="org:create" > </ shiro :lacksPermission >   Si le sujet actuel n'a pas d'autorisation, le contenu du corps sera affiché. balise hasRole < shiro :hasRole name = "admin" > </ shiro :hasRole >  Si le sujet actuel a un rôle, le contenu du corps sera affiché. balise hasAnyRoles < shiro : nom hasAnyRoles ="admin,user" > <

    




    




     shiro :hasAnyRoles >
  Si le sujet actuel a un rôle (ou une relation), le contenu du corps sera affiché. balise

LacksRole< shiro :lacksRole name ="abc" ></ shiro :lacksRole >   Si le sujet actuel n'a pas de rôle, le contenu du corps sera affiché. balisehasPermission< shiro :hasPermission name ="user:create" ></ shiro :hasPermission >  Si lesujeta la permission,lecontenu

    




    

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

</ shiro :hasAnyPermissions >

Si le sujet actuel dispose d'une autorisation (ou d'une relation), le contenu du corps sera affiché.

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

Doit avoir le rôle Sélectionner tout spécifié

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

L'autorisation Sélectionner tout doit être spécifiée

Je suppose que tu aimes

Origine blog.csdn.net/MinggeQingchun/article/details/126414384#comments_26193191
conseillé
Classement