Spring Security 5 基础教程之三用户授权

大纲

前言

版本说明

组件版本
SpringBoot2.2.1.RELEASE
Spring Security5.2.1.RELEASE

用户授权介绍

用户授权是指验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

校验方式

权限校验

  • hasAuthority:只能校验一个权限,例如 USER,返回的 UserDetailsAuthority 只要与这里匹配即可访问被保护的资源
  • hasAnyAuthority:可以校验多个权限,使用逗号分隔,例如 USER,ADMIN,只要用户拥有其中任意一个权限即可访问被保护的资源

角色校验

  • hasRole:只能校验一个角色,例如 ROLE_USER,在返回的 UserDetailsAuthority 中需要添加 ROLE_ 前缀,例如 ROLE_USER
  • hasAnyRole:可以校验多个角色,使用逗号分隔,例如 ROLE_USER,ROLE_ADMIN,只要用户拥有其中任意一个角色即可访问被保护的资源,在返回的 UserDetailsAuthority 中需要添加 ROLE_ 前缀,例如 ROLE_USER

校验方式的区别

hasAuthority 的源码如下,最终调用了 access 方法,传入了权限表达式 hasAuthority('xxx')

1
2
3
4
5
6
7
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}

private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}

hasRole 的源码如下,会自动给传入的字符串加上 ROLE_ 前缀

1
2
3
4
5
6
7
8
9
10
11
12
13
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}

private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException(
"role should not start with 'ROLE_' since it is automatically inserted. Got '"
+ role + "'");
}
return "hasRole('ROLE_" + role + "')";
}

可以看到,hasRole 的处理逻辑和 hasAuthority 似乎一模一样,但不同的是,hasRole 这里会自动给传入的字符串加上 ROLE_ 前缀,所以在数据库中的角色字符串需要加上 ROLE_ 前缀。即数据库中存储的用户角色如果是 ROLE_admin,那对于 Spring Security 来说就是 admin 角色。也就是说,使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,数据库什么样这里就是什么样的。而 hasRole 则不同,代码里如果写的是 admin,Spring Security 框架会自动加上 ROLE_ 前缀,所以数据库就必须是 ROLE_admin

用户授权使用

第一种方式

实现 UserDetailsService 接口,指定用户名、登录密码、权限、角色,并自定义登录页面。

代码下载

本章节完整的案例代码可以直接从 GitHub 下载对应章节 spring-security5-05

  • 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
  • /src/main/resources/static/ 目录下,创建 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>Login Page</title>
</head>
<body>
<div>
<form action="/user/login" method="post">
用户名: <input type="text" name="username"/><br/>
密码: <input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</div>
</body>
</html>
  • 创建控制器类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class HelloController {

@RequestMapping("/hello")
public String hello() {
return "Hello Spring Security";
}

@RequestMapping("/goodbye")
public String goodbye() {
return "Goodbye Spring Security";
}

@RequestMapping("/goodnight")
public String goodnight() {
return "Goodnight Spring Security";
}

}
  • 实现 UserDetailsService 接口,指定用户名、登录密码、权限、角色。特别注意,这里的角色必须以 ROLE_ 前缀开头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;

@Service("userDetailsService")
public class LoginServiceImpl implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authors = AuthorityUtils.commaSeparatedStringToAuthorityList("hr,manager,ROLE_sale");
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encodePassword = passwordEncoder.encode("123456");
return new User("admin", encodePassword, authors);
}

}
  • 创建配置类,指定 UserDetailsService,并配置自定义的登录页面与访问过滤规则。特别注意,这里的角色不需要以 ROLE_ 前缀开头,因为 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
import org.springframework.beans.factory.annotation.Autowired;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 配置哪个 URL 为登录页面
.loginProcessingUrl("/user/login") // 配置哪个为登录接口的 URL
.defaultSuccessUrl("/hello") // 配置登录成功之后跳转到哪个 URL
.and().authorizeRequests()
.antMatchers("/goodnight").hasRole("sale") // 需要用户拥有 sale 角色才能访问对应的 URL
.antMatchers("/goodbye").hasAuthority("hr") // 需要用户拥有 hr 权限才能访问对应的 URL
.antMatchers("/hello").hasAuthority("manager") // 需要用户拥有 manager 权限才能访问对应的 URL
.antMatchers("/", "/login.html", "/user/login").permitAll() // 配置哪些 URL 不需要登录就可以直接访问
.anyRequest().authenticated() // 配置其他 URL 需要登录才能访问
.and().csrf().disable(); // 关闭 CSRF 防护
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}

第二种方式

在实现 UserDetailsService 接口的基础上,从数据库中读取用户名、登录密码、权限、角色信息,并自定义登录页面。

代码下载

本章节完整的案例代码可以直接从 GitHub 下载对应章节 spring-security5-06

初始化数据库

特别注意

在数据库中存储的角色都以 ROLE_ 前缀开头,如 ROLE_sale

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
-- 创建数据库
CREATE DATABASE `spring_security_study` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 切换数据库
USE `spring_security_study`;

