Shiro - Introducción a Shiro; Diferencia entre Shiro y Spring Security; Spring Boot integra a Shiro

uno, shiro

Lo siguiente se cita de la Enciclopedia Baidu

shiro (marco de seguridad de Java) - Enciclopedia de Baidu

Apache Shiro es un marco de seguridad de Java potente y fácil de usar que realiza autenticación, autorización, contraseña y gestión de sesiones. Usando la API fácil de entender de Shiro, cualquier aplicación, desde la aplicación móvil más pequeña hasta la aplicación web y empresarial más grande, se puede obtener rápida y fácilmente.

(1) Funciones principales

Tres componentes principales: Asunto, SecurityManager y Realms

1, Asunto:

Es decir, el "usuario operativo actual". Sin embargo, en Shiro, el concepto de Sujeto no solo se refiere a personas, también puede ser un proceso de terceros, una cuenta en segundo plano (Cuenta Daemon) u otras cosas similares. Simplemente significa "algo que actualmente está interactuando con el software".

Asunto representa las operaciones de seguridad del usuario actual y SecurityManager administra las operaciones de seguridad de todos los usuarios.

2, Administrador de seguridad:

Es el núcleo del marco de trabajo de Shiro, un modo de fachada típico, Shiro administra instancias de componentes internos a través de SecurityManager y proporciona varios servicios para la administración de seguridad a través de él.

3, Reino:

Realm actúa como un "puente" o "conector" entre Shiro y los datos de seguridad de la aplicación. Es decir, al realizar la autenticación (inicio de sesión) y la verificación de autorización (control de acceso) en un usuario, Shiro buscará la información del usuario y su permiso en el Realm configurado por la aplicación.

En este sentido, Realm es esencialmente un DAO relacionado con la seguridad: encapsula los detalles de conexión de las fuentes de datos y proporciona datos relevantes a Shiro cuando los necesita. Al configurar Shiro, debe especificar al menos un Reino para la autenticación y/o autorización. Es posible configurar varios Reinos, pero se requiere al menos uno.

Shiro tiene un Realm incorporado que puede conectarse a una gran cantidad de fuentes de datos seguras (también conocidas como directorios), como LDAP, bases de datos relacionales (JDBC), recursos de configuración de texto similares a INI y archivos de propiedades. Si el Realm predeterminado no satisface sus necesidades, también puede conectar su propia implementación de Realm que representa una fuente de datos personalizada.

(2) puntos de función básica

  • Autenticación : autenticación de identidad/inicio de sesión, verificar si el usuario tiene la identidad correspondiente;
  • Autorización : La autorización, es decir, la verificación de permisos, verifica si un usuario autenticado tiene un determinado permiso, es decir, juzga si el usuario puede hacer cosas, como: verificar si un determinado usuario tiene un determinado rol. O una verificación detallada de si un usuario tiene un determinado permiso para un determinado recurso;
  • Gestión de sesión  : gestión de sesión, es decir, una sesión después de que el usuario inicia sesión y toda su información está en la sesión antes de cerrar sesión, la sesión puede ser en un entorno JavaSE normal o en un entorno web;
  • Criptografía : cifrado para proteger la seguridad de los datos, como cifrar y almacenar contraseñas en la base de datos en lugar de texto sin formato;
  • Soporte web : soporte web, se puede integrar fácilmente en el entorno web;
  • Almacenamiento en caché : el almacenamiento en caché, por ejemplo, después de que un usuario inicia sesión, no es necesario verificar cada vez su información de usuario, roles/permisos, lo que puede mejorar la eficiencia;
  • Concurrencia : Shiro admite la verificación simultánea de aplicaciones de subprocesos múltiples, es decir, si se abre otro subproceso en un subproceso, los permisos se pueden propagar automáticamente;
  • Pruebas : proporcionar soporte de prueba;
  • Ejecutar como : permite que un usuario finja ser otro usuario (si lo permiten);
  • Recuérdame : Recuérdame, esta es una función muy común, es decir, después de iniciar sesión una vez, no necesita iniciar sesión la próxima vez

(3) Características

1. API de seguridad Java fácil de entender

2. Autenticación de identidad simple (inicio de sesión), soporte para múltiples fuentes de datos (LDAP, JDBC, Kerberos, ActiveDirectory, etc.)

3. Derechos de firma simples (control de acceso) para roles, que admiten derechos de firma detallados

4. Admite caché de primer nivel para mejorar el rendimiento de la aplicación

5. Gestión de sesión empresarial basada en POJO incorporada, adecuada para entornos web y no web

6. Acceso de sesión de cliente heterogéneo

7. API de cifrado muy simple

8. No se incluye con ningún marco o contenedor y puede ejecutarse de forma independiente

2. La diferencia entre Shiro y Spring Security

