Spring Boot 2系列(三十):Spring Security 安全框架详解和集成
Spring Security 为基于 Spring 的应用程序提供安全保护,是一个功能强大且可高度自定义的自份验证和访问控制框架。
应用程序安全性的两个主要方面是身份验证
(认证:Authentication)和授权
(访问控制:Authorization), 这也是 Spring Security 目标的两个主要领域。
身份验证:认证,即确认用户可以访问系统,可理解为用户账号密码正确且有效。
授权:访问控制,即用户在当前系统下所拥有的功能权限。
Spring Boot 关于 Spring Security 官方说明Security, Spring Security 官方文档 -> learn, Spring Boot 集成 Spring Security 官方说明, Spring Security -> Samples and Guides (Start Here)
Spring Security
Spring Security 是通过过滤器来实现所有安全的功能。 Spring Security 提供了 AbstractSecurityWebApplicationInitializer 抽象类,实现了 WebApplicationInitializer 接口, 重写了 onStartup(ServletContext servletContext) 方法, 在方法里调用了 insertSpringSecurityFilterChain(servletContext)方法, 将 springSecurityFilterChain 过滤器注册到 Servlet 容器。
源码分析
- springSecurityFilterChain 过滤器, 在所有其它过滤器之前执行。
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
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
/**
* Creates the Spring Security Filter Chain (创建 springSecurityFilterChain 过滤器)
* @return
* @throws Exception
*/
public Filter springSecurityFilterChain() throws Exception {
//判断是否有自定义的WebSecurityConfig
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
//使用默认配置,在 WebSecurityConfigurerAdapter 的getHttp方法里
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
/**
* 该方法会被提前执行,用于提前设置安全配置
* 1. 获取自定义的 WebSecurityConfig 配置
* 2. 创建 webSecurity 实体
* 3. 将自定义 WebSecurityConfig 配置添加到 webSecurity
*/
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
//获取所有 WebSecurityConfigurer 接口类类型的 Bean(包括自定义的WebSecurityConfig配置)
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
//将自定义配置添加到 webSecurity里
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
} - AbstractSecurityWebApplicationInitializer 抽像类
继承了 WebApplicationInitializer 接口,重写了 onStartup()方法; 该类由 SpringServletContainerInitializer 类通过 Servlet 的 @HandlesTypes注解自动扫描, SpringServletContainerInitializer 实现了 ServletContainerInitializer 接口, Servlet(Tomcat)容器启动时会调用其onStartup()操作。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
64public abstract class AbstractSecurityWebApplicationInitializer
implements WebApplicationInitializer {
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
// onStartup()方法, Servlet容器初始化时执行
public final void onStartup(ServletContext servletContext) throws ServletException {
beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
//添加ContextLoaderListener监听器
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (enableHttpSessionEventPublisher()) {
servletContext.addListener(
"org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(getSessionTrackingModes());
//注册 springSecurityFilterChain 过滤器
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
/**
* Registers the springSecurityFilterChain(注册 springSecurityFilterChain 过滤器)
* @param servletContext the {@link ServletContext}
*/
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
//从Spring 容器中获取过滤器
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
//注册 Filter 到 Servlet容器
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
// 将过滤器添加到 Servlet容器,并提供异步支持
private final void registerFilter(ServletContext servletContext,
boolean insertBeforeOtherFilters, String filterName, Filter filter) {
//添加过滤器到容器
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(isAsyncSecuritySupported());
EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
"/*");
}
}集成使用
- 自定义初始化类,继承 AbstractSecurityWebApplicationInitializer 抽像类, 开启。
1
2
3
4
5
6
7
8
9import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* @Name: WebAppInitializer
* @Desc: 开启 Spring Security 过滤器的支持
**/
public class WebAppInitializer extends AbstractSecurityWebApplicationInitializer {
} - 自定义 WebSecurityConfig 类, 添加
@EnableWebSecurity
注解, 继承 WebSecurityConfigurerAdapter 抽像类,重写里面的方法。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
29import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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;
/**
* @Name: WebSecurityConfig
* @Desc: 自定义 WebSecurity 配置
**/
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity httpSecurity) throws Exception {
super.configure(httpSecurity);
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
public void configure(WebSecurity webSecurity) throws Exception {
super.configure(webSecurity);
}
}
Spring Boot 集成
自动配置
Spring Boot 为 Spring Security 提供了自动配置, 在 org.springframework.boot.autoconfigure.security 包下。
Spring Security 自动配置的核心类有 SecurityProperties(属性类), SecurityAutoConfiguration(Security自动配置类), SecurityFilterAutoConfiguration(过滤器自动配置类)。
SecurityProperties 获取在 application.properties 自定义的安全属性, 自定义属性前缀是 spring.security 。
在该文件中有个静态内部类(
User
), 里面定义了默认的用户名是user
, 默认的密码是UUID.randomUUID().toString()
, 如果集成了 Spring Security 但又没有自定义登录页面和用户认证, 则会输出默认的登录页面, 并使用此默认用户名和密码进行登录认证, 密码会随项目启动输出打印。默认的登录页面是由 DefaultLoginPageGeneratingFilter 过滤器处理,判断是否有自定义登录页面的URL路径, 不存在时则在该过滤器里拼接了 HTML 代码来输出页面。
SecurityAutoConfiguration 导入了 SpringBootWebSecurityConfiguration 类, 对于 SpringBootWebSecurityConfiguration 的使用,在 Spring Boot v1.5.x 版本和 2.0.x版本上存在较大差异, 详情请对比两个版本的源码。
SpringBootWebSecurityConfiguration 注入了 WebSecurityConfigurerAdapter 抽象类;自定义的安全配置继承 WebSecurityConfigurerAdapter 类。
SecurityFilterAutoConfiguration 创建 springSecurityFilterChain 实例,注册到 Spring 容器中。
Spring Security 默认提交用户认证的路径是
/login
, 请求方式默认也只能是POST
; 退出登录路径是/logout
; 提交认证的用户名属性是username
, 密码属性是password
。用户认证是由 UsernamePasswordAuthenticationFilter 过滤器处理。Spring Security 的用户认证是通过14个条件过滤器组成的过滤器链来实现的, 14个过滤器是(WebAsyncManagerIntegrationFilter, SecurityContextPersistenceFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter(退出登录过滤器), UsernamePasswordAuthenticationFilter, DefaultLoginPageGeneratingFilter(登录用户认证过滤器), BasicAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor)
封装用户认证的对象 authentication 的数据格式如下,可通过
SecurityContextHolder.getContext().getAuthentication();
来获取该对像实例。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{
"authenticated": true,
"authorities": [{
"authority": "ADMIN"
}],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": "19CACF37586038F3DE14F808A891D483"
},
"name": "admin",
"principal": {
"accountNonExpired": true,
"accountNonLocked": true,
"address": "中国",
"age": 21,
"authorities": [{
"authority": "ADMIN"
}],
"credentialsNonExpired": true,
"enabled": true,
"id": 3,
"password": "{bcrypt}$2a$10$/.4eK1JTNF9h6jBzPh94ROgdgsj6KBVNAmg3I7pNBx1wWbckq97jG",
"role": "ADMIN",
"state": true,
"username": "admin"
}
}默认提供一个基于 HTTP Basic 认证的安全防护策略,提供了默认的用户名和密码,也可通过以下属性设置:
1
2
3#认证账号密码
admin =
123456 =默认启用了一些必要的 Web 安全策略,比如针对 XSS、CSRF 等常见针对 Web 的攻击,同时,也会将一些常见的静态资源路径排除在安全防护之外。
JSP标签库
Spring Security 还提供了支持 JSP 的标签库,Spring Security -> JSP Tag Libraries
- 导入标签库包
1
2
3
4
5<!--Spring Security 支持JSP的标签库-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency> - 标签库使用
在 JSP 文件顶部添加 taglib 支持:<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
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<div>
<h1>欢迎来到首页:
<%--从authentication中取值--%>
<sec:authentication property="principal.username" />
<%--从model中取值--%>
${username}
</h1>
<a href="/admin">管理页面</a><br>
<a href="/user">USER页面</a><br>
<a href="/logout">退出登录</a>
<sec:authorize url="/admin">
<p>有权向 /admin 路径发送请求才可显示</p>
</sec:authorize>
</div>
<div>
<h1>USER, ADMIN 角色页面</h1>
<sec:authorize access="hasAuthority('ADMIN')">
<p>只有 ADMIN 角色可看</p>
</sec:authorize>
<sec:authorize access="hasAuthority('USER')">
<p>只有 USER 角色可看</p>
</sec:authorize>
</div>
集成示例
- 导入 Spring Security 依赖, 此示例使用 Spring Boot 2.0.2 Release版本
1
2
3
4
5<!--Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> - WebConfig:Web配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class WebConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
// registry.addViewController("/index").setViewName("index");
registry.addViewController("/admin").setViewName("admin");
registry.addViewController("/user").setViewName("user");
registry.addViewController("/error").setViewName("error");
registry.addViewController("/404").setViewName("404");
//根目录默认定位到首页
registry.addRedirectViewController("/","/index");
}
} - WebSecurityConfig:Web安全配置
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103/**
* @Name: WebSecurityConfig
* @Desc: WebSecurity配置类
**/
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private DataSource dataSource;
/*@Autowired
private CustomUserDetailsService customUserService;*/
/**
* 自定义请求授权
*
* @param httpSecurity
* @throws Exception
*/
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable() //关闭CSRF防护
.authorizeRequests() //开启请求权限配置
// .antMatchers("/admin/**").hasRole("ADMIN")
// .antMatchers("/user/**").hasAnyRole("ADMIN","USER")
.antMatchers("/admin/**").hasAuthority("ADMIN") //是否授权
.antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/", "/login").permitAll() //匹配路径,允许任何人访问(不拦截,放行)
.anyRequest().authenticated() //其它请求,要求登录验证后可访问
.and()
.formLogin()
.loginPage("/login") //自定义登录
.defaultSuccessUrl("/index") //登录成功后跳转url
.failureUrl("/error") //登录失败跳转url(无法跳转到/error路径,提示无映射,实际有映射,换其它路径正常)
.permitAll()
.and()
.rememberMe() //开启Cookie存储用户信息
.tokenValiditySeconds(604800) //Cookie有效期
.key("myKey") //Cookie中的私钥
.and()
.logout() //注销用户
.logoutUrl("/logout") //注销用户url
.logoutSuccessUrl("/login") //注解成功后跳转url
.permitAll()
.and()
.httpBasic();
}
public CustomUserDetailsService customUserDetailsService() {
return new CustomUserServiceImpl();
}
/**
* 用户认证方法
*
* @param auth
* @throws Exception
*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* 1. 内置允许通过认证的用户
* Spring Boot 2.0.x集成的是 Spring Security 5.0.x
* 对认证的密码需要加密处理,否则会报错
* */
/*auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("tom").password(new BCryptPasswordEncoder().encode("123456")).roles("USER")
.and()
.withUser("kitty").password(new BCryptPasswordEncoder().encode("112233")).roles("USER");
*/
/**
* 2. JDBC用户
* 默认的密码格式是: {id}encodedPassword, jdbc的用户密码必须以这种格式存储才能正确处理
* 如 123456 使用 bcrypt 加密后存储的密码: {bcrypt}$2a$10$/.4eK1JTNF9h6jBzPh94ROgdgsj6KBVNAmg3I7pNBx1wWbckq97jG
* 否则会报错误: There is no PasswordEncoder mapped for the id “null”
*/
/*auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, true from user where username = ?")
.authoritiesByUsernameQuery("select username, role from user where username = ?");
*/
/**
* 3. 自定义通用用户
*/
auth.userDetailsService(customUserDetailsService());
// auth.userDetailsService(customUserService);
}
/**
* 自定义Web安全策略
*
* @param webSecurity
* @throws Exception
*/
public void configure(WebSecurity webSecurity) throws Exception {
//设置忽略不拦截路径
webSecurity.ignoring().antMatchers("/resources/static/**");
}
} - SysUser:用户实体类
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/**
* @Name: SysUser
* @Desc: 实现UserDetails接口,该用户实体类即为Spring Security 所使用的用户
**/
//public class SysUser{
public class SysUser implements UserDetails { //实现 UserDetails,重写里面方法
private static final long serialVersionUID = -1L;
private Long id;
private String username;
private String password;
private String role;
private String address;
private Integer age;
private Boolean state;
//----get/set方法-----
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
/**
* 重写getAuthorities方法,将用户角色做为权限
* @return
*/
public Collection<? extends GrantedAuthority> getAuthorities() {
//添加授权
List<GrantedAuthority> authorityList = new ArrayList<>();
authorityList.add(new SimpleGrantedAuthority(getRole()));
return authorityList;
}
} - 控制器:IndexController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* @Name: IndexController
* @Desc: 首页控制器
**/
public class IndexController {
public String indexPage(Model model){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
logger.info("authentication:{}", JSON.toJSONString(authentication));
//返回的是个用户实体
SysUser sysUser = (SysUser) authentication.getPrincipal();
// Object principal = authentication.getPrincipal();
// Map<String, String> stringMap = ObjectToMapUtil.obj2Map(principal);
model.addAttribute("username", sysUser.getUsername());
return "index";
}
} - 用户业务层接口:CustomUserDetailsService
1
2
3
4
5
6
7
8
9import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @Name: CustomUserDetailsService
* @Desc: 用户业务层接口
**/
public interface CustomUserDetailsService extends UserDetailsService {
} - 用户业务实现:CustomUserServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* @Name: CustomUserServiceImpl
* @Desc: 用户业务处理
**/
public class CustomUserServiceImpl implements CustomUserDetailsService {
private SysUserRepository sysUserRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Example<SysUser> example = Example.of(new SysUser().setUsername(username));
SysUser sysUser = sysUserRepository.findOne(example).get();
// List<GrantedAuthority> authorityList = new ArrayList<>();
// authorityList.add(new SimpleGrantedAuthority(sysUser.getRole()));
// return new User(sysUser.getUsername(), sysUser.getPassword(),authorityList);
return sysUser;
}
} - 数据访问层:SysUserRepository
1
2
3
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
} - 登录页面:login
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<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<title>登录页面</title>
<head>
</head>
<body onload='document.f.username.focus();'>
<h3>欢迎登录</h3>
<form name='f' action='/login' method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password'/></td>
</tr>
<tr>
<td colspan='2'><input name="submit" type="submit" value="Login"/></td>
</tr>
<%--<input name="_csrf" type="hidden" value="a029a020-6e8f-4bce-9155-23545f36275d"/>--%>
</table>
</form>
</body>
</html> - 首页
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<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<title>首页</title>
<head>
</head>
<body>
<div>
<h1>欢迎来到首页:
<sec:authentication property="principal.username" />
---- ${username}
</h1>
<a href="/admin">管理页面</a><br>
<a href="/user">USER页面</a><br>
<a href="/logout">退出登录</a>
<sec:authorize url="/admin">
<p>有权向 /admin 路径发送请求才可显示</p>
</sec:authorize>
</div>
</body>
</html> - 管理员角色页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<title>首页</title>
<head>
</head>
<body>
<div>
<h1>ADMIN 角色页面</h1>
</div>
</body>
</html> - 普通用户角色页面
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<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<title>首页</title>
<head>
</head>
<body>
<div>
<h1>USER, ADMIN 角色页面</h1>
<sec:authorize access="hasAuthority('ADMIN')">
<p>只有 ADMIN 角色可看</p>
</sec:authorize>
<sec:authorize access="hasAuthority('USER')">
<p>只有 USER 角色可看</p>
</sec:authorize>
</div>
</body>
</html> - 错误页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<title>ERROR</title>
<head>
</head>
<body>
<div>
<h1>不好意思,出错了!!!</h1>
</div>
</body>
</html> - 404页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false"%>
<html>
<title>ERROR</title>
<head>
</head>
<body>
<div>
<h1>不好意思,出错了!!!</h1>
</div>
</body>
</html>
拒绝访问错误页
- Java 配置
1
2
3
4
5
6
7
8
9
10
protected void configure(HttpSecurity httpSecurity) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/*")
.hasAnyRole("ROLE_ADMIN")
...
.and()
.exceptionHandling()
.accessDeniedPage("/my-error-page");
} - 自定义AccessDeniedHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException {
response.sendRedirect("/my-error-page");
}
}
private CustomAccessDeniedHandler accessDeniedHandler;
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/*")
.hasAnyRole("ROLE_ADMIN")
...
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler);
} - 自定义异常响应消息
1
2
3
4
5
6
7
8
9
10
11
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
public ResponseEntity<Object> handleAccessDeniedException(Exception ex, WebRequest request) {
return new ResponseEntity<Object>("Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
}
...
}
Spring Boot 2系列(三十):Spring Security 安全框架详解和集成
http://blog.gxitsky.com/2018/08/31/SpringBoot-30-spring-security/