-- 创建用户表
create table user(
id bigint primary key auto_increment,
username varchar(20) unique not null,
password varchar(100)
);

-- 创建角色表
create table role(
id bigint primary key auto_increment,
name varchar(20),
code varchar(20)
);

-- 创建权限表
create table permission(
id bigint primary key auto_increment,
name varchar(20),
url varchar(100),
parent_id bigint,
code varchar(20)
);

-- 创建用户角色关联表
create table user_role(
uid bigint,
rid bigint
);

-- 创建角色权限关联表
create table role_permission(
rid bigint,
pid bigint
);

-- 插入初始化数据(用户密码是123456)
insert into user values(1, 'wangwu', '$2a$10$IwvZiSm3vdhRtdyU8rJQz.pb9U/kYHorC2aQqwtFX.RVuFFHOpt82');
insert into user values(2, 'zhangsan', '$2a$10$IwvZiSm3vdhRtdyU8rJQz.pb9U/kYHorC2aQqwtFX.RVuFFHOpt82');
insert into role values(1, '管理员', 'ROLE_admin');
insert into role values(2, '销售员', 'ROLE_sale');
insert into permission values(1, '系统管理', '', 0, 'system');
insert into permission values(2, '销售产品', '', 0, 'sale_product');
insert into user_role values(1, 1);
insert into user_role values(2, 2);
insert into role_permission values(1, 1);
insert into role_permission values(1, 2);
insert into role_permission values(2, 2);

引入 Maven 依赖

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>

<properties>
<mybatis-plus.version>3.0.5</mybatis-plus.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

创建 Mapper 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Mapper
public interface UserMapper extends BaseMapper<User> {

/**
* 根据用户 ID 查询角色
* @param userId
* @return
*/
List<Role> selectRoleByUserId(@Param("userId") Long userId);

/**
* 根据用户 ID 查询权限
* @param userId
* @return
*/
List<Permission> selectPermissionByUserId(@Param("userId") Long userId);

}

创建 SQL 映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<mapper namespace="com.clay.security.mapper.UserMapper">

<select id="selectRoleByUserId" resultType="com.clay.security.entity.Role">
SELECT r.id, r.name, r.code
FROM role r
INNER JOIN user_role ru ON r.id = ru.rid
where ru.rid = #{userId}
</select>

<select id="selectPermissionByUserId" resultType="com.clay.security.entity.Permission">
SELECT p.id, p.name, p.url, p.parent_id, p.code
FROM permission p
INNER JOIN role_permission rp ON p.id = rp.pid
INNER JOIN role r ON r.id = rp.rid
INNER JOIN user_role ru ON r.id = ru.rid
where ru.rid = #{userId}
</select>

</mapper>

实现 UserDetailsService 接口

实现 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
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.clay.security.entity.Permission;
import com.clay.security.entity.Role;
import com.clay.security.entity.User;
import com.clay.security.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;
import java.util.ArrayList;
import java.util.List;

@Service("userDetailsService")
public class LoginServiceImpl implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
User userEntity = userMapper.selectOne(queryWrapper);
if (userEntity == null) {
throw new UsernameNotFoundException("User name not exist");
}

// 查询用户角色
List<Role> roleList = userMapper.selectRoleByUserId(userEntity.getId());

// 查询用户权限
List<Permission> permissionList = userMapper.selectPermissionByUserId(userEntity.getId());

// 声明一个角色权限集合
List<GrantedAuthority> authorList = new ArrayList<>();

// 处理角色
roleList.forEach(role -> {
authorList.add(new SimpleGrantedAuthority(role.getCode()));
});

// 处理权限
permissionList.forEach(permission -> {
authorList.add(new SimpleGrantedAuthority(permission.getCode()));
});

return new org.springframework.security.core.userdetails.User(userEntity.getUsername(),
userEntity.getPassword(), authorList);
}

}

创建核心配置类

创建配置类,指定 UserDetailsService,并配置自定义的登录页面与访问过滤规则。特别注意,这里的角色不需要以 ROLE_ 前缀开头,因为 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
import org.springframework.beans.factory.annotation.Autowired;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html") // 配置哪个 URL 为登录页面
.loginProcessingUrl("/user/login") // 配置哪个为登录接口的 URL
.defaultSuccessUrl("/hello") // 配置登录成功之后跳转到哪个 URL
.and().authorizeRequests()
.antMatchers("/goodnight").hasRole("sale") // 需要用户拥有 sale 角色才能访问对应的 URL
.antMatchers("/goodbye").hasAuthority("system") // 需要用户拥有 system 权限才能访问对应的 URL
.antMatchers("/hello").hasAuthority("sale_product") // 需要用户拥有 sale_product 权限才能访问对应的 URL
.antMatchers("/", "/login.html", "/user/login").permitAll() // 配置哪些 URL 不需要登录就可以直接访问
.anyRequest().authenticated() // 配置其他 URL 需要登录才能访问
.and().csrf().disable(); // 关闭 CSRF 防护
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

}

创建配置文件

这里主要配置数据源和 MyBatis-Plus。

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
spring:
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://127.0.0.1:3306/spring_security_study?characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
username: root
password: 123456

