• SpringBoot笔记之SpringSecurity


    1. SpringSecurity

    • 一个安全的框架,其实通过过滤器和拦截器也可以实现

    • 官网:Spring Security官网地址

    • Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。

    • Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于可以轻松地扩展以满足定制需求

    • 在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

    1.1 认识SpringSecurity

    Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

    记住几个类:

    • WebSecurityConfigurerAdapter:自定义Security策略
    • AuthenticationManagerBuilder:自定义认证策略
    • @EnableWebSecurity:开启WebSecurity模式

    Spring Security的两个主要目标是“认证”“授权”(访问控制)。

    • “认证”(Authentication)

      • 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

      • 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

    • “授权” (Authorization)

      • 授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

    1.2 实战使用

    1.2.1 配置页面的访问权限

    1. 首先导入需要的静态资源:

      链接:https://pan.baidu.com/s/1oB9-VKrN4tzh61MtdW5Dow
      提取码:clsq
      在这里插入图片描述

    2. 引入 SpringSecurity 依赖

      
      <dependency>
           <groupId>org.springframework.bootgroupId>
           <artifactId>spring-boot-starter-securityartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
    3. 新建一个配置文件,并且继承 WebSecurityConfigurerAdapter,并且要添加@EnableWebSecurity注解

      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests()
                      .antMatchers("/").permitAll()
                      .antMatchers("/level1/**").hasAnyRole("vip1")
                      .antMatchers("/level2/**").hasAnyRole("vip2")
                      .antMatchers("/level3/**").hasAnyRole("vip3");
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      注意这里用到了链式

    4. 测试一下:发现除了首页都进不去了!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!

       403:表示权限不足
      
      • 1
    5. 在configure()方法中加入以下配置,开启自动配置的登录功能!

      // 开启自动配置的登录功能
      // /login 请求来到登录页
      // /login?error 重定向到这里表示登录失败
      http.formLogin();
      
      • 1
      • 2
      • 3
      • 4

      测试发现没有权限的时候,会跳转到登录的页面:

      在这里插入图片描述

    6. 查看刚才登录页的注释信息;

      我们可以定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法

      //定义认证规则
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         
         //在内存中定义,也可以在jdbc中去拿....
         auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123456").roles("vip2","vip3")
                .and()
                .withUser("root").password("123456").roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password("123456").roles("vip1");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    7. 测试,我们可以使用这些账号登录进行测试!发现会报错!

      在这里插入图片描述

    8. 原因,我们要将前端传过来的密码进行某种方式加密,否则就无法登录,修改代码

      //定义认证规则
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         //在内存中定义,也可以在jdbc中去拿....
         //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
         //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
         //spring security 官方推荐的是使用bcrypt加密方式。
         
         auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      测试,发现,登录成功,并且每个角色只能访问自己认证下的规则!

    1.2.2 权限控制和注销

    1. 开启自动配置的注销的功能

      //定制请求的授权规则
      @Override
      protected void configure(HttpSecurity http) throws Exception {
         //....
         //开启自动配置的注销的功能
         // /logout 注销请求
         http.logout();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    2. 我们在前端,增加一个注销的按钮,index.html 导航栏中

      <a class="item" th:href="@{/logout}">
          <i class="sign-out icon">i> 注销
      a>
      
      • 1
      • 2
      • 3
    3. 我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!

    4. 但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

      // .logoutSuccessUrl("/"); 注销成功来到首页
      http.logout().logoutSuccessUrl("/");
      
      • 1
      • 2
    5. 测试,注销完毕后,发现跳转到首页OK

    6. 我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如zhangsan这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?

      我们需要结合thymeleaf中的一些功能

      sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面

      导入thymeleaf和security结合的Maven依赖:

      
      <dependency>
         <groupId>org.thymeleaf.extrasgroupId>
         <artifactId>thymeleaf-extras-springsecurity5artifactId>
         <version>3.0.4.RELEASEversion>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    7. 修改我们的 前端页面

      导入命名空间

      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
      
      • 1

      修改导航栏,增加认证判断

      
      <div class="right menu">
      
         
         <div sec:authorize="!isAuthenticated()">
             <a class="item" th:href="@{/login}">
                 <i class="address card icon">i> 登录
             a>
         div>
      
         
         <div sec:authorize="isAuthenticated()">
             <a class="item">
                 <i class="address card icon">i>
                用户名:<span sec:authentication="principal.username">span>
                角色:<span sec:authentication="principal.authorities">span>
             a>
         div>
      
         <div sec:authorize="isAuthenticated()">
             <a class="item" th:href="@{/logout}">
                 <i class="address card icon">i> 注销
             a>
         div>
      div>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    8. 重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;

    9. 如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();

      SecurityConfig.java

      http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
      
      • 1

      到目前为止已近实现了权限的访问资源不同,接下来还想实现对不同用户的页面显示内容不同:

      关键代码:sec:authorize=“hasRole()” //判断当前的权限是否有这个权限直接加在想要设置权限的标签内即可

    1.2.3 记住我及首页定制

    现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单

    1. 开启记住我功能

      //定制请求的授权规则
      @Override
      protected void configure(HttpSecurity http) throws Exception {
      //...
         //记住我
         http.rememberMe();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    2. 我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!

      思考:如何实现的呢?其实非常简单

      我们可以查看浏览器的cookie

      记住我功能

      并且可以知道这个cookie的保存日期默认为14天

    3. 我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

    4. 结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie。

    1.2.4 定制登录页

    现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?

    1. 在刚才的登录页配置后面指定 loginpage

      http.formLogin().loginPage("/toLogin");
      
      • 1
    2. 然后前端也需要指向我们自己定义的 login请求

      	<a class="item" th:href="@{/toLogin}">
      	   <i class="address card icon">i> 登录
      	a>
      
      • 1
      • 2
      • 3
    3. 我们登录,需要将这些信息发送到哪里,我们也需要配置,login.html 配置提交请求及方式,方式必须为post:

      	<form th:action="@{/toLogin}" method="post">
      
      • 1
    4. 这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!

      默认的用户名和密码参数是username和password,如果改成name和pwd就会报错!

      这时就需要去config中设置一下,将对应的字段进行连接

       http.formLogin().usernameParameter("name").passwordParameter("pwd").loginPage("/toLogin1");
      
      • 1
    5. 在登录页增加记住我的选择框

      <input type="checkbox" name="remember"> 记住我
      
      • 1
              http.rememberMe().rememberMeParameter("remember");
      
      • 1

    在这里插入图片描述

    2. Shiro

    2.1 概述

    2.1.1 简介

    Apache Shiro是一个强大且易用的Java安全框架

    可以完成身份验证、授权、密码和会话管理

    Shiro 不仅可以用在 JavaSE 环境中,也可以用在 JavaEE 环境中

    官网: http://shiro.apache.org/

    2.1.2 功能

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

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

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

    Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

    Web Support:Web支持,可以非常容易的集成到Web环境;

    Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

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

    Testing:提供测试支持;

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

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

    2.1.3 从外部看

    在这里插入图片描述

    应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

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

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

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

    也就是说对于我们而言,最简单的一个Shiro应用:

    1. 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
    2. 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

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

    2.1.4 外部架构

    在这里插入图片描述

    • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

    • SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

    • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

    • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

    • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

    • SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

    • SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

    • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

    • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的

    2.1.5 认证流程

    用户 提交 身份信息、凭证信息 封装成 令牌 交由 安全管理器 认证

    2.2 快速入门

    将案例拷贝:

    按照官网提示找到 快速入门案例

    GitHub地址:shiro/samples/quickstart/

    从GitHub 的文件中可以看出这个快速入门案例是一个 Maven 项目

    1. 新建一个 Maven 工程,删除其 src 目录,将其作为父工程

    2. 在父工程中新建一个 Maven 模块

      在这里插入图片描述

      复制快速入门案例 POM.xml 文件中的依赖 (版本号自选)

      把快速入门案例中的 resource 下的log4j.properties 复制下来

      log4j.rootLogger=INFO, stdout
      
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
      
      # General Apache libraries
      log4j.logger.org.apache=WARN
      
      # Spring
      log4j.logger.org.springframework=WARN
      
      # Default Shiro logging
      log4j.logger.org.apache.shiro=INFO
      
      # Disable verbose logging
      log4j.logger.org.apache.shiro.util.ThreadContext=WARN
      log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      复制一下 shiro.ini 文件

      ok需要下载ini插件,如果在setting中无法下载,就去官网下载对应版本的然后导入

    2.2.1 ini插件安装和配置

    在这里插入图片描述
    在这里插入图片描述

    [users]
    # user 'root' with password 'secret' and the 'admin' role
    root = secret, admin
    # user 'guest' with the password 'guest' and the 'guest' role
    guest = guest, guest
    # user 'presidentskroob' with password '12345' ("That's the same combination on
    # my luggage!!!" ;)), and role 'president'
    presidentskroob = 12345, president
    # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
    darkhelmet = ludicrousspeed, darklord, schwartz
    # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
    lonestarr = vespa, goodguy, schwartz
    
    # -----------------------------------------------------------------------------
    # Roles with assigned permissions
    #
    # Each line conforms to the format defined in the
    # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
    # -----------------------------------------------------------------------------
    [roles]
    # 'admin' role has all permissions, indicated by the wildcard '*'
    admin = *
    # The 'schwartz' role can do anything (*) with any lightsaber:
    schwartz = lightsaber:*
    # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
    # license plate 'eagle5' (instance specific id)
    goodguy = winnebago:drive:eagle5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    导入quickstart.java

    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.Factory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Quickstart {
    
        private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
    
    
        public static void main(String[] args) {
    
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
    
            // get the currently executing user:
            //获取当前的用户对象 Subject
            Subject currentUser = SecurityUtils.getSubject();
    
            //通过当前用户拿到session
            Session session = currentUser.getSession();
            session.setAttribute("someKey", "aValue");
            String value = (String) session.getAttribute("someKey");
            if (value.equals("aValue")) {
                log.info("Subject=>session[" + value + "]");
            }
    
            //判断当前的用户是否被认证
            if (!currentUser.isAuthenticated()) {
                //token: 令牌
                UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
                token.setRememberMe(true); //设置记住我
                try {
                    currentUser.login(token);//执行登录操作
                } catch (UnknownAccountException uae) {
                    log.info("There is no user with username of " + token.getPrincipal());
                } catch (IncorrectCredentialsException ice) {
                    log.info("Password for account " + token.getPrincipal() + " was incorrect!");
                } catch (LockedAccountException lae) {
                    log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                            "Please contact your administrator to unlock it.");
                }
                // ... catch more exceptions here (maybe custom ones specific to your application?
                catch (AuthenticationException ae) {
                    //unexpected condition?  error?
                }
            }
    
            //say who they are:
            //print their identifying principal (in this case, a username):
            log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
    
            //test a role:
            if (currentUser.hasRole("schwartz")) {
                log.info("May the Schwartz be with you!");
            } else {
                log.info("Hello, mere mortal.");
            }
    
            //粗粒度
            //test a typed permission (not instance-level)
            if (currentUser.isPermitted("lightsaber:wield")) {
                log.info("You may use a lightsaber ring.  Use it wisely.");
            } else {
                log.info("Sorry, lightsaber rings are for schwartz masters only.");
            }
    
            //细粒度
            //a (very powerful) Instance Level permission:
            if (currentUser.isPermitted("winnebago:drive:eagle5")) {
                log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                        "Here are the keys - have fun!");
            } else {
                log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
            }
    
            //注销
            //all done - log out!
            currentUser.logout();
            System.exit(0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    执行一下main方法:
    在这里插入图片描述

    2.2.2 分析案例

    先分析一些方法

    1. 通过 SecurityUtils 获取当前执行的用户 Subject

      Subject currentUser = SecurityUtils.getSubject();
      
      • 1
    2. 通过 当前用户拿到 Session,shiro的session

      Session session = currentUser.getSession();
      
      • 1
    3. 用 Session 存值取值

      session.setAttribute("someKey", "aValue");
              String value = (String) session.getAttribute("someKey");
      
      • 1
      • 2
    4. 判断用户是否被认证

      currentUser.isAuthenticated()
      
      • 1
    5. 执行登录操作

       currentUser.login(token);
      
      • 1
    6. 打印其标识主体

      currentUser.getPrincipal()
      
      • 1
    7. 注销

      currentUser.logout();
      
      • 1

    2.3 Springboot集成Shiro

    1. 在刚刚的父项目中新建一个 springboot 模块

    2. 导入 SpringBoot 和 Shiro 整合包的依赖

      
      		
      		<dependency>
      			<groupId>org.apache.shirogroupId>
      			<artifactId>shiro-spring-boot-web-starterartifactId>
      			<version>1.6.0version>
      		dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      下面是编写配置文件
      Shiro 三大要素

      subject -> ShiroFilterFactoryBean               ----当前用户
      securityManager -> DefaultWebSecurityManager   		  ----管理所有用户
      Realm
      
      • 1
      • 2
      • 3

      实际操作中对象创建的顺序 : realm -> securityManager -> subject ----连接数据

    3. 创建config文件,编写UserRealm.java继承AuthorizingRealm并实现方法

      public class UserRealm extends AuthorizingRealm {
      
          //授权
          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              System.out.println("执行了授权方法");
              return null;
          }
      
          //认证
          @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
              System.out.println("执行了认证方法");
      
              return null;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      可以两个方法的功能主要实现了授权和认证,我们添加两个输出一会看看效果

    4. 在Config文件夹下编写ShiroConfig.java文件,将三大Bean注入到spring中

      ①根据前面提供的顺序,先编写realm类:

          //1:创建realm对象,需要自定义
          @Bean(name = "userRealm")
          public UserRealm userRealm(){
              return new UserRealm();
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      ②创建用户管理,这里需要用到前面创建的realm,因此在realm之后进行编写

          //2:创建管理用户需要用到
          @Bean
          public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
              DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
              securityManager.setRealm(userRealm);
      
              return securityManager;
      
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      ③创建subject,需要传入一个securityManager,因此最后进行编写(是不是很像套娃)

          @Bean
          public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
              ShiroFilterFactoryBean subject = new ShiroFilterFactoryBean();
              //设置安全管理器
              bean.setSecurityManager(securityManager);
      
              return subject;
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    2.3.1 搭建简单测试环境

    1. 新建一个登录页面
    2. 新建一个首页
      首页上有三个链接,一个登录,一个增加用户,一个删除用户
    3. 新建一个增加用户页面
    4. 新建一个删除用户页面
    5. 编写对应的 Controller

    2.3.2 实现登录拦截

            //添加shiro的内置过滤器
            /**
             anon:无需认证就可以访问
             authc:必须认证才可访问
             user:必须拥有   记住我才能用
             perms:拥有对某个资源的权限才能访问
             role:拥有某个角色权限才能访问
            **/
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager securityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            //设置安全管理器
            bean.setSecurityManager(securityManager);
    
            Map<String, String> filterMap =new LinkedHashMap<String,String>();
    
            filterMap.put("/user/add","anon");//表示/user/add这个请求所有人可以访问
    
            filterMap.put("/user/update","authc");//表示/user/update这个请求只有登录后可以访问
    
            bean.setLoginUrl("/toLogin");
    
            bean.setFilterChainDefinitionMap(filterMap);
    
            return bean;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    图示解析:

    当然在拦截请求中也支持通配符:如拦截指定/user下的所有请求:/user/*

    2.3.3 实现用户认证

    实现用户认证需要去realm类的认证方法中去配置

    这里我们先把用户名和密码写死,实际中是要去数据库中去取的

    去到UserRealm.java中

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了认证方法");
    
            String name="root";
            String password="123456";
            UsernamePasswordToken userToken = (UsernamePasswordToken)token;
    
            if(!userToken.getUsername().equals(name)){
                return null;
            }
            
            return new SimpleAuthenticationInfo("",password,"");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    编写Controller中的登录方法:

        @RequestMapping("/login")
        public String login(@RequestParam("username") String username,@RequestParam("password") String password,Model model) {
            //获取当前的用户
            Subject subject = SecurityUtils.getSubject();
            //封装用户的登录数据
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
            try {
                subject.login(token);//执行登录的方法,如果没有异常
                return "index";
            } catch (UnknownAccountException e) {
                model.addAttribute("msg", "用户名错误");
                return "login";
            } catch (IncorrectCredentialsException e) {
                model.addAttribute("msg", "密码错误");
                return "login";
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.3.4 Shiro整合Mybatis

    1. 首先导入需要的所有的mavem依赖

      ①mysql-connect ②druid ③log4j ④mybtis-spring-boot-starter

      <dependency>
          <groupId>org.mybatis.spring.bootgroupId>
          <artifactId>mybatis-spring-boot-starterartifactId>
          <version>2.1.1version>
      dependency>
      <dependency>
          <groupId>mysqlgroupId>
          <artifactId>mysql-connector-javaartifactId>
          <version>8.0.22version>
      dependency>
      
      <dependency>
          <groupId>com.alibabagroupId>
          <artifactId>druidartifactId>
          <version>1.1.21version>
      dependency>
      <dependency>
          <groupId>log4jgroupId>
          <artifactId>log4jartifactId>
          <version>1.2.17version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    2. 编写配置文件application.properties或者application.yaml

    3. 编写guo文件下的pojo,controller,service,mapper文件夹参考前面的springboot整合mybatis代码哦!

    整合完毕的文件目录:

    在这里插入图片描述

    在mapper中添加了一个queryUserByName的方法:

    整合完毕后先去测试类中进行测试:

    测试结果OK,进行下一步

    到目前为止整合完毕,没有问题,业务都是正常的接下来去之前的用户认证把之前写死的用户改为数据库中的用户

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了认证方法");
            UsernamePasswordToken userToken = (UsernamePasswordToken)token;
    
            User user = userService.queryUserByName(userToken.getUsername());
    
            if(user==null){
                return null;
            }
    
            return new SimpleAuthenticationInfo("",user.getPwd(),"");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试一下

    2.3.5 授权功能的实现

    1. 到ShiroConfig.java中,添加代码,注意要添加在(顺序)
     filterMap.put("/user/add","perms[user:add]");
    
    • 1
    1. 之后我们执行I一下发现登陆后add标签也无权访问

      在这里插入图片描述

      当然正常的是要访问无权限页面的

    2. 接下来我们去UserRealm中给添加权限

      注意不要导错类SimpleAuthorizationInfo

          @Override
          protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
              System.out.println("执行了授权方法");
      
              SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
      
              info.addStringPermission("user:add");//添加授权
      
              return info;
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      首先先去数据表中添加一个权限字段,紧接着去pojo中添加字段,再去手动添加一些用户的权限

      在这里插入图片描述

      我们想要获取到用户的权限,但是获取用户是在刚刚认证中通过userservice获取的,
      在认证里面查到的东西我们怎么放到授权中去呢?

      想到了两个:①session ②用户当前对象的属性来取get方法或(点属性)

      我们在认证中,最后new了一个SimpleAuthenticationInfo的当前认证对象,之前里面要传递三个参数分别为:
      (资源,密码,realmName)

      我们把获取到的user传入到资源中去。

      return new SimpleAuthenticationInfo(user,user.getPwd(),"");
      
      • 1

      这样一来,user这个资源就传递到了当前用户subject整体资源中,通过当前subject获取资源来获取到这个user

      所以在授权的功能中要先获取subject当前用户

      Subject subject = SecurityUtils.getSubject();
      
      • 1

      然后通过subject获取到资源

      User currentUser = (User) subject.getPrincipal();
      
      • 1

      再给当前用户添加user中对应的权限名,注意方法名是addStringPermission权限

       info.addStringPermission(currentUser.getPerms());
      
      • 1

      最后返回当前权限info

    整体授权代码:

        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了授权方法");
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            Subject subject = SecurityUtils.getSubject();
    
            User currentUser = (User) subject.getPrincipal();
            //设置当前用户的权限
            info.addStringPermission(currentUser.getPerms());
    
    
            return info;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    退出功能就和之前security的logout一样,设置一下退出的路径就可

    2.3.6 Shiro和thymeleaf整合

    实现没有权限的就不要显示

    1. 导入shrio和thymeleaf结合的依赖
    
    <dependency>
        <groupId>com.github.theborakompanionigroupId>
        <artifactId>thymeleaf-extras-shiroartifactId>
        <version>2.0.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 导入shiro的命名空间:
    xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
    
    • 1
    1. 前端代码:
    <p th:text="${msg}">p>
    <hr>
    <a><a th:href="@{/toLogin}">登录a>
    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">adda>
    div>
        
    <div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">updatea>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    解释:判断一下当前的用户是否有当前的权限,如果有则显示,没有不显示

    登录按钮无权限时显示:

    <div shiro:notAuthenticated>
        <a th:href="@{/toLogin}">登录a>
    div>
    
    • 1
    • 2
    • 3
  • 相关阅读:
    如何使用 DataAnt 监控 Apache APISIX
    python中图片处理库Image,transforms,cv2,matplotlib的图片矩阵尺寸变化
    STL是什么?如何理解STL?
    【torchvision】 torchvision.datasets.ImageFolder
    盘点美军的无人机家底
    Vector-常用CAN工具 - CANoe入门到精通_02
    基于51单片机的教室智能照明灯控制系统光控人数检测
    浏览器必备的管理工具,蓝色书签让你更方便
    macOS Sonoma 14.2beta2(23C5041e)发布(附黑白苹果镜像地址)
    Jmeter 运行jmeter.bat报错errorLevel=1解决办法
  • 原文地址:https://blog.csdn.net/qq_45609369/article/details/126278260