【问题标题】:Implement Spring Security for Rest Api为 Rest Api 实现 Spring Security
【发布时间】:2018-09-01 10:15:01
【问题描述】:

我将此代码用于 Rest API 身份验证:

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        Optional<String> basicToken = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
                .filter(v -> v.startsWith("Basic"))
                .map(v -> v.split("\\s+")).filter(a -> a.length == 2).map(a -> a[1]);
        if (!basicToken.isPresent()) {
            return sendAuthError(response);
        }

        byte[] bytes = Base64Utils.decodeFromString(basicToken.get());
        String namePassword = new String(bytes, StandardCharsets.UTF_8);
        int i = namePassword.indexOf(':');
        if (i < 0) {
            return sendAuthError(response);
        }
        String name = namePassword.substring(0, i);
        String password = namePassword.substring(i + 1);
//        Optional<String> clientId = authenticationService.authenticate(name, password, request.getRemoteAddr());
        Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
        if (merchant == null) {
            return sendAuthError(response);
        }
        request.setAttribute(CURRENT_CLIENT_ID_ATTRIBUTE, merchant.getId());
        return true;
    }

如何使用 Spring Security 重写代码以获得相同的结果但不同的链接具有身份验证?例如:

localhost:8080/v1/notification - requests should NOT be authenticated.
localhost:8080/v1/request - requests should be authenticated.

【问题讨论】:

  • 您使用的是基本身份验证,因此您可以使用类似于baeldung.com/spring-security-basic-authentication 的内容。无论如何,对于 REST API,我强烈建议您使用 OAuth2 和 JWT
  • @AngeloImmediata 你能用工作代码粘贴官方答案吗?

标签: spring spring-boot spring-security


【解决方案1】:

在这里你可以找到一个工作项目https://github.com/angeloimm/springbasicauth

我知道在 pom.xml 文件中有很多无用的依赖项,但是我从一个已经存在的项目开始,我没有时间去净化它

基本上你必须:

  • 配置弹簧安全
  • 配置spring mvc
  • 根据spring security实现你自己的认证提供者。注意我使用了 inMemoryAuthentication。请根据自己的意愿修改

让我解释一下代码。

Spring MVC 配置

@Configuration
@EnableWebMvc
@ComponentScan(basePackages= {"it.olegna.test.basic"})
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}

在这里,我们不做任何其他事情,通过告诉它在哪里可以找到控制器等来配置 Spring MVC,并使用单个消息转换器; MappingJackson2HttpMessageConverter 以生成 JSON 响应

Spring 安全配置

@Configuration
@EnableWebSecurity
@Import(value= {WebMvcConfig.class})
public class WebSecConfig extends WebSecurityConfigurerAdapter {
     @Autowired private RestAuthEntryPoint authenticationEntryPoint;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
              .inMemoryAuthentication()
              .withUser("test")
              .password(passwordEncoder().encode("testpwd"))
              .authorities("ROLE_USER");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
              .authorizeRequests()
              .antMatchers("/securityNone")
              .permitAll()
              .anyRequest()
              .authenticated()
              .and()
              .httpBasic()
              .authenticationEntryPoint(authenticationEntryPoint);
        }
        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
}

这里我们配置 Spring Security 以便对除以 securityNone 开头的请求之外的所有请求使用 HTTP 基本身份验证。我们使用NoOpPasswordEncoder 来对提供的密码进行编码;这个 PasswrodEncoder 绝对什么都不做……它保持原样。

RestEntryPoint

@Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,  AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

此入口点禁用所有不包含 Authentication 标头的请求

SimpleDto:一个非常简单的 DTO,表示来自控制器的 JSON 答案

public class SimpleDto implements Serializable {

    private static final long serialVersionUID = 1616554176392794288L;
    private String simpleDtoName;

    public SimpleDto() {
        super();
    }
    public SimpleDto(String simpleDtoName) {
        super();
        this.simpleDtoName = simpleDtoName;
    }
    public String getSimpleDtoName() {
        return simpleDtoName;
    }
    public void setSimpleDtoName(String simpleDtoName) {
        this.simpleDtoName = simpleDtoName;
    }

}

TestBasicController:一个非常简单的控制器

