什么是 Spring Security

Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

对应的 maven 坐标:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>

Spring Security 入门案例

1、创建 maven 工程

创建 maven 工程,打包方式为 war,并在 pom.xml 导入相关的依赖

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
70
71
72
<packaging>war</packaging>

<properties>
<spring.version>5.0.5.RELEASE</spring.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>

<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>88</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

2、配置 web.xml

在 web.xml 中主要配置 SpringMVC 的 DispatcherServlet 和用于整合第三方框架的 DelegatingFilterProxy

DelegatingFilterProxy(代理过滤器,真正的过滤器在 spring 的配置文件),用于整合 Spring Security

DelegatingFilterProxy 是 Spring 中定义的一个 Filter 实现类,其作用是代理真正的 Filter 实现类,也就是说在调用 DelegatingFilterProxy 的 doFilter() 方法时实际上调用的是其代理 Filter 的 doFilter() 方法。其代理 Filter 必须是一个 Spring bean 对象,所以使用 DelegatingFilterProxy 的好处就是其代理 Filter 类可以使用 Spring 的依赖注入机制方便自由的使用 ApplicationContext 中的 bean。

那么 DelegatingFilterProxy 如何知道其所代理的 Filter 是哪个呢?这是通过其自身的一个叫 targetBeanName 的属性来确定的,通过该名称,DelegatingFilterProxy 可以从 WebApplicationContext 中获取指定的 bean 作为代理对象。该属性可以通过在 web.xml 中定义 DelegatingFilterProxy 时通过 init-param 来指定,如果未指定的话将默认取其在 web.xml 中声明时定义的名称。

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">


<filter>
<!-- 整合Spring Security时过滤器的名称必须为springSecurityFilterChain,
否则会抛出NoSuchBeanDefinitionException异常 -->
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- spring mvc的核心控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

</web-app>

在上述配置中,DelegatingFilterProxy 代理的就是名为 SpringSecurityFilterChain 的 Filter。

需要注意的是被代理的 Filter 的初始化方法 init() 和销毁方法 destroy() 默认是不会被执行的。通过设置 DelegatingFilterProxy 的 targetFilterLifecycle 属性为 true,可以使被代理 Filter 与 DelegatingFilterProxy 具有同样的生命周期。

3、 配置 spring-security.xml

在 spring-security.xml 中主要配置 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
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">

<!-- 配置哪些链接可以放行(没有认证通过也可以访问的资源)
http:用于定义相关权限控制
security="none":没有权限
pattern="/login.html":没有任何权限,可以访问login.html -->

<!--<security:http security="none" pattern="/login.html"></security:http>-->

<!-- 定义哪些链接不可以放行(必须通过认证才能访问的资源),及需要有角色,有权限才可以放行访问资源
<security:http auto-config="true" use-expressions="true">

auto-config="true":开启自动配置 由spring security提供登录页面,提供登录的url地址,退出的url地址
use-expressions="true":使用表达式的方式控制权限
security:intercept-url:定义哪些链接不可以放行,需要当前角色和权限才能放行
pattern="/**":要求系统中的所有资源,都必须通过角色和权限才能访问
access:指定角色和权限

如果使用表达式use-expressions="true"
access="hasRole('ROLE_ADMIN'):表示具有ROLE_ADMIN的角色才能访问系统的资源

如果不使用表达式use-expressions="false"
access="ROLE_ADMIN:表示具有ROLE_ADMIN的角色才能访问系统的资源-->

<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
</security:http>

<!-- 认证管理:定义登录账号和密码,并授予当前用户访问的角色或权限
authentication-manager:认证管理器,用于处理认证操作
authentication-provider:认证提供者,执行具体的认证逻辑
user-service用来指定用户信息,例如name,password和权限

(1):将用户名和密码:当前用户具有的角色,写死到配置文件中(现在测试用)
security:user name="{noop}admin" :登录名
{noop}:表示当前使用的密码为明文。表示当前密码不需要指定加密方式

authorities="ROLE_ADMIN" :角色(ROLE_ADMIN),权限
password="admin" :密码
(2):用户名和密码,当前用户具有的角色,从数据库中查询(以后开发使用) -->