(一)Seguridad de primavera

Spring Security es un marco de control de acceso y autenticación flexible y potente para proteger las aplicaciones web Java basadas en Spring.

Spring Security es un marco de seguridad pesado que garantiza el soporte de autenticación y autorización para aplicaciones basadas en Spring. Está bien integrado con Spring MVC y viene con implementaciones de algoritmos de seguridad populares.

Spring Security implementa principalmente Autenticación (autenticación, resolver quién es usted?) y Control de Acceso (control de acceso, es decir, ¿qué puede hacer?, también conocido como Autorización). Spring Security separa arquitecturamente la autenticación y la autorización y proporciona puntos de extensión. La "autenticación" es el proceso de establecer un rol reclamado para un usuario, que puede ser un usuario, un dispositivo o un sistema. "Autenticación" se refiere a la capacidad de un usuario para realizar una acción en su aplicación. Los roles se establecen durante el proceso de autenticación antes de llegar a una decisión de autorización.

características

Lo que Shiro puede lograr, Spring Security básicamente lo puede lograr, apoyándose en el sistema Spring, pero la ventaja es que es un miembro del grupo de la familia Spring, y la integración es más adecuada. En términos de uso, es un poco más potente que Shiro (pero generalmente Shiro es suficiente)

(2) La diferencia entre Shiro y Spring Security

1. Shiro es más fácil de usar que Spring Security, es decir, es más simple de implementar, y Shiro es básicamente suficiente para la autenticación y autorización básica

2. La comunidad Spring Security tiene un mayor grado de soporte (pero es difícil instalar Spring Security) La comunidad Spring tiene ventajas en soporte y mantenimiento de actualizaciones, y se combina mejor con Spring.

3. Shiro es poderoso, simple y flexible. El proyecto bajo Apache es más confiable y no está vinculado a ningún marco o contenedor, y puede ejecutarse de forma independiente

comprensión personal

Shiro es la primera opción, rápido de usar, suficiente para usar, alto grado de libertad, algo en Spring Security, Shiro también lo tiene básicamente (el proyecto no usa Spring, no se preocupe, solo vaya a Shiro)

Si el proyecto de desarrollo usa Spring, puede ser más apropiado usar Spring Security; aunque Spring Security es más complicado, tiene una capacidad más fuerte para combinarse con la familia Spring y es una estructura de marco que se puede seleccionar con confianza.

3. Spring Boot integra a Shiro

(1) Crear entorno Shiro

1. Cree un proyecto web SpringBoot común

2. Agregue la dependencia de Shiro

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

Archivo pom.xml completo

<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) Configurar Shiro

1. Definir la clase de configuración 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. Personaliza la clase 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. Definir la clase 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. Defina login.html, nopermission.html, success.html

iniciar sesión.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>

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

sin permiso.html

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

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

<body>

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

</body>

</html>

 5. Inicie la prueba, ingrese   http://localhost:8080/ en el navegador

Hacer clic para iniciar sesión no responde, incluso si ingresa  http://localhost:8080/success

No hay respuesta a la visita.

En este punto, si comenta la clase ShiroConfig 

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

 

Visite localhost:8080 nuevamente para saltar a la página de éxito 

(3) Configurar la cuenta de autenticación de Shiro

1. Personaliza la clase 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. Modificar la clase 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) Caché de autenticación

Después de iniciar sesión con éxito una vez, hacemos clic en Atrás e ingresamos cualquier cuenta y contraseña en este momento. No importa qué información se ingrese, incluso si no ingresa nada y hace clic en el botón "Iniciar sesión" directamente, Shiro considerará que la autenticación fue exitosa. Esto se debe a que después de que Shiro inicie sesión con éxito, hará que los datos se escriban en el caché de Shiro, por lo que se debe agregar una operación de cierre de sesión en el controlador de la solicitud de inicio de sesión antes de juzgar si se ha autenticado y si el caché se ha borrado para que el inicio de sesión se puede probar repetidamente

Modificar controlador de usuario 

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

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

Antes de cerrar la sesión, verifique que el estado subject.isAuthenticated() sea verdadero

Comprobar que el estado subject.isAuthenticated() es falso después de cerrar la sesión

(5) Cifrado de contraseña

1. Cifrado de back-end simple

1. Modificar la clase 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. Cifrado de front-end y back-end 

1. Modificar la clase 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. Modificar inicio de sesión.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>

Nota:

1. Normalmente, los datos de contraseña almacenados en la base de datos no deben ser el código simple 123456, sino datos encriptados.

Por ejemplo, e10adc3949ba59abbe56e057f20f883e, esto es 123456 después de usar el cifrado MD5 Si la contraseña en la base de datos ya está cifrada, puede optar por no cifrarla aquí.

