SpringBoot整合SpringSecurity安全框架

SpringBoot的源码我全部放在下面链接上了,链接里面有我整理的SpringBoot整合其他技术的源码以及教程,还有SpringBoot的其他学习资料,欢迎大家来下载学习,如果该教程对你有所帮助,还请star支持一下,谢谢!
源码链接:https://gitee.com/oldou/springbootstudy

官方文档以及参考资料

SpringSecurity官网
帮助文档
JavaDoc文档

学习的时候,官网给出的文档介绍以及帮助文档中各个功能模块的介绍对于我们学习它都非常有帮助,多看看官方文档以及结合源码进行学习,对于我们提升自我能力非常有帮助,下面我就来介绍一下我学习B站狂神的视频所写笔记以及自己查阅文档和阅读源码的一些总结。希望对大家有所帮助。

安全简介

在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

市面上存在比较有名的:Shiro,Spring Security !

这里需要阐述一下的是,每一个框架的出现都是为了解决某一问题而产生了,那么Spring Security框架的出现是为了解决什么问题呢?

首先我们看下它的官网介绍:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
(Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准。)
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements(Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求)

SpringSecurity的简介

  • Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
  • 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

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

话不多说,下面我们来通过代码实现SpringBoot对SpringSecurity的整合以及学习。

项目环境搭建

1、新建一个SpringBoot项目,导入Web的功能,添加Thymeleaf依赖。

<!--Thymeleaf模板-->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

2、导入素材到static内,导入页面到templates目录下,并且在application.yml文件中关闭模板引擎缓存

素材地址:https://pan.baidu.com/s/1XbVArDtxyXkZuYX4RmN5oA
提取码:320r
application.yml文件中配置:

# 关闭Thymeleaf引擎缓存
spring:
  thymeleaf:
    cache: false

3、书写跳转的页面Controller,启动测试一下

@Controller
public class RounterController {
    
    
    //请求跳转到首页
    @RequestMapping({
    
    "/","/index"})
    public String index(){
    
    
        return "index";
    }
    //跳转到登录页面
    @RequestMapping("/toLogin")
    public String toLogin(){
    
    
        return "views/login";
    }
    //跳转到等级1下面的三个页面
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id ){
    
    
        return "/views/level1/"+id;
    }
	//跳转到等级2下面的三个页面
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id ){
    
    
        return "/views/level2/"+id;
    }
	//跳转到等级3下面的三个页面
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id ){
    
    
        return "/views/level3/"+id;
    }
}

启动项目之后,搜索localhost:8080/就能自动跳转到index.html页面,如下所示:
在这里插入图片描述

认识SpringSecurity

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

-WebSecurityConfigurerAdapter:自定义Security策略
-AuthenticationManagerBuilder:自定义认证策略

  • @EnableWebSecurity:开启WebSecurity模式

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

“认证”(Authentication)

  • 身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。
  • 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

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

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

用户认证和权限控制

下面我们开始去将Spring Security框架利用AOP的思想加入到项目中,这样就是不改动原有代码的基础上,加上自己的功能,Spring Security就相当于WEB中的拦截器,但是它比WEB中的拦截器更加简单,功能更加强大。

首先我们导入SpringSecurity的依赖:

<!--Spring Security模块-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

编写配置类SecurityConfig

新建一个config包,并且新建一个SecurityConfig类,继承WebSecurityConfigurerAdapter,添加@EnablewebSecurity注解。然后开始实现用户的权限控制代码的编写。

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    

   @Override
   protected void configure(HttpSecurity http) throws Exception {
    
    
       
  }
}

授权管理请求

我们可以去WebSecurityConfigurerAdapter类的源码中查看一下关于protected void configure(HttpSecurity http) throws Exception {} 授权管理的方法是如何实现的,然后我们可以仿照源码中的代码进行编写,我们可以发现SpringSecurity是链式编程,实现如下:

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        /** 实现首页所有人可以访问,功能页只有对应有权限的人才能访问
                *  请求授权的规则
                */
        http.authorizeRequests()
                .antMatchers("/").permitAll()   //实现所有人可以访问请求 /
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        //没有权限默认会跳转到登录页面(/login),这里需要开启登录的页面。
        http.formLogin();
    }
}