<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}admin"></security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>

</beans>

请求 url 地址:在上面 pom.xml 文件中配置的端口:http://localhost:88/

访问该地址会自动调整到由 spring Security 提供的登录页面

之后在 webapp 文件夹下面,新建 index.html,可以正常访问 index.html

Spring Security 进阶

1、需要解决的问题

实际环境下往往有一些资源不需要认证也可以访问,也就是实现匿名访问

登录页面是由框架生成的,要设置使用自己的登录页面

直接将用户名和密码配置在了配置文件中,要将用户名和密码保存在数据库中

在配置文件中配置的密码使用明文,要实现对密码进行加密

2、配置可匿名访问的资源

在 spring-security.xml 文件中配置,指定哪些资源可以匿名访问

注意:配置信息要放在拦截规则前面,放到所有配置最前面就可以。

在这里我在 webapp 下创建了 pages 文件夹,用来存放公共资源

1
<security:http security="none" pattern="/pages/**" />

3、使用指定的登录页面

在 webapp 文件夹下面,创建一个 login.html 作为项目的登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>登录</title>
</head>
<body>
<form action="/login.do" method="post">
账号:<input type="text" name="username" /><br />
密码:<input type="password" name="password" /><br />
<input type="submit" value="submit" />
</form>
</body>
</html>

修改 spring-security.xml 文件,指定 login.html 页面可以匿名访问,否则无法访问。

1
<security:http security="none" pattern="/login.html" />

继续修改 spring-security.xml 文件,加入表单登录信息的配置(配置需要放在拦截规则配置里面)

同时需要关闭 CsrfFilter 过滤器

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
<security:http auto-config="false" use-expressions="true">

<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>

<!--form-login:定义表单登录信息
login-page="/login.html":表示指定登录页面

username-parameter="username":使用登录名的名称,默认值是username
默认参数username和上面login.html中name="username"相对应
password-parameter="password":使用登录名的密码,默认值是password
默认参数是password和上面login.html中name="password"相对应
目前账号密码还是在认证管理中定义的admin,admin

login-processing-url="/login.do":表示登录的url地址
default-target-url="/index.html":登录成功后的url地址
authentication-failure-url="/login.html":认证失败后跳转的url地址,失败后指定/login.html
always-use-default-target="true":登录成功后,始终跳转到default-target-url指定的地址,即登录成功的默认地址 -->

<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/login.html"
always-use-default-target="true"/>

<!-- csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)-->
<security:csrf disabled="true"></security:csrf>

</security:http>

4、关于 csrf

CSRF(Cross Site Request Forgery)即跨站请求伪造。

跨站请求伪造(CSRF)是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。例如,这些非预期请求可能是通过在跳转链接后的 URL 中加入恶意参数来完成。

客户端防范:对于数据库的修改请求,全部使用 POST 提交,禁止使用 GET 请求。
服务器端防范:一般的做法是在表单里面添加一段隐藏的唯一的 token(请求令牌)。

5、从数据库查询用户信息

如果我们要从数据库动态查询用户信息,就必须按照 spring security 框架的要求提供一个实现 UserDetailsService 接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

① 创建 java bean 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.pojo;

