avatar

微服务安全框架SpringSecurity

为什么要学SpringSecurity

SpringSecurity的竞争对手是Apache的Shiro框架,并且市面上用Shir的公司要更多,但是在SpringCloud微服务中推荐用SpringSecurity框架做安全控制。

什么是SpringSecurity

SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

SpringSecurity应用场景

Security在很多企业中作为后台角色权限框架、授权认证oauth2.0、安全防护(防止跨站点请求)、Session攻击、非常容易融合SpringMVC使用等

比如有两个账户admin和zhangsan,admin账户所有请求都有访问权限,zhangsan账户只能访问查询和添加订单权限,如果框架返回403表示权限不足,401表示没有授权

SpringBoot项目集成Security

环境搭建

导入POM依赖

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
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cn.itcast</groupId>
<artifactId>springboot-security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-security-demo</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- springboot整合freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>


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

</project>

导入application.yml配置文件

这里页面用到了freemarker,所以要配置一下,不会freemarker也没关系。当做html页面按照教程配置就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 配置freemarker
spring:
freemarker:
# 设置模板后缀名
suffix: .ftl
# 设置文档类型
content-type: text/html
# 设置页面编码格式
charset: UTF-8
# 设置页面缓存
cache: false
# 设置ftl文件路径
template-loader-path:
- classpath:/templates
# 设置静态文件路径,js,css等
mvc:
static-path-pattern: /static/**

添加OrderController

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
package cn.itcast.springbootsecuritydemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class OrderController {
// 首页
@RequestMapping("/")
public String index() {
return "index";
}

// 查询订单
@RequestMapping("/showOrder")
public String showOrder() {
return "showOrder";
}

// 添加订单
@RequestMapping("/addOrder")
public String addOrder() {
return "addOrder";
}

// 修改订单
@RequestMapping("/updateOrder")
public String updateOrder() {
return "updateOrder";
}

// 删除订单
@RequestMapping("/deleteOrder")
public String deleteOrder() {
return "deleteOrder";
}

// 自定义登陆页面
@GetMapping("/login")
public String login() {
return "login";
}
}

在resources目录下的templates目录添加模板页

index.ftl

1
2
3
4
5
6
7
8
9
<h1>订单系统</h1>
<br>
<a href="showOrder">查询订单</a>
<br>
<a href="addOrder">添加订单</a>
<br>
<a href="deleteOrder">删除订单</a>
<br>
<a href="updateOrder">修改订单</a>

login.ftl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<h1>每特教育--权限控制登陆系统</h1>
<form action="/login" method="post">
<span>用户名称</span><input type="text" name="username" /> <br>
<span>用户密码</span><input type="password" name="password" /> <br>
<input type="submit" value="登陆">
</form>
<#if RequestParameters['error']??>
用户名称或者密码错误
</#if>

</body>
</html>

addOrder.ftl

1
<h1>添加订单</h1>

deleteOrder.ftl

1
<h1>删除订单</h1>

showOrder.ftl

1
<h1>查询订单</h1>

updateOrder.ftl

1
<h1>修改订单</h1>

logFail.ftl

1
登陆失败!

使用HttpBasic模式登陆

什么是Basic认证

在HTTP协议进行通信的过程中,HTTP协议定义了基本认证过程以允许HTTP服务器对WEB浏览器进行用户身份证的方法,当一个客户端向HTTP服务 器进行数据请求时,如果客户端未被认证,则HTTP服务器将通过基本认证过程对客户端的用户名及密码进行验证,以决定用户是否合法。客户端在接收到HTTP服务器的身份认证要求后,会提示用户输入用户名及密码,然后将用户名及密码以BASE64加密,加密后的密文将附加于请求信息中, 如当用户名为mayikt,密码为:123456时,客户端将用户名和密码用“:”合并,并将合并后的字符串用BASE64加密为密文,并于每次请求数据 时,将密文附加于请求头(Request Header)中。HTTP服务器在每次收到请求包后,根据协议取得客户端附加的用户信息(BASE64加密的用户名和密码),解开请求包,对用户名及密码进行验证,如果用 户名及密码正确,则根据客户端请求,返回客户端所需要的数据;否则,返回错误代码或重新要求客户端提供用户名及密码。

添加SpringSecurity依赖

1
2
3
4
5
<!-->spring-boot 整合security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加SpringSecurity配置文件

本节知识点主要介绍Basic认证,下面配置文件不做详细介绍,后续介绍。

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
package cn.itcast.springbootsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
/*配置用户认证信息*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password("123456")
.authorities("addOrder");
}

/*配置HttpSecurity 拦截资源*/
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.fullyAuthenticated()
.and()
.httpBasic();
}

