Spring Boot 整合 Spring Security

Spring Boot 整合 Spring Security

前言

在前面的博客中已经认识到:Spring Security 是一种安全框架,可以为 Java 应用程序提供身份认证和授权功能。

Spring Security 集成在 Spring 中需要比较繁琐的 XML 配置文件,当然,在新的 Spring 框架中已经可以实现了使用 Java 配置 Spring Security 。本篇博客就来简单讲一讲 Spring Boot 如何来整合 Spring Security

一、 Spring Boot 概述

1.1 回顾 Spring

要想学习 Spring Boot,首先得来回顾一下 Spring 框架。

Spring 框架是 Java 企业版 (Java Enterprise Edition, JEE or J2EE) 的轻量级代替品。无需开发重量级的 Enterprise JavaBean (EJB),Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象 (Plain Old Java Object,POJO) 实现了 EJB 的功能。

Spring 框架十分强大,但是它也有一定的弱点:虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的,比如之前学习到的使用 XML 框架对 Spring 框架进行配置。所有这些配置都代表了开发时的损耗。除外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。

总而言之:Spring的缺点可以分为两个方面,第一个是配置较为繁琐,第二个是导入的依赖坐标可能存在版本冲突。

1.2 Spring Boot 介绍

SpringBoot 对上述 Spring 的缺点进行了改善和优化,基于 “约定优于配置的思想”,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,从而全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

总而言之:SpringBoot 不是对 Spring 功能上的增强 (它有的功能 Spring 都有),而是提供了一种快速便捷使用 Spring 的方式。

  • Spring Boot 的特点

    • 为基于Spring的开发提供更快的入门体验。

    • 开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求。

    • 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等。

    • SpringBoot 不是对 Spring 功能上的增强,而是提供了一种快速使用 Spring 的方式。

  • Spring Boot 的核心功能

    • 起步依赖

      起步依赖本质上是一个 Maven 项目对象模型 (Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

      简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

    • 自动配置

      Spring Boot 的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定 Spring 配置应该用哪个,不该用哪个。该过程是 Spring 自动完成的。

二、 使用 IDEA 快速搭建一个 Spring Boot 项目

2.1 新建一个 Spring Boot 项目

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

在这里插入图片描述
通过查看 pom.xml 可以看到,IDEA 快速创建的 SpringBoot 项目已经自动导入了我们选择的 web 的起步依赖的坐标:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.itheima</groupId>
	<artifactId>springboot_quick2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot_quick2</name>
	<description>Demo project for Spring Boot</description>

    <!-- SpringBoot 要求,项目要继承 SpringBoot 的起步依赖 spring-boot-starter-parent -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>9</java.version>
	</properties>

	<dependencies>
        <!-- SpringBoot 要集成 SpringMVC 进行 Controller 的开发,所以要导入 web 的启动依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
2.2 SpringBoot 启动类

项目新建完成之后,Spring Boot 会为我们自动生成一个启动类。关于 Spring Boot 的启动类,将在后面的博客中详细分析。

// 标注SpringBoot的启动类
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
2.3 编写测试 Controller

在启动类 Application 同级包或者子级包中创建 QuickStartController,用于测试 Spring Boot

@Controller
public class QuickStartController {
    @RequestMapping("/quick")
    @ResponseBody
    public String quick(){
        return "SpringBoot 访问成功!";
    } 
}
2.4 测试

执行 Spring Boot 启动类的主方法,控制台打印日志如下:

在这里插入图片描述
通过日志发现:tomcat 已经启动成功,端口监听 8080,web 应用的虚拟工程名称为空,打开浏览器访问 ur l地址为:http://localhost:8080/quick。

​浏览器访问结果如下:
在这里插入图片描述

三、 Spring Boot 简单整合 Spring Security

3.1 工程创建

对于 Spring Boot 整合 Spring Security 的工程的创建,其实步骤与上面的 Spring Boot 工程创建的是一致的。唯一需要注意的地方就是:在选择的 Dependencies 的时候需要勾选上 Spring Security 的依赖。

如果在创建工程的时候没有勾选上 Spring Security 的依赖,也可以在 pom.xml 文件中自行添加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.2 自定义 Controller

新建一个包用于存放 controller 文件,在包中新建一个 controller 用于被访问以测试 Spring Security 的功能是否正常。

新建一个 controller 如下:

@RestController  // 相当于 @ResponseBody + @Controller
public class IndexController {
    @GetMapping("/index")
    public String index(){
        return "入门成功";
    }
}
3.3 测试

执行 Spring boot 为我们生成的启动类 Application 的 主方法,控制台打印出如下内容:
在这里插入图片描述
​从控制台打印的内容我们可以读出如下信息:① 系统为我们默认生成了 Spring Security 的密码;② 访问的端口号为 8080。

​在浏览器中输入 http://localhost:8080/index 对控制器的 index 方法进行访问, 由于 Spring Security 的存在,此次访问被拦截,需要输入用户名和密码进行登录:
在这里插入图片描述
Spring Security 提供的默认的登录用户名是 user,密码是控制台随机生成的字符串。将用户名和密码输入之后点击登录,认证成功之后,可以看到成功访问 Controller 的 index 方法:
在这里插入图片描述
​如此,便简单实现了对 Java 应用的权限控制,增加了安全性。

四、 Spring Boot 整合 Spring Security 之自定义配置

4.1 自定义用户名和密码

在之前写的 Spring 整合 Spring Security 的博客中,我们使用 XML 的方式来配置自定义用户名和密码,过程确实比较繁琐。现在采用更为方便快捷的 Java 配置的方式来自定义用户名和密码。

​基于 Java 配置 Spring Security 的核心在于自定义 Security 配置类,这个配置类必须要继承 WebSecurityConfigurerAdapter

​而基于 Java 配置如同 XML 配置一样,一般可以在内存中配置用户名和密码,也可以在数据库中读取用户名和密码。用户配置依赖于 WebSecurityConfigurerAdapter 类的 configure(AuthenticationManagerBuilder auth) 方法。下面以在内存中配置用户名、密码为例:

@Configuration			// 声明这是一个配置类
@EnableWebSecurity      // 启用 Spring Security 的 Web 安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 在内存中配置用户
     * @param auth
     * @throws Exception
     */
    @Autowired
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .passwordEncoder(passwordEncoder())     // 配置密码加密器
                .withUser("ronz")
                .password(passwordEncoder().encode("root"))
                .roles("ADMIN")
                .and()
                .withUser("test")
                .password(passwordEncoder().encode("root"))
                .roles("USER");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        // Spring Security 提供的加密工具,可快速实现加密解密
        return new BCryptPasswordEncoder();
    }
}