# Mybatis-Plus
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
typeAliasesPackage: com.clay.security.**.entity
# MyBatis-Plus 配置
global-config:
db-config:
id-type: AUTO
banner: false
# MyBatis 原生配置
configuration:
map-underscore-to-camel-case: true
call-setters-on-nulls: true
jdbc-type-for-null: 'null'
# 打印 SQL 语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

用户授权注解

代码下载

本章节完整的案例代码可以直接从 GitHub 下载对应章节 spring-security5-07

@Secured 注解

在使用 @Secured 注解之前,必须先使用 @EnableGlobalMethodSecurity(securedEnabled = true) 开启 Spring Security 方法级别的安全功能。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MainApplication {

public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}

}

@Secured 注解用于判断当前用户是否拥有角色,在控制器方法上添加 @Secured 注解后,当用户拥有对应的角色才能访问。特别注意,这里的角色必须以 ROLE_ 前缀开头。

1
2
3
4
5
6
7
8
9
10
@RestController
public class HelloController {

@Secured({"ROLE_admin", "ROLE_sale"})
@RequestMapping("/hello")
public String hello() {
return "Hello Spring Security";
}

}

@PreAuthorize 注解

在使用 @PreAuthorize 注解之前,必须先使用 @EnableGlobalMethodSecurity (prePostEnabled = true) 开启 Spring Security 方法级别的安全功能。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MainApplication {

public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}

}

@PreAuthorize 注解用于判断当前用户是否拥有角色或者权限,适合在方法执行之前进行校验;它可以将登录用户的角色、权限参数传递到表达式中。特别注意,这里的角色必须以 ROLE_ 前缀开头。

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
@RestController
public class HelloController {

@PreAuthorize("hasRole('ROLE_sale')")
@RequestMapping("/hello")
public String hello() {
return "Hello Spring Security";
}

@PreAuthorize("hasAnyRole('ROLE_sale,ROLE_admin')")
@RequestMapping("/hello2")
public String hello2() {
return "Hello Spring Security";
}

@PreAuthorize("hasAuthority('manager')")
@RequestMapping("/goodbye")
public String goodbye() {
return "Goodbye Spring Security";
}

@PreAuthorize("hasAnyAuthority('manager,hr')")
@RequestMapping("/goodbye2")
public String goodbye2() {
return "Goodbye Spring Security";
}

}

@PostAuthorize 注解

在使用 @PostAuthorize 注解之前,必须先使用 @EnableGlobalMethodSecurity (prePostEnabled = true) 开启 Spring Security 方法级别的安全功能。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MainApplication {

public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}

}

@PostAuthorize 注解用于判断当前用户是否拥有角色或者权限,适合在方法执行之后进行校验,即适合验证带有返回值的权限;它可以将登录用户的角色、权限参数传递到表达式中。特别注意,这里的角色必须以 ROLE_ 前缀开头。

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
@RestController
public class HelloController {

@PostAuthorize("hasRole('ROLE_sale')")
@RequestMapping("/hello")
public String hello() {
return "Hello Spring Security";
}

@PostAuthorize("hasAnyRole('ROLE_sale,ROLE_admin')")
@RequestMapping("/hello2")
public String hello2() {
return "Hello Spring Security";
}

@PostAuthorize("hasAuthority('manager')")
@RequestMapping("/goodbye")
public String goodbye() {
return "Goodbye Spring Security";
}

@PostAuthorize("hasAnyAuthority('manager,hr')")
@RequestMapping("/goodbye2")
public String goodbye2() {
return "Goodbye Spring Security";
}

}

@PostFilter 注解

@PostFilter 注解用于在方法执行之后对数据进行过滤,表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素。在下述的例子中,数据过滤之后只会留下 wangwu 的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class HelloController {

@PreAuthorize("hasRole('ROLE_sale')")
@PostFilter("filterObject.username == 'wangwu'")
@RequestMapping("/goodbye")
public List<UserInfo> goodbye() {
List<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1L, "wangwu", "123456"));
list.add(new UserInfo(2L, "zhangsan", "123456"));
return list;
}

}

@PreFilter 注解

@PreFilter 注解用于在方法执行之前对数据进行过滤,表达式中的 filterObject 引用的是方法参数 List 中的某一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class HelloController {

@PreAuthorize("hasRole('ROLE_sale')")
@PreFilter(value = "filterObject.id % 2 == 0")
@PostMapping("/goodbye")
public List<UserInfo> goodbye(@RequestBody List<UserInfo> list) {
list.forEach(t -> {
System.out.println(t.getId() + " " + t.getUsername());
});
return list;
}

}

上述代码使用 PostMan 进行测试,数据过滤之后只会留下 wangwu 的数据,如下图所示:

自定义 403 页面

  • 引入 thymeleaf 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • /src/main/resources/static/ 目录下,创建 unauth.html 页面
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unauth Page</title>
</head>
<body>
<h2>没有权限访问此页面</h2>
</body>
</html>
  • 在配置类中指定自定义的 403 页面
1
2
3
4
5
6
7
8
9
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedPage("/unauth.html");
}

}