2. Si la contraseña en la base de datos ha sido encriptada, la contraseña debe estar encriptada antes de que los datos se transmitan en la página, de lo contrario, el inicio de sesión puede fallar.

3. Si se selecciona la transmisión encriptada, los tiempos de encriptación de la contraseña y la sal en la página y la base de datos deben ser los mismos; de lo contrario, el inicio de sesión debe fallar.

(6) Distribución de autoridad

Para determinar si se puede usar el permiso de un usuario, primero debemos asignar un permiso al usuario antes de poder asignarlo. Debe configurarse en la clase MyRealm y modificar la clase principal heredada.

1. Modifique la clase principal heredada por la clase MyRealm a la clase AuthorizingRealm e implemente el método abstracto 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. Modificar 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;
    }
}

Una vez que los usuarios tienen roles y permisos, deben configurar las reglas de permisos de Shiro

(7) Control de permisos basado en anotaciones

1. Modificar ShiroConfig, agregar

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

Nota:

Después de iniciar el control de permisos de anotaciones, debe eliminar las reglas de configuración para la interceptación de permisos en la clase de configuración de Shiro

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

2. Modificar el controlador de usuario 

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

Nota:

Se lanzará una excepción después de que falle la autenticación de Shiro, por lo que debe configurar un método de monitoreo de excepción de Spring myError en este momento; de lo contrario, no podrá redirigir a la página de error después de que falle la autenticación actual de la autoridad de Shiro.

(8) etiqueta Shiro

Integrando etiquetas Shiro con Thymeleaf

1. Agregar dependencia experta

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

2. ShiroConfig configura beans, etiquetas Shiro e integración Thymeleaf

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

3. Success introduce el espacio de nombres

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

Sintaxis de la etiqueta Shiro 

Como control de atributos
< tipo de botón   = "botón" shiro :authenticated = "true" > Control de acceso </ botón > Como etiqueta < shiro :hasRole name = "admin" > < tipo de botón = "botón" > Control de acceso </ botón > </ shiro :hasRole >
    



    
        
    

Instrucciones de etiqueta comunes

Etiqueta de invitado
< shiro :guest >
    </ shiro :guest >
  Cuando el usuario no tiene verificación de identidad, se muestra la información correspondiente, es decir, la información de acceso del visitante. etiqueta

de usuario < shiro :usuario > </ shiro :usuario >   Muestra la información correspondiente después de que el usuario haya sido autenticado/recordarme conectado. La etiqueta autenticada < shiro :authenticated > </ shiro :authenticated >   El usuario ha sido autenticado, es decir, el inicio de sesión de Subject.login es exitoso y no me recuerda que inicie sesión.

    




    


etiqueta notAuthenticated
< shiro :notAuthenticated >
    </ shiro :notAuthenticated >
  El usuario ha sido autenticado, es decir, el usuario no ha llamado a Subject.login para iniciar sesión, incluido recordar que mi inicio de sesión automático tampoco está autenticado. etiqueta

principal < shiro : propiedad principal = "nombre de usuario" >

< shiro :principal / >
  es equivalente a ((User)Subject.getPrincipals()).getUsername().

lacksPermission etiqueta
< shiro :lacksPermission name ="org:create" >
    </ shiro :lacksPermission >
  Si el Sujeto actual no tiene permiso, se mostrará el contenido del cuerpo. etiqueta

hasRole < shiro :hasRole name = "admin" > </ shiro :hasRole >  Si el Sujeto actual tiene una función, se mostrará el contenido del cuerpo. etiqueta hasAnyRoles < shiro :hasAnyRoles nombre ="admin,usuario" > <

    




     shiro :hasAnyRoles >
  Si el Sujeto actual tiene algún rol (o relación), se mostrará el contenido del cuerpo.

lacksRoleetiqueta
< shiro :lacksRole name ="abc" >
    </ shiro :lacksRole >
  Si el Sujeto actual no tiene ninguna función, se mostrará el contenido del cuerpo. etiqueta

hasPermission< shiro :hasPermission name ="user:create" ></ shiro :hasPermission >  Si elSujetotiene permiso,contenidodel cuerpo

    

< shiro :tieneCualquierPermiso nombre ="admin:agregar,admin:actualizar" >

</ shiro :tieneCualquierPermiso >

Si el Sujeto actual tiene algún permiso (o relación), se mostrará el contenido del cuerpo.

< shiro :tieneTodoslosRoles nombre ="" ></ shiro :tieneTodoslosRoles >

Debe tener el rol Seleccionar todo especificado

< shiro :tieneTodosLosPermisos name ="" ></ shiro :tieneTodosLosRoles >

Debe tener el permiso Seleccionar todo especificado

Supongo que te gusta

Origin blog.csdn.net/MinggeQingchun/article/details/126414384#comments_26193191
Recomendado
Clasificación