Spring Security
一、配置Spring Security。
在 pom.xml
文件中引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
有几种配置 Spring Security 的方法,包括冗长的基于 xml 的配置。
基本的 Spring Security 配置类:
一个内存用户存储
基于 JDBC 的用户存储
由 LDAP 支持的用户存储
自定义用户详细信息服务
1、内存用户存储.
package com.example.web.security;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("zhangsan")
.password("{noop}pwd111")
.authorities("ROLE_USER")
.and()
.withUser("wangwu")
.password("{noop}pwd222")
.authorities("ROLE_USER");
}
}
2、基于 JDBC 的用户存储
用户信息通常在关系数据库中维护,基于 JDBC 的用户存储似乎比较合适。
pom引入数据库相关依赖。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource);
}
}
3、 LDAP 支持的用户存储
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchFilter("(uid={0})")
.groupSearchFilter("member={0}");
}
4、自定义用户身份验证
二、web请求保护
1、保护请求
可以配置 HttpSecurity 的属性包括:
在允许服务请求之前,需要满足特定的安全条件
配置自定义登录页面
使用户能够退出应用程序
配置跨站请求伪造保护
package com.example.web.security;
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.context.annotation.Configuration;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);
// 确保 /design 和 /orders 的请求仅对经过身份验证的用户可用;
// 1、对于 /design 和 /orders 的请求应该是授予 ROLE_USER 权限的用户的请求。
// 2、所有的请求都应该被允许给所有的用户
// 这些规则的顺序很重要。首先声明的安全规则优先于较低级别声明的安全规则。
// 如果交换这两个安全规则的顺序,所有请求都将应用 permitAll(),那么关于 /design 和 /orders 请求的规则将不起作用。
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.hasRole("ROLE_USER")
.antMatchers("/", "/**").permitAll();
}
}
所有可用方法:
大多数方法为请求处理提供了基本的安全规则,但是它们是自我限制的,只支持那些方法定义的安全规则。
方法 | |
---|---|
access(String) | 如果 SpEL 表达式的值为 true,则允许访问 |
anonymous() | 默认用户允许访问 |
authenticated() | 认证用户允许访问 |
denyAll() | 无条件拒绝所有访问 |
fullyAuthenticated() | 如果用户是完全授权的(不是记住用户),则允许访问 |
hasAnyAuthority(String…) | 如果用户有任意给定的权限,则允许访问 |
hasAnyRole(String…) | 如果用户有任意给定的角色,则允许访问 |
hasAuthority(String) | 如果用户有给定的权限,则允许访问 |
hasIpAddress(String) | 来自给定 IP 地址的请求允许访问 |
hasRole(String) | 如果用户有给定的角色,则允许访问 |
not() | 拒绝任何其他访问方法 |
permitAll() | 无条件允许访问 |
rememberMe() | 允许认证了的同时标记了记住我的用户访问 |
可以使用 access() 方法提供 SpEL 表达式来声明更丰富的安全规则。
Spring Security 对 SpEL 的扩展:
Security 表达式 | |
---|---|
authentication | 用户认证对象 |
denyAll | 通常值为 false |
hasAnyRole(list of roles) | 如果用户有任何给定的角色,则为 true |
hasRole(role) | 如果用户有给定的角色,则为 true |
hasIpAddress(IP Address) | 如果请求来自给定 IP 地址,则为 true |
isAnonymous() | 如果用户是默认用户,则为 true |
isAuthenticated() | 如果用户是认证了的,则为 true |
isFullyAuthenticated() | 如果用户被完全认证了的(不是使用记住我进行认证),则为 true |
isRememberMe() | 如果用户被标记为记住我后认证了,则为 true |
permitAll() | 通常值为 true |
principal | 用户 pricipal 对象 |
如使用access() 方法以及 hasRole() 和 permitAll 表达式:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll");
}
又如,只想允许具有 ROLE_USER 权限的用户在周二创建新的 Taco;:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER') && " +
"T(java.util.Calendar).getInstance().get("+
"T(java.util.Calendar).DAY_OF_WEEK) == " +
"T(java.util.Calendar).TUESDAY")
.antMatchers(“/”, "/**").access("permitAll");
}
2、创建用户登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and() // and() 方法表示已经完成了授权配置,开始新的配置部分时,将多次使用 and()。
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate") // 指定 Spring Security 应该监听请求 /authenticate 请求以处理登录提交
.usernameParameter("user") // 用户名和密码字段现在应该命名为 user 和 pwd
.passwordParameter("pwd")
.defaultSuccessUrl("/design"); // 成功的登录将直接将到指定的一个默认的页面,如design。
//.defaultSuccessUrl("/design", true); // 可以强制用户在登录后进入设计页面,即使他们在登录之前已经在其他地方导航,方法是将 true 作为第二个参数传递给 defaultSuccessUrl
}
3、登出
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.logout()
.logoutSuccessUrl("/");
}
当用户单击登出按钮时,他们的 session 将被清除,他们将退出应用程序。默认情况下,它们将被重定向到登录页面。如果希望它们被发送到另一个页面,可以调用 logoutSuccessUrl() 来指定一个不同的登出后的登录页面。
4、阻止CSRF请求伪造
为了防止此类攻击,应用程序可以在显示表单时生成 CSRF token,将该 token 放在隐藏字段中,
然后将其存储在服务器上供以后使用。
提交表单时,token 将与其他表单数据一起发送回服务器。
然后服务器拦截请求,并与最初生成的 token 进行比较。如果 token 匹配,则允许继续执行请求。
否则,表单一定是由一个不知道服务器生成的 token的恶意网站呈现的
Spring Security 有内置的 CSRF
保护。默认启用
。
在 Thymeleaf 模板的一个隐藏字段中呈现 CSRF token:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
如果使用 Spring MVC 的 JSP 标签库或带有 Spring 安全方言的 Thymeleaf,
那么甚至不需要显式地包含一个隐藏字段,隐藏字段将自动呈现。
在 Thymeleaf 中,只需确保 <form> 元素的一个属性被前缀为 Thymeleaf 属性。
如:Thymeleaf 渲染隐藏字段所需要的仅仅是 th:action
属性。
<form method="POST" th:action="@{/login}" id="loginForm">
禁用csrf:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.csrf()
.disable();
}
5、获得认证用户的信息
通过 SecurityContext 对象(从 SecurityContextHolder. getcontext() 中返回)或
使用 @Authentication Principal 注入控制器中,可以获得认证用户的信息。
会将与安全性无关的代码与安全代码一起丢弃:
@PostMapping
public String processOrder(@Valid Order order, Errors errors,
SessionStatus sessionStatus,
Principal principal) {
...
User user = userRepository.findByUsername(principal.getName());
order.setUser(user);
...
}
可以接收Authentication 对象,调用 getPrincipal() 来获取主体对象:
@PostMapping
public String processOrder(@Valid Order order, Errors errors,
SessionStatus sessionStatus,
Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
@AuthenticationPrincipal 的优点在于它不需要强制转换:
@PostMapping
public String processOrder(@Valid Order order, Errors errors,
SessionStatus sessionStatus,
@AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
从安全上下文获取一个认证对象:
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();