/**
* @author: ShiGuang
* @create: 2021-06-01 20:03
* @description:
*/
public class User {
private String username;
private String password;
private String telephone;

//.......................

② 实现 UserDetailsService 接口

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
package com.atguigu.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author: ShiGuang
* @create: 2021-06-01 20:05
* @description:
*/
@Component
public class UserService implements UserDetailsService {

// 假设map是数据库
// 这里使用的全类名的User,即com.atguigu.pojo.User,没有导包
// 因为重写方法最后返回的User是org.springframework.security.core.userdetails中的User
static Map<String, com.atguigu.pojo.User> map = new HashMap<String, com.atguigu.pojo.User>();

static {
com.atguigu.pojo.User user1 = new com.atguigu.pojo.User();
user1.setUsername("admin");
user1.setPassword("admin");
user1.setTelephone("12345");

com.atguigu.pojo.User user2 = new com.atguigu.pojo.User();
user2.setUsername("root");
user2.setPassword("root");
user2.setTelephone("54321");

map.put(user1.getUsername(), user1);
map.put(user2.getUsername(), user2);
}

/**
* @param username 用户登录的名称
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询数据库
com.atguigu.pojo.User user = map.get(username);

if (user == null) {
// 说明用户没有注册
return null;
}

// 获取密码
String password = user.getPassword();
List<GrantedAuthority> lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("add"));
lists.add(new SimpleGrantedAuthority("delete"));
lists.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

return new User(username, password, lists);
}
}

③ 修改 spring-security.xml

配置自动扫描的包,并在 spring 配置文件中注册 UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时,spring security 框架会调用 UserService 的 loadUserByUsername 方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。

1
2
3
4
5
6
7
8
9
10
    <context:component-scan base-package="com"/>

<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
<!-- <security:user-service>
<security:user name="admin" authorities="ROLE_ADMIN" password="{noop}admin"></security:user>
</security:user-service>
</security:authentication-provider>-->
</security:authentication-manager>

6、加密

前面我们使用的密码都是明文的,这是非常不安全的。一般情况下用户的密码需要进行加密后再保存到数据库中。

常见的密码加密方式

3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码

MD5、SHA1:使用单向 HASH 算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解

彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。一般主流的彩虹表都在 100G 以上。 这样的表常常用于恢复由有限集字符组成的固定长度的纯文本密码。这是空间/时间替换的典型实践, 比每一次尝试都计算哈希的暴力破解处理时间少而储存空间多,但却比简单的对每条输入散列翻查表的破解方式储存空间少而处理时间多。使用加 salt 的 KDF 函数可以使这种攻击难以实现。彩虹表是马丁·赫尔曼早期提出的简单算法的应用

什么是 md5 盐值?

SALT 值(盐值)属于随机值。用户注册时,系统用来和用户密码进行组合而生成的随机数值,称作 salt 值,通称为加盐值。

1、背景:系统通常把用户的密码如 MD5 加密后,以密文形式保存在数据库中,来防止黑客偷窥。

2、产生:随着对 MD5 密文查询工具的出现,而很多用户的密码又设置简单,单纯的对用户密码进行 MD5 加密后保存,用密文很容易就能反查询得到某用户的密码。

3、原理:为用户密码添加 Salt 值,使得加密的得到的密文更加冷僻,不宜查询。即使黑客有密文查询到的值,也是加了 salt 值的密码,而非用户设置的密码。salt 值是随机生成的一组字符串,可以包括随机的大小写字母、数字、字符,位数可以根据要求而不一样。

4、用途:当用户首次提供密码时(通常是注册时),由系统自动添加随机生成的 salt 值,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的加盐值,然后散列,再比较散列值,已确定密码是否正确。

5、其它:经过添加 salt 值处理的密码,即使用户设置的原密码是相通的,数据库中的密文却是不同的。

为了加强单向散列计算的安全性,会给散列算法加点盐(salt),salt 相当于加密的密钥,增加破解的难度。常用的单向散列算法有 MD5、SHA 等。

单向散列算法还有一个特点就是输入的任何微小变化都会导致输出的完全不同,这个特性有时也会被用来生成信息摘要、计算具有高离散程度的随机数等用途。

什么是 Bcrypt

Bcrypt 就是一款加密工具,可以比较方便地实现数据的加密工作。

Bcrypt 将 salt 随机并混入最终加密后的密码,验证时也无需单独提供之前的 salt,从而无需单独处理 salt 问题。你也可以简单理解为它内部自己实现了随机加盐处理

Spring Security 中的密码加密算法

spring security 中的 BCryptPasswordEncoder 方法采用 SHA-256 +随机盐+密钥对密码进行加密。SHA 系列是 Hash 算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用 Hash 处理,其过程是不可逆的。

(1)加密(encode):注册用户时,使用 SHA-256+随机盐+密钥把用户输入的密码进行 hash 处理,得到密码的 hash 值,然后将其存入数据库中。

(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过 Hash 处理,是不可逆的),而是使用相同的算法把用户输入的密码进行 hash 处理,得到密码的 hash 值,然后将其与从数据库中查询到的密码 hash 值进行比较。如果两者相同,说明用户输入的密码正确。

这正是为什么处理密码时要用 hash 算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码。

SpringSecurity 加盐加密

加密后的格式一般为

$2a$10$cJ/afeJlWJz.88hs1Ha6tejpk0ljGF/yLxi9hTYoEwk50oHmACNWC

加密后字符串的长度为固定的 60 位。其中

$是分割符,无意义;

2a 是 bcrypt 加密版本号;

10 是 cost 的值;

而后的前 22 位是 salt 值;

再然后的字符串就是密码的密文了。

7、实现加密

① 指定密码加密对象

在 spring-security.xml 文件中指定密码加密对象

1
2
3
4
5
6
7
8
9
10
<context:component-scan base-package="com"/>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>

<!--配置密码加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

② 修改 UserService 实现类

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
70
71
72
73
74
package com.atguigu.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* @author: ShiGuang
* @create: 2021-06-01 20:05
* @description:
*/
@Component
public class UserService implements UserDetailsService {
// 模拟数据库中的用户数据
static Map<String, com.atguigu.pojo.User> map = new HashMap<String, com.atguigu.pojo.User>();

static {
com.atguigu.pojo.User user1 = new com.atguigu.pojo.User();
user1.setUsername("admin");
// user1的密码设置的是admin123
user1.setPassword("$2a$10$xKDLqHaeiWH04BJ495p4Ou0PA2oOo2JxBBdDJbqzww2j1ziJNnL3C");
user1.setTelephone("12345");

com.atguigu.pojo.User user2 = new com.atguigu.pojo.User();
user2.setUsername("root");
// user2的密码设置的是root123
user2.setPassword("$2a$10$zKyYA3KBHLdbpuan.S4xD.b5oSIs1r7kWel3Y4wCUYSEwU.un5KoC");
user2.setTelephone("54321");

map.put(user1.getUsername(), user1);
map.put(user2.getUsername(), user2);
}

/**
* 根据用户名加载用户信息
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("username" + username);
//模拟根据用户名查询数据库
com.atguigu.pojo.User userInDb = map.get(username);

if (userInDb == null) {
// 根据用户名没有查询到用户,抛出异常,表示登录名输入有误
return null;
}
// 模拟数据库中的密码,后期需要查询数据库
// String passwordInDb ="{noop}" + userInDb.getPassword();
String passwordInDb = userInDb.getPassword();
// 授权,后期需要改为查询数据库动态获得用户拥有的权限和角色
List<GrantedAuthority> lists = new ArrayList<>();
lists.add(new SimpleGrantedAuthority("add"));
lists.add(new SimpleGrantedAuthority("delete"));
lists.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

// public User(String username, String password,Collection<? extends GrantedAuthority > authorities)
// 返回User,参数一:存放登录名,
// 参数二:存放数据库查询的密码(数据库获取的密码,默认会和页面获取的密码进行比对,成功跳转到成功页面,失败回到登录页面,并抛出异常表示失败),存放当前用户具有的角色
return new User(username, passwordInDb, lists);
}
}

③ 对页面配置多种校验规则

为了测试方便,首先在项目的 webapp 文件夹下面创建 a.html、b.html、c.html、d.html 几个页面

修改 spring-security.xml 文件,将校验规则放到拦截规则里面

前提:

  • <security:http auto-config=“true” use-expressions=“true”>
  • 需要关闭全局拦截
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
<security:http auto-config="true" use-expressions="true">

<!--<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>-->

<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/index.html" access="isAuthenticated()" />
<security:intercept-url pattern="/a.html" access="isAuthenticated()" />

<!--拥有add权限就可以访问b.html页面-->
<security:intercept-url pattern="/b.html" access="hasAuthority('add')" />

<!--拥有ROLE_ADMIN角色就可以访问c.html页面,
注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_(最好还是自己加上)-->
<!-- ADMIN区分大小写 -->
<security:intercept-url pattern="/c.html" access="hasRole('ADMIN')" />

<!--拥有ROLE_ROOT角色才可以访问d.html页面,需要在前面UserService类中为lists增加角色-->
<security:intercept-url pattern="/d.html" access="hasRole('ROLE_ROOT')" />

<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/login.html"
always-use-default-target="true"/>

<security:csrf disabled="true"></security:csrf>

</security:http>

④ 注解方式权限控制(对类)

Spring Security 除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。例如 Controller 中的某个方法要求必须具有某个权限才可以访问,此时就可以使用 Spring Security 框架提供的注解方式进行控制。

[1]在 spring-security.xml 文件中配置组件扫描和mvc 的注解驱动,用于扫描 Controller

1
2
<context:component-scan base-package="com"/>
<mvc:annotation-driven></mvc:annotation-driven>

[2]在 spring-security.xml 文件中开启权限注解支持,放到最前面就可以

1
2
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />

[3]创建 Controller 类并在 Controller 的方法上加入注解(@PreAuthorize)进行权限控制

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
package com.atguigu.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author: ShiGuang
* @create: 2021-06-01 23:42
* @description:
*/
@RestController
@RequestMapping("/hello")
public class ControllerTest {

@RequestMapping("/add")
@PreAuthorize("hasAuthority('add')")//表示用户必须拥有add权限才能调用当前方法
public String add() {
System.out.println("add...");
return "success";
}

@RequestMapping("/update")
@PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法
public String update() {
System.out.println("update...");
return "success";
}

@RequestMapping("/delete")
@PreAuthorize("hasRole('ROLE_ROOT')")//表示用户必须拥有ROLE_ROOT角色才能调用当前方法
public String delete() {
System.out.println("delete...");
return "success";
}
}

效果:使用拥有 ROLE_ADMIN 角色去访问 /hello/delete.do 没有权限

8、实现退出登录

用户完成登录后 Spring Security 框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。接下来实现用户退出登录。

①index 页面增加退出登录链接

1
<a href="/logout.do">退出登录</a>

② 在 spring-security 中定义

同样需要放在拦截规则里

1
2
3
4
5
6
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
invalidate-session="true" 默认为true,用户在退出后Http session失效 -->
<security:logout logout-url="/logout.do" logout-success-url="/login.html" invalidate-session="true"/>

9、总结

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">

<!-- 权限控制:指定不需要进行权限校验的资源 -->
<security:http security="none" pattern="/js/**"/>
<security:http security="none" pattern="/css/**"/>
<security:http security="none" pattern="/img/**"/>
<security:http security="none" pattern="/plugins/**"/>
<security:http security="none" pattern="/login.html"/>

<!-- 开启注解方式权限控制 -->
<security:global-method-security pre-post-annotations="enabled"/>

<!-- 权限控制:指定需要的特定角色和权限才可以放行的资源-->
<security:http auto-config="true" use-expressions="true">

<security:headers>
<!--看实际情况,设置在页面可以通过iframe,访问受保护的页面,默认为不允许访问-->
<security:frame-options policy="SAMEORIGIN"></security:frame-options>
</security:headers>

<!-- 增加一条拦截index页面,方便输入 http://localhost:端口号/ 后自动跳转到index时被拦截而跳转到登录页面-->
<security:intercept-url pattern="/index.html" access="isAuthenticated()"/>
<!-- 配置需要权限的资源-->
<security:intercept-url pattern="/pages/**" access="isAuthenticated()"/>

<!--配置自定义登录页面-->
<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/pages/main.html"
authentication-failure-url="/login.html"
always-use-default-target="true"></security:form-login>
<!-- 配置自定义退出页面 -->
<security:logout logout-url="/logout.do"
logout-success-url="/login.html" invalidate-session="true"/>

<!-- 关闭CSRF,使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)-->
<security:csrf disabled="true"></security:csrf>
</security:http>


<!-- 配置密码加密对象 -->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>


<!-- 认证管理,定义登录账号名和密码,并授予访问的角色、权限 -->
<security:authentication-manager>
<!-- springSecurityUserService显示为红色,是因为没有配置扫描的包
我在spring-mvc配置文件中引用该配置文件并配置扫描包,所以这里就不配置了,防止冲突-->
<security:authentication-provider user-service-ref="springSecurityUserService">
<!--指定密码加密策略-->
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>

</beans>