【问题标题】:Spring Security using HTTP headers使用 HTTP 标头的 Spring Security
【发布时间】:2017-10-22 00:08:05
【问题描述】:

我正在尝试为我的 Spring Boot 应用程序添加安全性。我当前的应用程序正在使用 REST 控制器,每次收到 GETPOST 请求时,我都会读取 HTTP 标头以检索用户和密码,以便根据我存储的所有用户的属性文件验证它们。我想将其更改为使用 Spring Security,这就是我目前所得到的:

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/index.html").permitAll()
            .antMatchers("/swagger-ui.html").hasRole("ADMIN")
            .anyRequest().authenticated();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("admin").password("password").roles("ADMIN").build());
    }
}

我如何告诉configure 方法是从标头而不是登录表单中检索用户凭据?

【问题讨论】:

  • 您的意思是使用Authentication 标头进行基本身份验证吗?
  • 你的HTTP头是什么意思,头的名字是什么?

标签: security authentication spring-boot spring-security http-headers


【解决方案1】:

在 spring boot 应用程序中,您可以在 application.properties 中添加以下内容

security.user.name=user
security.user.password=password

它会做剩下的事情,比如从标题和验证中获取它 更多信息请访问https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-security.html

【讨论】:

【解决方案2】:

您应该避免使用默认的org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter,因为它会从您的请求参数中获取客户端提供的用户名和密码,而您确实需要从标头中获取它们。

因此,您应该编写自定义 AuthenticationFilter 扩展引用 UsernamePasswordAuthenticationFilter 以更改其行为以满足您的要求:

public class HeaderUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  public HeaderUsernamePasswordAuthenticationFilter() {
    super();
    this.setFilterProcessesUrl("/**");
    this.setPostOnly(false);
  }

  /* (non-Javadoc)
   * @see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#obtainPassword(javax.servlet.http.HttpServletRequest)
   */
  @Override
  protected String obtainPassword(HttpServletRequest request) {
    return request.getHeader(this.getPasswordParameter());
  }

  /* (non-Javadoc)
   * @see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#obtainUsername(javax.servlet.http.HttpServletRequest)
   */
  @Override
  protected String obtainUsername(HttpServletRequest request) {
    return request.getHeader(this.getPasswordParameter());
  }

}

此过滤器示例扩展 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 监听每个请求并从标头而不是 parameters 获取 usernamepassword

那么你应该这样改变配置,将你的过滤器设置在UsernamePasswordAuthenticationFilter位置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.addFilterAt(
                new HeaderUsernamePasswordAuthenticationFilter(), 
                UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/index.html").permitAll()
            .antMatchers("/swagger-ui.html").hasRole("ADMIN")
            .anyRequest().authenticated();

}

【讨论】:

  • 好的,谢谢,但我现在如何在我的属性文件中读取角色?
  • 请编辑您的帖子,包括您的属性文件结构。您必须构建一个UserDetailsService,它会从您的文件中读取条目
【解决方案3】:

内存中的身份验证可以满足您的目的

@Configuration
@EnableWebMvc
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
        .withUser("user1").password("password1").roles("USER")
        .and()
        .withUser("user2").password("password2").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().fullyAuthenticated();
        http.httpBasic();   
    }

}

【讨论】:

    【解决方案4】:

    最少的代码添加是定义一个过滤器并将其添加到安全配置中,smth like

    XHeaderAuthenticationFilter.java

    @Component
    public class XHeaderAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    
        String xAuth = request.getHeader("X-Authorization");
    
        User user = findByToken(xAuth);
    
        if (user == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token invalid");
        } else {
            final UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
    
            filterChain.doFilter(request, response);
        }
    }
    
    //need to implement db user validation...
    private User findByToken(String token) {
        if (!token.equals("1234"))
            return null;
    
        final User user = new User(
                "username",
                "password",
                true,
                true,
                true,
                true,
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
    
        return user;
    }
    }
    

    SecurityConfig.java

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http.sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .exceptionHandling()
                    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
                .and()
                .addFilterBefore(new XHeaderAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }
    

    另一种方法是使用spring的AOP在进入带注释的控制器方法之前定义一些要执行的逻辑的注释

    【讨论】:

    • 有趣的例子。在我的代码中,我在过滤器的开头检查是否存在安全上下文,以避免为每个请求访问数据库。当然我也允许创建会话。
    • @Pino ,是的,那会更有效,请记住,通常在使用标头身份验证时,它与无状态并驾齐驱......所以基本上没有任何会话可以从中获得上下文......但是这段代码是一个最小的,可以大大扩展......
    猜你喜欢
    • 2013-03-06
    • 1970-01-01
    • 2017-07-25
    • 2022-09-23
    • 2016-06-30
    • 2019-11-12
    • 2020-09-07
    • 2016-07-12
    • 1970-01-01
    相关资源
    最近更新 更多