SpringSecurity-(一)SpringSecurity的简单应用

本文最后更新于:February 1, 2022 pm

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转、依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

目录

环境准备

1.页面

准备5个HTML页面,一个index首页,一个login登陆页面。然后div1目录下一个页面;div2目录下的一个页面;div3目录下的一个页面。

div1/1.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>div1------------- 1</h3>

</body>
</html>

div2/1.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>div2------------- 1</h3>

</body>
</html>

div3/1.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>div3------------- 1</h3>

</body>
</html>

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

<form action="">
用户名:<input type="text"> <br>
密 码:<input type="password"> <br>
<input type="submit" value="登陆">
</form>

</body>
</html>

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>

<a th:href="@{/div1/1}">1-1</a> <br>
<a th:href="@{/div2/1}">2-1</a><br>
<a th:href="@{/div3/1}">3-1</a><br>

</body>
</html>

注意:我们在后面中的登录中,用的都是系统自带的登录,这里这个并没有用。

2.依赖

功能使用到的依赖

1
2
3
4
5
6
7
8
<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>

实现

页面跳转控制

页面控制RouterController.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
package com.tothefor.controller;

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

@Controller
public class RouterController {

@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}

@RequestMapping("/div1/{pid}")
public String div1(@PathVariable("pid")int id){
return "views/div1/"+id;
}

@RequestMapping("/div2/{pid}")
public String div2(@PathVariable("pid")int id){
return "views/div2/"+id;
}

@RequestMapping("/div3/{pid}")
public String div3(@PathVariable("pid")int id){
return "views/div3/"+id;
}

}

其中,@RequestMapping(“/div1/{pid}”) 后面的{pid} 表示传入一个参数,然后@PathVariable(“pid”)int id 表示把传入的参数给id。这样可以实现一个目录下的不同页面的配置,就不用每一个页面配置一个。

在把页面控制写完之后可以跑一下,看是否成功。

配置类

首先需要再加入一个依赖:

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

再写一个类继承WebSecurityConfigurerAdapter,再用注解@EnableWebSecurity表示被Spring接管。并重写一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tothefor.config;


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;

@EnableWebSecurity
public class SecutityConfig extends WebSecurityConfigurerAdapter{

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

授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.tothefor.config;


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;

@EnableWebSecurity
public class SecutityConfig extends WebSecurityConfigurerAdapter{

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll() //允许所有人访问,是hasAnyRole
.antMatchers("/div1/**").hasRole("div1") //只有角色是div1的才能访问/div1下的所有页面,下同
.antMatchers("/div2/**").hasRole("div2")
.antMatchers("/div3/**").hasRole("div3");
}
}

接下来,再一次跑项目,可以发现:首页可以访问,但div下面的访问不了。访问不了的,会自动到报错页面。我们可以自定义跳转到报错页面。添加

1
2
//没有权限时,会默认跳到登录页面(不是自己的,是自带的)
http.formLogin();

没有权限时,会跳到登录页面(/login),但这个url我们并没有设置,而且这个页面不是自己写的,而是自带的。具体可查看源码。而且我们后面进行登录都是在这个自带的页面进行登录。

认证

有内存认证(inMemoryAuthentication)和数据库认证(jdbcAuthentication)。用法相同,这里用内存认证为例。在授权类中再重写另外一个方法。

1
2
3
4
5
6
7
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("loong").password("123456").roles("div1") //给loong设置角色
.and()
.withUser("root").password("123456").roles("div1","div2","div3");
}

正常情况这里都是从数据库中读取数据的。

这样配置好之后跑项目再访问时,会报一个错误,说的是密码并没有进行加密。还需要进行密码加密处理:

1
2
3
4
5
6
7
8
9
10
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("loong")
.password(new BCryptPasswordEncoder().encode("123456")).roles("div1")
.and()
.withUser("root")
.password(new BCryptPasswordEncoder().encode("123456")).roles("div1","div2","div3");

}

这下再跑项目就可以实现登录后访问。

连接数据库

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
package com.tothefor.config;


import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
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.bcrypt.BCryptPasswordEncoder;

import javax.sql.DataSource;

@EnableWebSecurity
public class SecutityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private DataSource dataSource;

private DruidDataSource ds = (DruidDataSource) dataSource;

//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(ds)
.withDefaultSchema()
.withUser("loong").password("123").roles("")
.and()
.withUser("root").password("123").roles("");
}
}

注销

在授权方法中加入:

1
http.logout()

这样就可以实现注销功能。然后前端

1
<a th:href="@{/logout}">注销</a>

注意:这里的/logout 不是我们自己配置的,而是自带的。

还能实现在成功注销后跳转到的页面:

1
http.logout().logoutSuccessUrl("/"); //跳转到首页

权限控制

当用户对某一区域没有权限时,也不给用户显示。也可以是用户未登录时显示登陆,登陆后显示注销。

这就需要Thymeleaf和Security的整合,还需要添加一个依赖:

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>

因为需要操作前端页面,所以还需要导入命名空间:

1
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

但官方推荐使用:

1
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

使用官方的会有提示!

登录与注销

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
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>

<!-- 未登录 -->
<div sec:authorize="!isAuthenticated()"> //isAuthenticated()判断是否登录
<a href="">登录</a>
<a href="">注册</a>
</div>

<!-- 登录后 -->
<div sec:authorize="isAuthenticated()">
用户名:<span sec:authentication="name"></span>
角 色:<span sec:authentication="principal.authorities"></span>
</div>
//principal.authorities 获取角色

<div sec:authorize="isAuthenticated()">
<a th:href="@{/logout}">注销</a>
</div>

<a th:href="@{/div1/1}">1-1</a> <br>
<a th:href="@{/div2/1}">2-1</a><br>
<a th:href="@{/div3/1}">3-1</a><br>

</body>
</html>

页面显示

1
<div sec:authorize="hasRole('div1')"></div>

如果有角色div1,则div显示。

关闭Csrf功能

默认是打开的。在授权中加入。

1
http.csrf().disable();