​分析上面的代码:

configure(AuthenticationManagerBuilder auth) 方法主要是用来配置认证信息的,其中的 .inMemoryAuthenticaltion() 会在项目启动时,将账户、密码、角色信息等配置保存进内存中,随后的 passwordEncoder(passwordEncoder()) 表示配置密码加密器,Security5 默认要求密码使用加密,不加密的话就要使用 “{noop}root” 这样的写法,随后定义了两个用户 ronz 和 test 以及用户的密码、角色。

​对于 passwordEncoder() 方法,其将返回一个由 Spring Security 提供的加密工具以注入到上面的 configure() 方法中,可快速实现密码的加密解密。

​自定义用户名和密码之后,可以启动 Spring Boot 项目,对控制器方法进行访问。然后在登录页面输入自定义的用户名和密码即可成功访问到控制器方法。

4.2 自定义登录页面及权限

​自定义登录界面这部分我搞了一下午才明白一点,真的是太蠢了。刚开始按照别人的教程配置的时候,访问自己定义的登录界面总是出现 404,急的我都上火了。后来沉下心来,仔细理了理思路。

​根据我的个人体会,在自定义界面这部分需要明确以下几个知识点

​ ① SpringBoot默认 static 中放静态页面,而 templates 中存放动态页面。

​ ② 默认情况下,可以直接访问到 static 下的文件。

​ ③ SpringBoot 项目默认是不允许直接访问 template 文件夹下的文件的,它们是受保护的。

​ ④ 如果想要实现访问 templates 下的文件,需要借助控制器做跳转。先访问后台应用程序,然后再转向到页面,比如访问 JSP,Spring Boot建议不要使用 JSP,默认使用 Thymeleaf 来做动态页面。

​使用 Thymeleaf 需要导入如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在这里,我们选择借助控制器方法跳转、使用 Thymeleaf 来实现访问 templates 下的 .html 文件。

​明确了上面这些内容之后,就可以愉快滴开始配置自定义功能啦~

配置:

​下面将正式介绍如何自定义登录页面以及权限等相关配置。

​自定义登录页面及权限依赖于 WebSecurityConfigurerAdapter 类的 configure(HttpSecurity http) 方法。详细配置如下:

/**
 * 自定义登录及相关配置
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 配置授权请求    
    http.authorizeRequests()
                // 只有 ADMIN 才能访问这个路径
                .antMatchers("/admin").hasRole("ADMIN")
                // ADMIN 和 USER 都能访问这个路径
                .antMatchers("/index").hasAnyRole("ADMIN", "USER");
	
    // 配置登录
    http.formLogin()
        // 使用自定义的登录界面
        .loginPage("/myLogin")
        // 登陆页面提交的 url
        .loginProcessingUrl("/login")
        .failureForwardUrl("/failed")
        .permitAll();

    // 开启自动配置的记住我功能
    http.rememberMe();

    // 开启自动配置的退出功能
    http.logout().logoutSuccessUrl("/logoutSuccess");
    
    // 禁止跨站请求伪造、跨域资源共享
    http.csrf().disable().cors().disable();
}

​分析上面的代码:

​首先,使用 authorizeRequests() 方法配置了哪些角色可以访问哪些资源,例如 /admin 路径的资源只有角色为 ADMIN 的用户才可以访问。

​其次,使用 formLogin() 方法配置了登录的具体信息,其中 loginPage() 指定了自定义登录的控制器的路径,loginProcessingUrl() 指定了自定义登录页面提交登录请求的 url,failureForwardUrl() 指定了登录失败时将要跳转到的控制器路径。由于登录相关页面需要暴露给所有用户包括访客,所以还需要使用 permitAll() 方法配置所有人都能访问。需要补充的是:默认情况下,form 表单的 username 字段提交用户名, password 字段提交密码,也可以分别通过 usernameParameter()passwordParameter() 来自定义。

​然后,使用 remebmerMe() 方法开启记住我功能。这个功能会在登录成功之后,将 cookie 发送给浏览器保存,以后再访问的时候带上这个 cookie,只要通过检查就可以免登录。rememberMe() 默认前端对应表单 name 属性是 remember-name,也可以通过 rememberMeParamter() 自定义 。

​接着,使用 logout() 方法配置用户注销功能,用户发起注销,会清除 session。logoutSuccessUrl() 指定了用户注销成功后要跳转到的 url,一般可指定为登录界面的 url。 默认情况下,页面注销提交的 url 为 logout,也可以通过 logoutUrl() 方法来自定义。

​最后,使用 csrf()cors() 方法分别配置跨站请求伪造和跨域资源共享功能。禁用这些功能可以提高网站的安全性,所以一般如果没有特别需求的话,都会选择将它们禁用掉。

​下面贴上 Controller 和前端代码。

Controller代码:

@Controller
public class IndexController {
    
    // 跳转到 /templates/index.html
    @RequestMapping("/index")
    public String index(){
        return "index";
    }
    
	// 跳转到 /templates/login.html
    @RequestMapping("/myLogin")
    public String myLogin(){
        return "login";
    }

    // 跳转到 /templates/failed.html
    @RequestMapping("/failed")
    public String failed(){
        return "failed";
    }

    // 跳转到 /templates/admin.html
    @RequestMapping("/admin")
    public String admin(){
        return "admin";
    }
}

前端代码 (/templates/):

​login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2 style="color: rebeccapurple">自定义登录界面</h2>
    <form action="/login" method="post">
        账户:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

​failed.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2 style="color: red;">您登陆失败了</h2>
</body>
</html>

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2 style="color: indianred">欢迎来到首页</h2>
</body>
</html>

​admin.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2 style="color: burlywood">尊敬的管理员先生,欢迎您</h2>
</body>
</html>

测试:

​按照前面的针对自定义配置类 SecurityConfig 的配置,我们可以预想出如下结果:当打开浏览器访问 /index 时,会自动跳转到 /myLogin 路径对应的控制器方法从而跳转到自定义的 login.html 页面。如果登录成功,会跳转到 /index 路径对应的控制器方法从而跳转到 index.html 页面;如果登录失败,会跳转到 /failed 路径对应的控制器方法从而跳转到 failed.html 界面。如果当前登录成功的是 root 用户 (角色为 ADMIN),那么可以访问 /admin 路径和 /index 路径;如果当前登录成功的是 user 用户(角色为 USER), 那么只能访问 /index 路径,若访问 /admin 路径会报 403 权限不足错误。

​根据上面预想的结果,我们开始测试:

​首先运行启动类 Application 类的主方法。启动成功之后,在浏览器输入 http://localhost:8080/index,结果如下:
在这里插入图片描述
​可以看到访问 /index 路径后,由于 Spirng Security 的权限控制,请求被重定向到了 /myLogin。从下面的 index 请求的请求头中的 302 状态码可以验证这一点。
在这里插入图片描述
​当我们输入正确的用户名和密码,以 user 用户为例,登录成功后,页面又被重定向到了我们之前访问的 index 路径下的 index.html。
在这里插入图片描述
​这时我们以 user 用户的身份尝试访问 /admin 路径,已知 user 用户的角色为 USER,而访问 /admin 路径需要角色为 ADMIN。因此如果配置有效,应当会出现 403 权限不足的情况。在地址栏输入 http://localhost:8080/admin,结果如下:
在这里插入图片描述
​从图中可以看出来,user 用户由于权限不足,无法访问 /admin 路径,与我们预想的结果一致。

​后续登录失败页面跳转以及 root 用户(角色为 ADMIN)访问 /admin 路径功能经本地测试,与预想结果完全一致,由于篇幅原因,不再在此赘述。

​啊!调试代码加写博客花了两天,难受。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值