【发布时间】:2018-11-09 13:51:36
【问题描述】:
我正在尝试在 Spring Boot Rest 服务项目中启用 Spring Security,但遇到了一些问题。
我用这段代码配置了它
@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
并实现了一个自定义身份验证提供程序以登录 LDAP(它具有非标准配置,因此我无法使默认 ldap 提供程序工作)
@Component
public class LdapAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String email = authentication.getName();
String password = authentication.getCredentials().toString();
LdapConnection ldap = new LdapConnection();
String uid = ldap.getUserUID(email);
if(uid == null || uid == ""){
throw new BadCredentialsException("User " + email + " not found");
}
if(ldap.login(uid, password)){
return new UsernamePasswordAuthenticationToken(uid, null, new ArrayList<>());
}else{
throw new BadCredentialsException("Bad credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
return true;
//To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true
}
}
这段代码运行良好,因为使用基本授权标头调用我的服务,它能够正确登录并返回调用的服务。当我尝试插入不同的授权/身份验证时,问题就开始了。我不想使用基本身份验证,而是想从我的反应前端中的表单传递凭据,所以我想在 POST 调用中将它们作为 json 主体传递。 (想法是生成一个 jwt 令牌并将其用于以下通信)。
所以我把configure方法改成这样:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
并定义了一个自定义身份验证过滤器:
public class JWTAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
@Autowired
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
String requestBody;
try{
requestBody = IOUtils.toString(req.getReader());
JsonParser jsonParser = JsonParserFactory.getJsonParser();
Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()));
}catch(IOException e){
throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException{
JwtTokenProvider tokenProvider = new JwtTokenProvider();
String token = tokenProvider.generateToken(auth.getPrincipal().toString());
Cookie cookie = new Cookie("jwt",token);
cookie.setHttpOnly(true);
cookie.setSecure(true);
res.addCookie(cookie);
}
}
问题是,无论我在做什么,运行时似乎根本没有进入这个过滤器。我错过了什么?我想这是一件又大又愚蠢的事情,但我想不通......
更新:问题似乎是 UsernamePassWordAuthenticationFilter 只能通过表单调用。然后,我将代码更改为扩展 AbstractAuthenticationProcessingFilter。
修改后的过滤器:
public class JWTAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super("/api/secureLogin");
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
String requestBody;
try{
requestBody = IOUtils.toString(req.getReader());
JsonParser jsonParser = JsonParserFactory.getJsonParser();
Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>());
return authenticationManager.authenticate(token);
}catch(IOException e){
throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
}
}
}
以及修改后的配置方法:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/secureLogin").permitAll()
.antMatchers(HttpMethod.GET, "/api").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
【问题讨论】:
-
当您希望此过滤器使用时,请确认您正在点击“/login”端点。
-
我在 /api/login 上有一个自定义控制器(基本上是写一个字符串)并且具有相同的行为。我也试过 /login (我没有定义任何东西)但它没有改变:过滤器没有被击中。
-
好的,所以我发现只有在我使用默认表单登录时才会调用它(在我的配置方法中添加
.formLogin())。然后我可以定义一个自定义表单,但作为一个 web 服务应用程序(用户界面在其他地方管理)我不想在这里生成一个登录表单,因为我想使用我已经拥有的休息调用......我应该扩展一个不同的类然后 UsernamePasswordAuthenticationFilter?在那种情况下,是否有一个类可以用于我的场景,或者我应该扩展 AbstractAuthenticationProcessingFilter 类吗? -
所以,这里有几件事。第一个是,默认情况下,UsernamePasswordAuthenticationFilter#attemptAuthentication 仅在 /login 或任何配置为 filterProcessesUrl 的情况下调用——过滤器可能会拒绝 requiresAuthentication 方法中的请求。其次,是的,如果这是一个休息电话,那么扩展 UsernamePasswordAuthenticationFilter 有点奇怪。如果您看一下successAuthentication,您会看到默认的successHandler 想要在完成后重定向调用。我可能只是扩展 OncePerRequestFilter。
-
谢谢,我已经使用 AbstractAuthenticationProcessingFilter 解决了(请参阅帖子上的更新),但我会看看你提到的 OncePerRequestFilter 类。
标签: spring spring-boot spring-security