通过重写WebSecurityConfigurerAdapter类的configure(HttpSecurity http)方法,调用http.authorizeRequests()在后面追加不同的方法实现不同的功能,要实现用户的授权功能验证,并且对应用程序中的每个URL进行身份验证,那么我们就需要通过http.authorizeRequests()方法添加多个子级来为URL指定自定义要求。以上代码的方法总结为:

  • (1)http.authorizeRequests():该方法有多个子级,每个子级都按照声明顺序进行判断;
  • (2).antMatchers("/","/index").permitAll():指定了任何用户都可以访问的多个URL模式,只要是URL以"/",或者"/index"开头,则任何用户都可以进行访问。
  • (3).antMatchers("/level2/**").hasRole("vip2"):以"/level2/"开头的任何URL都将限定于角色为"vip2"的权限。
  • (4).antMatchers("/level3/**").access("hasRele('vip1') and hasRole('vip3')"):任何以"/level3/"开头的URL都要求用户同时具备vip1和vip3的权限。
  • (5).anyRequest().authenticated():任何尚未匹配的URL仅要求对用户进行身份验证。

在项目的练习中只介绍了前面两种的使用方式,就是实现所有人都可以访问"/",因为在Controller我设置了如果访问的URL是"/"那么就跳转到首页index.html,然后后面就是将访问的URL进行了授权。其他的几种方式我是参考官网文档介绍进行了总结。

  • 最后的http.formLogin():表示没有权限默认会跳转到登录页面(“/login”),它是SpringSecurity提供默认的登录页面,关于此方法还有很多的设置选项,都在源码中进行了注释,详细请见源码。后面会对此方法进行介绍。

测试代码
发现没有权限时,会跳转到SpringSecurity默认的登录页面
在这里插入图片描述

身份认证

我们要实现身份认证,首先要重写configure(AuthenticationManagerBuilder auth)方法,在此方法中通过auth.inMemoryAuthentication()或者是auth.jdbcAuthentication()对用户的身份进行验证。
注意:这里项目示例中使用的是内存中的身份验证方式,下面为了总结知识点,还介绍了一种JDBC验证,但是本文示例没有连接数据库。

我们在配置类中重写configure(AuthenticationManagerBuilder auth)方法

内存中的身份认证

使用内存中的身份认证,要通过auth.inMemoryAuthentication()方法追加特定的方法实现对身份的认证。如下代码所示:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
    auth.inMemoryAuthentication()
            .withUser("oldou").password("123456").roles("vip1","vip2")
            .and()
            .withUser("root").password("123456").roles("vip1","vip2","vip3")
            .and()
            .withUser("guest").password("123456").roles("vip1");
}

以上代码编写好以后,进行测试,发现浏览器登录页面中一访问就会报错,这是由于密码password需要进行编码也就是加密处理才可以。
在这里插入图片描述
因此,关于SpringSecurity的加密方式也有很多中方式,这里我们使用SpringSecurity最常用的的一种加密方式BCryptPasswordEncoder,详细见官方文档加密方式,加密后的代码如下:


/** 认证   springboot 2.1.x 可以直接使用
 *  这里如果不设置密码的编码,就会报服务器错误,同时会说PasswordEncoder(密码编码)为null,因此要设置编码格式
 *  在Spring Security中有很多加密的方式,还可以使用Java原生的MD5加盐的方式,这里我们使用框架推荐的加密方式
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
            .withUser("oldou").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2")
            .and()
            .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
            .and()
            .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}

过程详解
要实现内存中的身份认证,首先要重写protected void configure(AuthenticationManagerBuilder auth) throws Exception {}方法,接着通过auth.inMemoryAuthentication()方法开始去设置用户名以及密码还有该用户所具备的角色(权限),在设置权限的时候,可以给一个用户设置多个权限,并且加入要创建多个用户认证,就需要通过.and()方法往后面继续追加,最后别忘记了要给密码进行编码设置,如果不设置就会报一下错误,这是由于文件可以反编译,所以不太安全,必须对密码进行一次硬编码,所以我们要使用一个加密的方式对密码进行硬编码,这里我们使用了SpringSecurity框架推荐的加密方式BCryptPasswordEncoder(),其他的加密方式见官方帮助文档。

启动项目进行测试,发现我们使用不同的角色登录以后,比如root登录以后,首页中所有的vip都能进去,但是使用guest角色登录,就只能访问vip1的,其他的都访问不了,这就实现了权限的控制。

接下来便是注销和权限控制的实现。

JDBC验证(参考)

之前我们例子中是用的内存中的身份验证,这里的是从数据库中取数据进行身份验证,我给出的官网中的示例代码:

//注入数据源
@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
    
    // ensure the passwords are encoded properly
    UserBuilder users = User.withDefaultPasswordEncoder();
    auth.jdbcAuthentication()
            .dataSource(dataSource)
            .withDefaultSchema()
            .withUser(users.username("user").password("password").roles("USER"))
            .withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}

使用auth.jdbcAuthentication()方法对用户进行身份的验证。详细如上所示。
首先.dataSource(dataSource)表示的是连接数据源,后面其他的都是一样的。

注销和权限控制

注销

使用时WebSecurityConfigurerAdapter,将自动应用注销功能。默认是访问URL /logout将通过以下方式注销用户:

  • 使HTTP会话无效
  • 清理配置的所有RememberMe身份验证
  • 清除 SecurityContextHolder
  • 重定向到 /login?logout
    但是,与配置登录功能相似,您还可以使用各种选项来进一步自定义注销要求:

我们先开启自动配置的注销功能,然后再去前端增加一个注销的按钮,在index.html的导航栏中:

<a class="item" th:href="@{/logout}">
    <i class="address card icon"></i> 注销
</a>

接着,我们在配置类的protected void configure(HttpSecurity http)方法下开启自动配置注销的功能:

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
   //....
   //开启自动配置的注销的功能
      // /logout 注销请求
   http.logout();
}

启动项目测试,我们可以发现登录成功之后点击注销,发现注销成功之后会跳转到登录页面,我们想让它注销以后依旧能够跳转到首页,该怎么处理呢?

//开启了注销功能,注销成功之后跳转到首页
http.logout().logoutSuccessUrl("/");

我们通过在后面追加.logoutSuccessUrl(URL地址)方法,实现注销之后跳转到指定页面,以上我是跳转到首页。

注销功能只需要在protected void configure(HttpSecurity http) throws Exception {}方法中调用http.logout()方法即可开启注销的功能,其中关于注销的方法后面还可以追加一些功能参数,详细的源码中的注释有介绍,这里我根据源码中的注释来说明一下,源码中的有很多的说明,这里我介绍一下关于注销之后几种常用的方法

  • .deleteCookies(&quot;remove&quot)方法表示注销成功之后删除Cookie。
  • .invalidateHttpSession(false)方法的意思为销毁Session,这里是false默认不删除,如果要开启该功能得设置为true;
  • .logoutUrl(&quot;/custom-logout&quot;)方法表示的是触发注销发生的url(默认为 “/logout”),如果启动了CSRF保护(默认),则请求必须是POST请求,这就意味着默认情况下需要POST "/logout"来触发注销;加入CSRF保护被禁用了,则允许任何的HTTP方法。
  • .addLogoutHandler(logoutHandler)方法添加一个LogoutHandler。 默认情况下SecurityContextLogoutHandler被添加为最后一个LogoutHandler。
    还有其他的可以去这个文档中查看学习:这个文档

启动项目进行测试,发现注册可以实现页面跳转到首页,接下来我们就进行权限控制的实现。

权限控制

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

这里我们需要结合Thymeleaf去实现这个功能,Thymeleaf中通过sec:authorize="isAuthenticated()"来判断是否认证登录,以此来实现显示不同的页面。

首先导入Maven依赖:

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

同时在我们的index.html文件中,也就是前端页面中导入命名空间

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

之后便是修改一下导航栏,实现登录成功之后只能看见注销按钮,未登录时只显示登录按钮。

<!--如果未登录-->
<div sec:authorize="!isAuthenticated()">
    <a class="item" th:href="@{/toLogin}">
        <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>

以上使用了sec:authorize="isAuthenticated()",如果用户已经登录,那么登录按钮就不会显示在导航栏中,而用户名和角色,还有注销按钮就会显示在导航栏中,相反,如果用户未登录,那么导航栏只显示登录按钮。启动测试一下,如果成功就进行下一步。

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

//防止网站的跨站请求伪造CSRF 注销是GET请求
http.csrf().disable(); //关闭csrf功能,登录失败可能存在的原因

//开启了注销功能,注销成功之后跳转到首页
http.logout().logoutSuccessUrl("/");

代码中http.csrf().disable();方法表示关闭CSRF保护,什么是CSRF可去查阅百度。

如果测试成功了,那么就来实现一下关于用户登录成功之后根据权限的不同而登录成功展示的页面不同的功能。
还是在index.html中,这里我只给出其中一个权限的代码,其他两个也要修改,如下所示:

<!--菜单根据用户的角色动态的实现-->
<div class="column" sec:authorize="hasRole('vip1')">
    <div class="ui raised segment">
        <div class="ui">
            <div class="content">
                <h5 class="content">Level 1</h5>
                <hr>
                <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
            </div>
        </div>
    </div>
</div>

以上代码中就添加了一个sec:authorize="hasRole('vip1')"实现只要是拥有vip1的角色就能显示该功能选项,同样的可以通过此方式来设置多个功能选项,假如是root用户的权限,那么所有的功能选项登录成功都能看到,如果只是拥有vip1的权限,那么登录成功之后只能看见vip1的功能,其他的就没有那么权限。

启动项目测试:
当我使用root用户登录是,看到所有功能:
在这里插入图片描述
当我使用权限最低的guest用户登录时:
在这里插入图片描述
这样功能就实现了。下面我们要实现记住我Remember Me的功能和定制首页。

记住我以及首页定制

记住我Remember Me

“记住我”或“持久登录”身份验证是指能够记住会话之间的主体身份的网站。通常,这是通过向浏览器发送一个cookie来实现的,该cookie在以后的会话中被检测到并导致自动登录。Spring Security提供了进行这些操作所需的钩子,并具有两个具体的“记住我”实现。一种使用散列来保留基于cookie的令牌的安全性,另一种使用数据库或其他持久性存储机制来存储生成的令牌。

开启记住我的功能

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
//。。。。。。。。。。。
   //记住我
   http.rememberMe();
}

说起来你可能不信,就这样就可以实现记住我的功能了,真尼玛爽,启动项目测试一下,F12查看,当我们登录成功的之后就会有一个Cookie帮助我们进行存储,我们关闭浏览器之后直接访问首页都可以不用登录就直接访问。
在这里插入图片描述
以上的实现是通过SpringSecurity提供的登录页面实现的这个功能,那么我们怎么样才能实现将这个功能添加到其他的登录页面呢?

首先我们去我们自己的login.html页面中添加一个记住我的单选框,起名name为rememberMe
在这里插入图片描述
之后我们需要在http.rememberMe()后面追加.rememberMeParameter("rememberMe")方法实现实现自定义接受前端的参数,实现不使用自带的登录页面完成记住我完成这个功能。

//开启记住我的功能,使用的是cookie,默认保存时间是两周,我们还可以使用自定义的记住我
        http.rememberMe().rememberMeParameter("rememberMe");

**结论:**登录成功之后,服务器会将Cookie发送给浏览器进行保存,当后面再次登录的时候就带上Cookie,只要通过检查就可以免登录了,如果点击注销就会删除Cookie,Cookie保存的默认时长是两周。

首页定制

我们之前的登录页面都不是使用我们自己书写的登录页面,那是SpringSecurity框架提供的登录页面,我们可以进源码中进行查看:
在这里插入图片描述
源码中的注释说到,SpringsCurity的默认配置为自动生成一个登录的页面,URL为"/login",如果身份验证失败了就会重定向到"/login?error"…

从该段信息可以得知,我们默认的登录页面是框架给提供的,那么我们怎么才能进行将自己设计的登录页面替换掉默认的呢?
这个时候,我们可以从http.formLogin()方法入手,在源码中给出了登录之后的一些选项,其中.loginPage("/toLogin")方法就是可以帮助我们设置为自己的登录页面,这里的"/toLogin"是由于我在Controller层中定义了一个跳转到登录页面的Controler,如下所示:
在这里插入图片描述
在这里插入图片描述
这样就可以实现我们的定制登录页面了,但是我们一般都会习惯性的想着到"/login",这个该怎么处理呢?
这里我们得介绍一下.loginProcessingUrl("/login")方法,假如我们在login.html中的toLogin处写成login,那么我们需要加入这个方法,同时还需要保证表单中的用户名和密码的name名和源码中的一致,不然就会报错。
在这里插入图片描述
在这里插入图片描述
一般我们命名时会习惯的将密码的name名写为pwd什么的,这个时候我就需要自定义参数名,这就没问题了,如下所示:

//没有权限默认会跳转到登录页面(/login),这里需要开启登录的页面。
//定制登录页面 loginPage("/toLogin")
http.formLogin().loginPage("/toLogin")
        .usernameParameter("uname")
        .passwordParameter("pwd")
        .loginProcessingUrl("/login");

这里如果我们设置了以上代码,就需要将登录页面的用户名和密码改为uname和pwd,并且提交的地址改为login。

这里我们还是使用http.formLogin().loginPage("/toLogin");就可以了,上面的代码是为了提示如果以后出现这样的问题了该怎么解决,我们还是使用"/toLogin"进行页面的跳转到login.html登录页面,启动项目进行测试,功能OK,实现。

总结

本文是看狂神的视频学习所写的笔记,其中包含了自己看官方资料以及看源码的总结,虽然功能没有全部介绍到,感觉写得不是很全,如果有不对的地方请指正,这里必须说一下,源码是真的香,虽然看着痛苦,但是坚持下来还是不错的。另附一句:官方文档有时候虽然有时候介绍得比较多,但是还是没有源码解释得全。

猜你喜欢

转载自blog.csdn.net/weixin_43246215/article/details/108436490