• 37、springboot——安全


    一、简介

    Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。

    对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。

    几个类:

      WebSecurityConfigurerAdapter:自定义Security策略

      AuthenticationManagerBuilder:自定义认证策略

      @EnableWebSecurity:开启WebSecurity模式

    应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。

    认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。

    授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。

    这个概念是通用的而不只在Spring Security中。

    二、测试Spring Security

    1.项目向导中映入Web模块、Thymeleaf模板引擎、还有Spring Security模块

     自动引入的依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <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>

    2.创建相关页面以及跳转的controller

    @Controller
    public class KungfuController {
        private final String PREFIX = "pages/";
        /**
         * 欢迎页
         * @return
         */
        @GetMapping("/")
        public String index() {
            return "welcome";
        }
        
        /**
         * 登陆页
         * @return
         */
        @GetMapping("/userlogin")
        public String loginPage() {
            return PREFIX+"login";
        }
        
        
        /**
         * level1页面映射
         * @param path
         * @return
         */
        @GetMapping("/level1/{path}")
        public String level1(@PathVariable("path")String path) {
            return PREFIX+"level1/"+path;
        }
        
        /**
         * level2页面映射
         * @param path
         * @return
         */
        @GetMapping("/level2/{path}")
        public String level2(@PathVariable("path")String path) {
            return PREFIX+"level2/"+path;
        }
        
        /**
         * level3页面映射
         * @param path
         * @return
         */
        @GetMapping("/level3/{path}")
        public String level3(@PathVariable("path")String path) {
            return PREFIX+"level3/"+path;
        }
    
    }

    3.编写SpringSecurity的配置类(继承SecurityConfigurerAdapter 标注注解@EnableWebSecurity)

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            //定制请求的授权规则
            http.authorizeRequests().antMatchers("/").permitAll()       //允许所有访问首页/
                    .antMatchers("/level1/**").hasRole("VIP1")          //允许VIP1的角色访问/level1下的页面
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3");
        }
    }

    4.开启服务进行测试

    首页访问成功

     点击罗汉拳或者其它都是403,没有权限

    5.修改SpringSecurity配置类——开启自动配置的登录功能

    不指定登录页默认转向security的登录页
    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            //定制请求的授权规则
            http.authorizeRequests().antMatchers("/").permitAll()       //允许所有访问首页/
                    .antMatchers("/level1/**").hasRole("VIP1")          //允许VIP1的角色访问/level1下的页面
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3");
            //开启自动配置的登录功能
            http.formLogin();
            //1、/login来到security默认的登录页
            //2、重定向到/login?error表示登录失败
            //3、更多详细规则
        }
    }

    再次访问罗汉拳或者其它没有权限时会自动跳转到登录页

     6.修改SpringSecurity配置类——重写定制授权规则方法

        //定制授权规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //super.configure(auth);
            //这些用户名和密码本该从数据库取的,这里只是做演示,所以是从内存中取
            auth.inMemoryAuthentication()
                    .withUser("zhangsan").password("123456").roles("VIP1","VIP2")
                    .and()
                    .withUser("lisi").password("123456").roles("VIP2","VIP3")
                    .and()
                    .withUser("wangwu").password("123456").roles("VIP1","VIP3");
        }

    重启服务,点击罗汉跳转到登录页用用户名zhangsan,密码123456进行登录测试 

     但却报错500

     控制台显示报错信息

    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

    这是因为Spring security 5.0中新增了多种加密方式,也改变了密码的格式

    具体原因查看https://blog.csdn.net/canon_in_d_major/article/details/79675033

    所以我们修改定制授权规则的方法

        //定制授权规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //super.configure(auth);
            //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取
            auth.inMemoryAuthentication()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3");
        }
    }

    重启服务再次进行登录测试

    登录成功

     由于zhangsan只有VIP1和VIP2的角色,所以只能有VIP1和VIP2的访问全选

     7.修改SpringSecurity配置类——开启注销功能

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        //定制请求的授权规则
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            http.authorizeRequests().antMatchers("/").permitAll()       //允许所有访问首页/
                    .antMatchers("/level1/**").hasRole("VIP1")          //允许VIP1的角色访问/level1下的页面
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3");
    
            //开启自动配置的登录功能
            http.formLogin();
            //1、/login来到登录页
            //2、重定向到/login?error表示登录失败
            //3、更多详细规则
    
            //开启自动配置的注销功能
            http.logout();
            //访问/logout 表示用户注销,并清空session
            //注销成功会默认访问访问/login?logout
        }
    
    
        //定制授权规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //super.configure(auth);
            //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取
            auth.inMemoryAuthentication()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3");
        }
    }

    在主页面添加一个表单访问/logout  提交方式必须是post方式

    <form th:action="@{/logout}" method="post">
        <input th:type="submit" th:value="注销">
    </form>

    登录成功点击注销:

    默认跳转到/login?logout

     

     想要指定注销成功之后去哪个页面需要在后面增加logoutSuccessUrl("")方法;修改为跳转到首页:

    http.logout().logoutSuccessUrl("/");

    三、thymeleaf和springSecurity的整合操作

    1、导入整合的依赖

    我的springboot的版本是2.2.6,所以默认的thymeleaf和thymeleaf-layout-dialect偏高,需要导入的是thymeleaf-extras-springsecurity5

    根据自己thymeleaf和thymeleaf-layout-dialect的版本进行是否选择为thymeleaf-extras-springsecurity4

    想选择相应的thymeleaf-extras-springsecurity则可以在pom.xml文件中的properties标签中指定 thymeleaf 以及 thymeleaf-layout-dialect版本默认springboot默认的版本

            <dependency>
                <groupId>org.thymeleaf.extras</groupId>
                <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            </dependency>

    2、在页面中添加springsecurit的名称空间

    虽然我导入的依赖是thymeleaf-extras-springsecurity5,但是在页面中导入命名空间是xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5时,使用sec的时候并不会提示,但页面效果是有的;但是命名空间改为4的话,则代码提示功能就有,功能也还有,具体原因不太清除,抱歉学艺不精。

    <html xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">

    3.修改页面中的部分代码(判断是否登录)

    <!--如果没有登录认证-->
    <div sec:authorize="!isAuthenticated()">
        <h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2>
    </div>
    <hr>
    <!--如果登录认证了-->
    <div sec:authorize="isAuthenticated()">
        <h2><span sec:authentication="name"></span>您好,您的角色有
            <span sec:authentication="principal.authorities"></span></h2>
        <form th:action="@{/logout}" method="post">
            <input th:type="submit" th:value="注销">
        </form>
    </div>

    访问页面查看效果

    没有登录时:

     登录了之后:

    4.修改页面中的部分代码(根据角色显示页面)

    <!--有VIP1的角色才显示-->
    <div sec:authorize="hasRole('VIP1')">
        <h3>普通武功秘籍</h3>
        <ul>
            <li><a th:href="@{/level1/1}">罗汉拳</a></li>
            <li><a th:href="@{/level1/2}">武当长拳</a></li>
            <li><a th:href="@{/level1/3}">全真剑法</a></li>
        </ul>
    </div>
    
    <!--有VIP2的角色才显示-->
    <div sec:authorize="hasRole('VIP2')">
        <h3>高级武功秘籍</h3>
        <ul>
            <li><a th:href="@{/level2/1}">太极拳</a></li>
            <li><a th:href="@{/level2/2}">七伤拳</a></li>
            <li><a th:href="@{/level2/3}">梯云纵</a></li>
        </ul>
    </div>
    
    <!--有VIP3的角色才显示-->
    <div sec:authorize="hasRole('VIP3')">
        <h3>绝世武功秘籍</h3>
        <ul>
            <li><a th:href="@{/level3/1}">葵花宝典</a></li>
            <li><a th:href="@{/level3/2}">龟派气功</a></li>
            <li><a th:href="@{/level3/3}">独孤九剑</a></li>
        </ul>
    </div>

    没有登录时都不显示

     登录张三(拥有VIP1和VIP2角色)后,显示普通武功和高级武功,绝世武功不显示

    5.设置记住登录用户

    修改security配置类

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        //定制请求的授权规则
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            http.authorizeRequests().antMatchers("/").permitAll()       //允许所有访问首页/
                    .antMatchers("/level1/**").hasRole("VIP1")          //允许VIP1的角色访问/level1下的页面
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3");
    
            //开启自动配置的登录功能
            http.formLogin();
            //1、/login来到登录页
            //2、重定向到/login?error表示登录失败
            //3、更多详细规则
    
            //开启自动配置的注销功能
            http.logout().logoutSuccessUrl("/");
            //访问/logout 表示用户注销,并清空session
            //注销成功会默认访问访问/login?logout
            //后面增加.logoutSuccessUrl("")方法指定注销成功后去哪个页面
    
            //开启记住我功能
            http.rememberMe();
        }
    
    
        //定制授权规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //super.configure(auth);
            //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取
            auth.inMemoryAuthentication()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3");
        }
    }

    开启之后在登录页会有记住我的选项

     勾选登录之后浏览器就会把用户名密码保存到cookie中(默认保存14天的),关闭浏览器再次访问就不用登录了

    当然注销之后cookie也就会删除了

    6.使用自己的登录页

    之前一直跳转的是security的默认登录页,现在我们使用自己的登录页

     我们的controller跳转到登录页的映射是/userlogin

    修改security配置类,指定跳转到默认登录页

    @EnableWebSecurity
    public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
        //定制请求的授权规则
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            http.authorizeRequests().antMatchers("/").permitAll()       //允许所有访问首页/
                    .antMatchers("/level1/**").hasRole("VIP1")          //允许VIP1的角色访问/level1下的页面
                    .antMatchers("/level2/**").hasRole("VIP2")
                    .antMatchers("/level3/**").hasRole("VIP3");
    
            //开启自动配置的登录功能
            http.formLogin().loginPage("/userlogin");
            //1、/login来到登录页,不指定登录页默认来到security的登录页
            //2、重定向到/login?error表示登录失败
            //3、更多详细规则
    
            //开启自动配置的注销功能
            http.logout().logoutSuccessUrl("/");
            //访问/logout 表示用户注销,并清空session
            //注销成功会默认访问访问/login?logout
            //后面增加.logoutSuccessUrl("")方法指定注销成功后去哪个页面
    
            //开启记住我功能
            http.rememberMe();
        }
    
    
        //定制授权规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //super.configure(auth);
            //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取
            auth.inMemoryAuthentication()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3")
                    .and()
                    .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3");
        }
    }

    当我们访问需要权限的链接时

     此时就跳转到我们自己的登录页

     但是想要security进行处理我们的表单登录需要一定的规则:*******

    1、如同上诉的如果指定了登录页面的话,好比登录页指定为userlogin

    则表单的action为/userlogin(即登录页名) 且提交方法为postspringsecurity进行登录验证处理

    如果一个访问的是/userlogin,且提交方式是get则springsecurity会跳转到我们的登录页

    没有指定登录页的话,访问的链接是/login;提交方式也是想对应

    当然我们也可以指定访问的链接

    http.formLogin().loginPage("/userlogin").loginProcessingUrl("/login");

    2、input的name值分别是username和password时;security才能进行进行验证

    也可以指定input的name属性

    http.formLogin().usernameParameter("user").passwordParameter("pwd")
                    .loginPage("/userlogin").loginProcessingUrl("/login");

    以上修改一些属性后,想让security帮我们验证登录登录页面代码如下

        <h1 align="center">欢迎登陆武林秘籍管理系统</h1>
        <hr>
        <div align="center">
            <form th:action="@{/login}" method="post">
                用户名:<input name="user"/><br>
                密码:<input name="pwd"><br/>
                <input type="submit" value="登陆">
            </form>
        </div>

    此时我们进行测试,

    访问需要权限的页面

     跳转到了我们的登录页

     输入账号和密码,登录成功

     7、在自己的登录页上添加记住用户

    之前我们配置了security记住用户的功能

     修改登录页代码,添加记住用户选项

    <body>
        <h1 align="center">欢迎登陆武林秘籍管理系统</h1>
        <hr>
        <div align="center">
            <form th:action="@{/login}" method="post">
                用户名:<input name="user"/><br>
                密码:<input name="pwd"><br/>
                <input type="checkbox" name="remember-me">记住我<br>
                <input type="submit" value="登陆">
            </form>
        </div>
    </body>

    当开启security记住用户的功能时,想要功能有效,则checkbox的name值默认是remember-me

    也可以指定checkbox的name值

    //开启记住我功能
     http.rememberMe().rememberMeParameter("remember");

    则checkbox的name值也要相应改变

    <input type="checkbox" name="remember">记住我<br>

    此时登录页就有记住用户的选项

     勾选进行登录之后用户名和密码就会记录在cookie中了,下次访问页面就会自动登录了,(cookie的默认保存时间是14天)

  • 相关阅读:
    GB 51411-2020 金属矿山土地复垦工程设计标准
    GB/T 51413-2020 有色金属工业余热利用设计标准
    DL/T 1907.1-2018等最新电力行业标准
    GB 50205-2020 钢结构工程施工质量验收标准
    DL/T 5210.6-2019 电力建设施工质量验收规程 第6部分:调整试验
    GB/T 38640-2020 盲用数字出版格式
    GB 50325-2020 民用建筑工程室内环境污染控制标准(含条文说明)
    GB 50216-2019 铁路工程结构可靠性设计统一标准
    最新发布的国家标准下载(2020-5-21)
    bind、apply、call的区别
  • 原文地址:https://www.cnblogs.com/lyh233/p/12707696.html
Copyright © 2020-2023  润新知