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天)

原文地址:https://www.cnblogs.com/lyh233/p/12707696.html