@RestController
@RequestMapping(value= {"/rest"})
public class TestBasicController {
    @RequestMapping(value= {"/simple"}, method= {RequestMethod.GET}, produces= {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public ResponseEntity<List<SimpleDto>> getSimpleAnswer()
    {
        List<SimpleDto> payload = new ArrayList<>();
        for(int i= 0; i < 5; i++)
        {
            payload.add(new SimpleDto(UUID.randomUUID().toString()));
        }
        return ResponseEntity.ok().body(payload);
    }
}

因此,如果您使用邮递员或任何其他测试人员尝试此项目,您可以有 2 个场景:

  • 需要身份验证
  • 一切正常

假设您要调用 URL http://localhost:8080/test_basic/rest/simple 而不传递 Authentication 标头。 HTTP 状态码将为401 Unauthorized

这意味着需要Authentication Header

通过将此标头添加到请求 Authorization Basic dGVzdDp0ZXN0cHdk 一切都很好 注意字符串dGVzdDp0ZXN0cHdk是字符串username:password的Base64编码;在我们的例子中是在 inMemoryAuthentication 中定义的test:testpwd 的 Base64 编码

希望对你有用

安杰洛

网络安全用户数据服务

为了配置 Spring 安全性以从 DB 中检索用户详细信息,您必须执行以下操作:

像这样创建一个 org.springframework.security.core.userdetails.UserDetailsS​​ervice 实现:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private BasicService svc;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        BasicUser result = svc.findByUsername(username);
        if( result == null )
        {
            throw new UsernameNotFoundException("No user found with username "+username);
        }
        return result;
    }

}

将它注入到spring安全配置中并像这样使用它:

public class WebSecConfig extends WebSecurityConfigurerAdapter {
    @Autowired private RestAuthEntryPoint authenticationEntryPoint;
    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//      auth
//      .inMemoryAuthentication()
//      .withUser("test")
//      .password(passwordEncoder().encode("testpwd"))
//      .authorities("ROLE_USER");
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .authorizeRequests()
        .antMatchers("/securityNone")
        .permitAll()
        .anyRequest()
        .authenticated()
        .and()
        .httpBasic()
        .authenticationEntryPoint(authenticationEntryPoint);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

我在我提供的 github 链接上推送了代码。在那里你可以找到一个完整的工作示例:

  • 春天 5
  • 弹簧安全 5
  • 休眠
  • h2 数据库

根据自己的情况随意调整

【讨论】:

  • 还有一个问题。如果我错了,请纠正我:我看到在应用程序启动期间设置了用户凭据。如何为每个请求添加数据库查询方法?
  • 我告诉过你我正在使用内存身份验证。您应该通过创建自己的详细服务来更改它
  • 事实证明,对于某些链接,我不需要使用身份验证。请查看更新后的帖子。
  • 那么在这种情况下你必须正确配置spring security。看看我的配置。我对其进行配置是为了使路径为securityNone 的所有请求都不受保护,因此不需要身份验证。所有其他请求都必须经过身份验证。您应该根据自己的场景更改此配置
  • 最后一个请求。请您修改代码以使用服务进行数据库身份验证吗?
【解决方案2】:

您可以使用各种网站上描述的默认 spring-security 配置,例如 baeldung.commkyong.com。您的示例中的技巧似乎是获取Merchant 的调用。根据authenticationServiceMerchant 对象的复杂性,您可以使用以下代码,也可以实现一个外观来获得类似的行为。

@Autowired
public void authenticationManager(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(new AuthenticationProvider() {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Merchants merchant = authenticationService.authenticateMerchant(name, password, request.getRemoteAddr());
            if(merchant == null) {
                throw new AuthenticationException("No Merchant found.");
            }
            return new UsernamePasswordAuthenticationToken(name, password, merchant.getAuthorities());
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        }
    });
}

在请求中设置属性,如有必要,可以通过单独的过滤器完成,该过滤器从SecurityContext 中获取Principal,并将其作为属性放在请求中。

【讨论】:

  • 事实证明,对于某些链接,我不需要使用身份验证。请查看更新后的帖子
猜你喜欢
  • 2014-08-31
  • 2016-07-08
  • 2019-10-14
  • 2017-04-04
  • 2014-02-21
  • 2017-06-25
  • 2018-11-06
  • 2017-01-04
  • 1970-01-01
相关资源
最近更新 更多