赞
踩
利用 WebSecurityConfigurerAdapter 类的configure(HttpSecurity http) 方法,可以实现以下功能:
对 HTTP 请求路径进行权限配置。假设必须具有 ROLE_USER 角色的账户才能访问 /notice 与 /sms 路径;而其它路径无限制。具体配置代码如下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/notice", "/sms")
.hasRole("USER")
.antMatchers("/", "/**").permitAll();
}
注意:
方法名 | 说明 |
---|---|
access(String) | 如果给定的 SpEL 表达式计算结果为 true ,就允许访问 |
anonymous() | 允许匿名账户访问 |
authenticated() | 允许认证过的账户访问 |
denyAll() | 拒绝所有访问 |
fullyAuthenticated() | 如果账户是经过完整流程认证的,即非使用 “记住我”功能认证的,就允许访问 |
hasAnyAuthority(String … ) | 如果账户具有给定权限列表中的某一个权限,就允许访问 |
hasAuthority(String) | 如果账户具有给定权限,就允许访问 |
hasAnyRole(String … ) | 如果账户具有给定角色列表中的某一个角色,就允许访问 |
hasRole(String) | 如果账户具有给定角色,就允许访问 |
hasIpAddress(String) | 如果请求来自于某个给定的 IP 地址,就允许访问 |
not() | 对前面的判定取反操作 |
permitAll() | 无限制访问 |
上面这张表中的绝大多数方法都只能完成单一功能,如果需要的安全规则复杂,建议使用 access(String) 方法,这个方法的入参是 SpEL 表达式。Spring Security 还扩展了SpEL 表达式,具体说明如下。
安全表达式 | 结果 |
---|---|
authentication | 账户的认证对象 |
denyAll | 拒绝访问,返回 false |
hasAnyRole(roles) | 如果账户具有角色列表中任意角色,返回 true |
hasRole(role) | 如果账户具有指定角色,返回 true |
hasIpAddress( ip) | 如果请求来自于指定 IP ,返回为 true |
Craig Walls 举了这样一个使用安全表达式示例:
http.authorizeRequests()
.antMatchers("/design", "/orders")
.access("hasRole('USER') &&" +
"T(java.util.Calendar).getInstance().get(" +
"T(java.util.Calendar).DAY_OF_WEEK)==" +
"T(java.util.Calendar).TUESDAY");
access() 中的表达式含义是:只允许具有 ROLE_USER 权限的账户在星期二访问 /design 或 /orders 地址。
在configure(Http Securityhttp) 方法中,我们还可以自定义登录页。
http.authorizeRequests()
.antMatchers("/notice", "/sms")
.hasRole("USER")
.antMatchers("/", "/**").permitAll()
.and()
.formLogin()
.loginPage("/login");
这里通过调用formLogin() 方法来实现,loginPage() 方法用于配置登录页 URL 地址。利用and() 方法,我们可以把配置串联起来。antMatchers() 方法下的每一段不同类型的配置,都可以通过 and() 进行串联。
通过这样配置之后,只要账户没有通过认证,就会将地址重定向到该登录路径。
因为登录页只是一个视图,所以我们可以很简单地在实现了 WebMvcConfigurer 的 WebConfig 中将其声明为一个视图控制器。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
…
registry.addViewController("/login");
}
}
Spring Security会在指定的登录请求路径下监听登录请求,默认的用户名和密码名称为 username 和 password。 用户名和密码参数名称支持可配置。
http.authorizeRequests()
.antMatchers("/notice", "/sms")
.hasRole("USER")
.antMatchers("/", "/**").permitAll()
//登录页
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("user")
.passwordParameter("pwd");
loginProcessingUrl() 方法用于指定登录请求路径。usernameParameter() 与passwordParameter() 分别用于自定义用户名和密码参数名称。
默认情况下,用户登录成功之后,将会跳转到根路径("/”),一般我们会将其设定为登录后的主页地址。这个默认的登录成功页也可自定义。
http.authorizeRequests()
...
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("user")
.passwordParameter("pwd")
//登录成功后跳转页
.defaultSuccessUrl("/index");
这样配置之后,只要用户登录成功,就会跳转到 “/index”。
defaultSuccessUrl() 方法还提供了一个可选入参(alwaysUse)。如果该参数设置为 True,那么即使用户在登录之前正在访问其他页面,在登录成功后,也会跳转到指定页面。
.defaultSuccessUrl("/index",true);
退出账户也是一个常见功能,即用户点击“登出”按钮,退出应用。
在 HttpSecurity 对象上,连缀配置登出过滤器,该过滤器会拦截所有针对 “/logout” 的请求。
默认情况下,用户会被重定向到登录页,这样他们就可以重新登录。我们也可以定制登出导航页,而这是通过 logoutSuccessUrl() 方法来实现的。
http.authorizeRequests()
...
.logout()
.logoutSuccessUrl("/");
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
为了防止CSRF 攻击,我们可以在表单渲染的时候生成一个 CSRF token,并将其放入表单隐藏域。提交表单时,会把表单数据与这个token一并发送到服务端。服务端会首先拦截该请求,并与最初生成的 token 进行比对。如果 token 匹配成功,那么请求将会被转发到业务层进行后续处理;如果 token 匹配不成功,说明这个可能是恶意请求,服务端将不再转发。
Spring Security 本身提供了 CSRF 保护,而且默认是启用状态。我们只需要在表单中加入一个名为“_csrf”的隐藏域即可,这个隐藏域用于存储CSRF token。
比如在 Thymeleaf 模板中,可以这样定义“_csrf” 隐藏域:
< input type=" hidden" name="_ csrf" th: value="${_ csrf. token}"/>
也可以关闭CSRF 保护,在HttpSecurity对象上调用 csrf() 方法开启 CSRF 配置,然后调用disable() 方法关闭CSRF:
http.authorizeRequests()
...
.csrf()
.disable();
这一般只适用于开发环境,生产环境还是建议开启 CSRF保护,保障应用安全。
有以下几种常用的方式:
(1)注入 Principal 对象
在 Controller 方法的入参中直接注入 Principal 对象,然后再通过Principal 对象内的账户名获取 User 对象。
@PostMapping
public String process(@Valid Notice notice, Errors errors, Principal principal) {
...
User user=userRepository.findByUsername(principal.getName());
...
}
这种方式虽然可行,但存在安全代码与业务代码杂糅的现象。
(2)注入 Authentication对象
在 Controller 方法的入参中直接注入 Authentication对象,然后直接获取账户对象。
@PostMapping
public String process(@Valid Notice notice, Errors errors, Authentication authentication
) {
...
User user= (User) authentication.getPrincipal();
...
}
getPrincipal() 方法返回的是 Object 对象,我们需要将其转为 User 对象。
(3)使用 @AuthenticationPrincipal 注解
在 Controller 方法的账户对象入参中使用 @AuthenticationPrincipal 注解。
@PostMapping
public String process(@Valid Notice notice, Errors errors, @AuthenticationPrincipal User user
) {
...
}
@AuthenticationPrincipal 写法不需要类型转换,而且将与安全相关的代码限制在账户对象上,从而避免了前两种方式所带来的问题。另外这种用法也是目前最简洁的写法,因此推荐使用。
(4)SecurityContextHolder
可以从SecurityContextHolder中获取一个 Authentication 对象,然后再获取其中的 principal。
Authentication authentication= SecurityContextHolder.getContext().getAuthentication();
User user= (User) authentication.getPrincipal();
使用 SecurityContextHolder的好处是:它可以任何地方使用!我们一般会在底层代码编程中使用到它。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。