/**
* There is no PasswordEncoder mapped for the id "null"
原因:升级为Security5.0以上密码支持多中加密方式,恢复以前模式
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}

效果展示

1

使用FromLogin模式登陆

表单登陆这种方式在实际开发中会用的更多一点

修改配置

修改配置文件中的configure方法,如下

1
2
3
4
5
6
7
8
9
/*配置HttpSecurity 拦截资源*/
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.fullyAuthenticated()
.and()
.formLogin();
}

效果展示

SpringSecurity框架会提供一个默认的登陆页面,也可以是使用我们自己的

2

权限控制

打开localhost:8080登录成功后发现每一个功能都能访问,原因是没有配置控制权限

修改配置信息

修改SecurityConfig.java文件

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
package cn.itcast.springbootsecuritydemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
/*配置用户信息并且分配权限*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置用户账号信息和权限
auth.inMemoryAuthentication()
.withUser("admin") //配置帐号
.password("123456") //配置密码
//配置权限名
.authorities("showOrder","addOrder","updateOrder","deleteOrder");


// 添加 useradd账号 只有添加查询和添加订单权限
auth.inMemoryAuthentication().
withUser("userAdd") //配置账号
.password("123456") //配置密码
.authorities("showOrder","addOrder"); //配置权限名
}


/*配置HttpSecurity 拦截资源,每一个资源路径和权限名的映射关系*/
protected void configure(HttpSecurity http) throws Exception {
// 拦截请求, 权限名称
http.authorizeRequests()
.antMatchers("/showOrder").hasAnyAuthority("showOrder")
.antMatchers("/addOrder").hasAnyAuthority("addOrder")
.antMatchers("/updateOrder").hasAnyAuthority("updateOrder")
.antMatchers("/deleteOrder").hasAnyAuthority("deleteOrder")
.antMatchers("/**").fullyAuthenticated().and().formLogin();
}


/**
* There is no PasswordEncoder mapped for the id "null"
原因:升级为Security5.0以上密码支持多中加密方式,恢复以前模式
SpringBoot2.0抛弃了原来的NoOpPasswordEncoder,
要求用户保存的密码必须要使用加密算法后存储,
在登录验证的时候Security会将获得的密码在进行编码后再和数据库中加密后的密码进行对比
*/
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}

使用admin帐号登陆效果

每一个页面都有权限访问

3

使用userAdd帐号登陆效果

查询和添可以访问,删除和修改提示权限不足

3

修改403页面

分析

查看报错页面,发现错误原因是因为/error 没有对应的映射路径

3

然后查看控制台找到/error对应的控制器

3

查看BasicErrorController控制器中的源代码,发现error视图在errorHtml方法里,观察status.value()的值是403

3

编写配置文件覆盖SpringBoot默认控制器

我们可以覆盖SpringBoot提供的默认的错误页面,根据不同的错误状态码,跳转的不同的页面

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
package cn.itcast.springbootsecuritydemo.config;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration
public class WebServerAutoConfiguration {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
ErrorPage errorPage400 = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400");
ErrorPage errorPage401 = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");
ErrorPage errorPage403 = new ErrorPage(HttpStatus.FORBIDDEN, "/error/403");
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
ErrorPage errorPage415
= new ErrorPage(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "/error/415");
ErrorPage errorPage500
= new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
factory.addErrorPages(errorPage400, errorPage401, errorPage403, errorPage404, errorPage415, errorPage500);
return factory;
}
}

编写错误页面对应的控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.itcast.springbootsecuritydemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/error")
public class ErrorController {
@RequestMapping("/403")
public String statusCode403(){
return "/error/403";//在templates的error目录下
}
}

添加403错误页面

在templates目录下新建error目录,error目录下新建403模板页面

403.ftl

1
您的权限不足!

效果

3

文章作者: 微信:hao_yongliang
文章链接: https://haoyongliang.gitee.io/2019/12/19/SpringCloud/SpringCloud-Security/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 郝永亮的主页
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论