当前位置:   article > 正文

说说如何使用 Spring Security 保护 web 请求_http传principal

http传principal

利用 WebSecurityConfigurerAdapter 类的configure(HttpSecurity http) 方法,可以实现以下功能:

  • 只有满足特定条件的请求,才允许提供服务;
  • 自定义登录页;
  • 退出账户;
  • 预防跨站请求伪造。

1 权限配置

对 HTTP 请求路径进行权限配置。假设必须具有 ROLE_USER 角色的账户才能访问 /notice 与 /sms 路径;而其它路径无限制。具体配置代码如下:

 	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/notice", "/sms")
                .hasRole("USER")
                .antMatchers("/", "/**").permitAll();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意:

  1. 这里顺序很重要,因为先声明的规则比后声明的规则具有更高的优先级。
  2. hasRole() 会自动为入参加上 “ROLE_” 前缀,所以我们这里只传入 USER 即可。
方法名说明
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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

access() 中的表达式含义是:只允许具有 ROLE_USER 权限的账户在星期二访问 /design 或 /orders 地址。

2 自定义登录页

在configure(Http Securityhttp) 方法中,我们还可以自定义登录页。

     http.authorizeRequests()
                .antMatchers("/notice", "/sms")
                .hasRole("USER")
                .antMatchers("/", "/**").permitAll()
                .and()
                .formLogin()
                .loginPage("/login");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里通过调用formLogin() 方法来实现,loginPage() 方法用于配置登录页 URL 地址。利用and() 方法,我们可以把配置串联起来。antMatchers() 方法下的每一段不同类型的配置,都可以通过 and() 进行串联。

通过这样配置之后,只要账户没有通过认证,就会将地址重定向到该登录路径。

因为登录页只是一个视图,所以我们可以很简单地在实现了 WebMvcConfigurer 的 WebConfig 中将其声明为一个视图控制器。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        …
        registry.addViewController("/login");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Spring Security会在指定的登录请求路径下监听登录请求,默认的用户名和密码名称为 username 和 password。 用户名和密码参数名称支持可配置。

        http.authorizeRequests()
                .antMatchers("/notice", "/sms")
                .hasRole("USER")
                .antMatchers("/", "/**").permitAll()
                //登录页
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/authenticate")
                .usernameParameter("user")
                .passwordParameter("pwd");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

loginProcessingUrl() 方法用于指定登录请求路径。usernameParameter() 与passwordParameter() 分别用于自定义用户名和密码参数名称。

默认情况下,用户登录成功之后,将会跳转到根路径("/”),一般我们会将其设定为登录后的主页地址。这个默认的登录成功页也可自定义。

http.authorizeRequests()
  	...
    .formLogin()
    .loginPage("/login")
    .loginProcessingUrl("/authenticate")
    .usernameParameter("user")
    .passwordParameter("pwd")
    //登录成功后跳转页
    .defaultSuccessUrl("/index");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这样配置之后,只要用户登录成功,就会跳转到 “/index”。

defaultSuccessUrl() 方法还提供了一个可选入参(alwaysUse)。如果该参数设置为 True,那么即使用户在登录之前正在访问其他页面,在登录成功后,也会跳转到指定页面。

.defaultSuccessUrl("/index",true);
  • 1

3 退出账户

退出账户也是一个常见功能,即用户点击“登出”按钮,退出应用。

在 HttpSecurity 对象上,连缀配置登出过滤器,该过滤器会拦截所有针对 “/logout” 的请求。

默认情况下,用户会被重定向到登录页,这样他们就可以重新登录。我们也可以定制登出导航页,而这是通过 logoutSuccessUrl() 方法来实现的。

http.authorizeRequests()
  	...
    .logout()
    .logoutSuccessUrl("/");
  • 1
  • 2
  • 3
  • 4

4 预防跨站请求伪造

跨站请求伪造(英语: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}"/>

  • 1
  • 2
  • 3

也可以关闭CSRF 保护,在HttpSecurity对象上调用 csrf() 方法开启 CSRF 配置,然后调用disable() 方法关闭CSRF:


http.authorizeRequests()

 ...

 .csrf()

 .disable();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这一般只适用于开发环境,生产环境还是建议开启 CSRF保护,保障应用安全。

5 获取登录账户

有以下几种常用的方式:

  1. 在 Controller 方法中注入 Principal 对象;
  2. 在 Controller 方法中注入 Authentication 对象;
  3. 在 Controller 方法的入参中使用 @AuthenticationPrincipal 注解;
  4. 使用 SecurityContextHolder。

(1)注入 Principal 对象

在 Controller 方法的入参中直接注入 Principal 对象,然后再通过Principal 对象内的账户名获取 User 对象。

@PostMapping
public String process(@Valid Notice notice, Errors errors, Principal principal) {
    ...

    User user=userRepository.findByUsername(principal.getName());

   	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这种方式虽然可行,但存在安全代码与业务代码杂糅的现象。

(2)注入 Authentication对象

在 Controller 方法的入参中直接注入 Authentication对象,然后直接获取账户对象。

@PostMapping
public String process(@Valid Notice notice, Errors errors, Authentication authentication
) {
    ...
    User user= (User) authentication.getPrincipal();
   	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

getPrincipal() 方法返回的是 Object 对象,我们需要将其转为 User 对象。

(3)使用 @AuthenticationPrincipal 注解

在 Controller 方法的账户对象入参中使用 @AuthenticationPrincipal 注解。

@PostMapping
public String process(@Valid Notice notice, Errors errors, @AuthenticationPrincipal User user
) {
    ...
   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

@AuthenticationPrincipal 写法不需要类型转换,而且将与安全相关的代码限制在账户对象上,从而避免了前两种方式所带来的问题。另外这种用法也是目前最简洁的写法,因此推荐使用。

(4)SecurityContextHolder

可以从SecurityContextHolder中获取一个 Authentication 对象,然后再获取其中的 principal。

Authentication authentication= SecurityContextHolder.getContext().getAuthentication();
User user= (User) authentication.getPrincipal();
  • 1
  • 2

使用 SecurityContextHolder的好处是:它可以任何地方使用!我们一般会在底层代码编程中使用到它。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/481524
推荐阅读
相关标签
  

闽ICP备14008679号