【问题标题】:IP Address verification in REST API with SpringSecurity使用 SpringSecurity 在 REST API 中验证 IP 地址
【发布时间】:2018-08-28 10:46:55
【问题描述】:

我有配置了 SpringSecurity 的 Spring Boot 应用程序。它使用由 UUID.randomUUID().toString() 生成的令牌,由 AuthUser 对象中 UUIDAuthenticationService 类中的方法 login 返回。授权用户保存在 LoggedInUsers 类中。当我向 API 令牌发送请求时,通过 UUIDAuthenticationService 类中的方法 findByToken 进行验证。

最后我为令牌验证添加了超时。现在我想添加IP地址验证。如果用户从地址 X.X.X.X (保存在 AuthUser 对象中)登录,他应该被授权使用他的令牌仅表单地址 X.X.X.X。怎么做?

我的 SecurityConfig.java:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@FieldDefaults(level = PRIVATE, makeFinal = true)
class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
        new AntPathRequestMatcher("/api/login/login"),
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);

TokenAuthenticationProvider provider;

SecurityConfig(final TokenAuthenticationProvider provider) {
    super();
    this.provider = requireNonNull(provider);
}

@Override
protected void configure(final AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(provider);
}

@Override
public void configure(final WebSecurity web) {
    web.ignoring()
            .requestMatchers(PUBLIC_URLS);
    web.httpFirewall(defaultHttpFirewall());    
}

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
            .sessionManagement()
            .sessionCreationPolicy(STATELESS)
            .and()
            .exceptionHandling()
            // this entry point handles when you request a protected page and you are not yet
            // authenticated
            .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
            .and()
            .authenticationProvider(provider)
            .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
            .antMatchers("/api/application/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE", "ROLE_PORTAL")
            .antMatchers("/api/rezerwacja/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE")
            .anyRequest()
            .authenticated()
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .logout().disable();

}

@Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
    final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(successHandler());
    return filter;
}

@Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
    final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
    successHandler.setRedirectStrategy(new NoRedirectStrategy());
    return successHandler;
}

/**
 * Disable Spring boot automatic filter registration.
 */
@Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
    final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

@Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
    return new HttpStatusEntryPoint(FORBIDDEN);
}

@Bean                                                 
public HttpFirewall defaultHttpFirewall() {
    return new DefaultHttpFirewall();
}
}

AbstractAuthenticationProcessingFilter.java:

@FieldDefaults(level = PRIVATE, makeFinal = true)
public final class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String BEARER = "Bearer";

public TokenAuthenticationFilter(final RequestMatcher requiresAuth) {
    super(requiresAuth);
}

@Override
public Authentication attemptAuthentication(
        final HttpServletRequest request,
        final HttpServletResponse response) {
    final String param = ofNullable(request.getHeader(AUTHORIZATION))
            .orElse(request.getParameter("t"));

    final String token = ofNullable(param)
            .map(value -> removeStart(value, BEARER))
            .map(String::trim)
            .orElseThrow(() -> new BadCredentialsException("Missing Authentication Token"));

    final Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
    return getAuthenticationManager().authenticate(auth);
}

@Override
protected void successfulAuthentication(
        final HttpServletRequest request,
        final HttpServletResponse response,
        final FilterChain chain,
        final Authentication authResult) throws IOException, ServletException {
    super.successfulAuthentication(request, response, chain, authResult);
    chain.doFilter(request, response);
}
}

TokenAuthenticationProvider/java:

@Component
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
public final class TokenAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@NonNull
UserAuthenticationService auth;

@Override
protected void additionalAuthenticationChecks(final UserDetails d, final UsernamePasswordAuthenticationToken auth) {
    // Nothing to do
}

@Override
protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
    final Object token = authentication.getCredentials();
    return Optional
            .ofNullable(token)
            .map(String::valueOf)
            .flatMap(auth::findByToken)
            .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
}

UUIDAuthenticationService.java:

@Service
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
public final class UUIDAuthenticationService implements UserAuthenticationService {

private static final Logger log = LoggerFactory.getLogger(UUIDAuthenticationService.class);

@NonNull
UserCrudService users;

@Autowired
LoginManager loginMgr;

@Override
public AuthUser login(final String username, final String password) throws Exception { //throws Exception {

    AuthUser user = loginMgr.loginUser(username, password);
    if (user != null) {
        users.delete(user);
        users.save(user);
        log.info("Zalogowano użytkownika {}, przydzielono token: {}", user.getUsername(), user.getUuid());
    }

    return Optional
            .ofNullable(user)
            .orElseThrow(() -> new RuntimeException("Błędny login lub hasło"));
}

@Override
public Optional<AuthUser> findByToken(final String token) {

    AuthUser user = users.find(token).orElse(null); // get();
    if (user != null) {
        Date now = Date.from(OffsetDateTime.now(ZoneOffset.UTC).toInstant());
        int ileSekund = Math.round((now.getTime() - user.getLastAccess().getTime()) / 1000);        // timeout dla tokena
        if (ileSekund > finals.tokenTimeout) {
            log.info("Token {} dla użytkownika {} przekroczył timeout", user.getUuid(), user.getUsername());
            users.delete(user);
            user = null;
        }
        else {
            user.ping();
        }
    }
    return Optional.ofNullable(user); //users.find(token);
}

@Override
public void logout(final AuthUser user) {

    users.delete(user);
}
}

我想过在 UUIDAuthenticationService 中创建方法 findByTokenAndIp,但我不知道如何查找用户发送请求的 IP 地址以及如何在登录 UUIDAuthenticationService 中的登录方法时获取 IP 地址(我在创建 AuthUser 时需要它对象)。

【问题讨论】:

    标签: java spring spring-boot spring-security


    【解决方案1】:

    您可以在过滤器中访问HttpServletRequest request,以便从中提取 IP。

    https://www.mkyong.com/java/how-to-get-client-ip-address-in-java/

    获得IP后,你可以任意拒绝请求!

    【讨论】:

      【解决方案2】:

      我会简单地执行以下步骤:

      将 IP 保存在 UUIDAuthenticationService 中。如果您使用的是控制器/请求映射,您可以添加 HttpServletRequest request 作为参数,因为它是自动注入的:

      @RequestMapping("/login")
      public void lgin(@RequestBody Credentials cred, HttpServletRequest request){
          String ip = request.getRemoteAddr();
          //...
      }
      

      在身份验证过滤器中,使用 IP 作为UsernamePasswordAuthenticationToken 的“用户名”,使用令牌作为“密码”。已经有HttpServletRequest request 为您提供getRemoteAddr() 的IP。 也可以创建自己的 AbstractAuthenticationToken 甚至 UsernamePasswordAuthenticationToken 实例,它明确持有 IP 甚至是对身份验证管理器的请求。

      然后,您只需将更改调整到您的 retrieveUser 方法即可。

      【讨论】:

        【解决方案3】:

        我修改控制器以获取带有HttpServletRequest参数的IP地址,并将参数ipAddress添加到登录方法。

        @PostMapping("/login")
        public AuthUser login(InputStream inputStream, HttpServletRequest request) throws Exception {
        
            final String ipAddress = request.getRemoteAddr();
            if (ipAddress == null || ipAddress.equals("")) {
                throw new Exception("Nie udało się ustalić adresu IP klienta");
            }
        
            Login login = loginMgr.prepareLogin(inputStream);
            return authentication
                    .login(login.getUsername(), login.getPasword(), ipAddress);
        }
        

        并修改了TokenAuthenticationProvider中的retrieveUser方法

        @Override
        protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
        
            System.out.println("Verification: "+authentication.getPrincipal()+" => "+authentication.getCredentials());
        
            final Object token = authentication.getCredentials();
            final String ipAddress= Optional
                    .ofNullable(authentication.getPrincipal())
                    .map(String::valueOf)
                    .orElse("");
        
            return Optional
                    .ofNullable(token)
                    .map(String::valueOf)
                    .flatMap(auth::findByToken)
                    .filter(user -> user.ipAddress.equals(ipAddress))   // weryfikacja adresu ip
                    .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
        }
        

        而且它有效。谢谢帮忙。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-07-08
          • 1970-01-01
          • 2014-05-13
          • 2012-02-29
          • 1970-01-01
          • 2016-12-22
          • 2011-06-12
          相关资源
          最近更新 更多