简体   繁体   English

带有SpringSecurity的REST API中的IP地址验证

[英]IP Address verification in REST API with SpringSecurity

I have Spring Boot application with configured SpringSecurity. 我有配置了SpringSecurity的Spring Boot应用程序。 It uses token generated by UUID.randomUUID().toString(), returned by method login in UUIDAuthenticationService class in AuthUser object. 它使用由UUID.randomUUID()。toString()生成的令牌,该令牌由Aut​​hUser对象的UUIDAuthenticationService类中的方法login返回。 Authorized users are kept in LoggedInUsers class. 授权用户保留在LoggedInUsers类中。 When I'm sending request to API token is verified by method findByToken in UUIDAuthenticationService class. 当我向API令牌发送请求时,由UUIDAuthenticationService类中的findByToken方法验证。

Lastly I added timeout for token verification. 最后,我添加了令牌验证超时。 Now I want to add ip address verification. 现在,我想添加IP地址验证。 If user is logged in from address XXXX (which is kept in AuthUser object) he should be authorized with his token only form address XXXX How to do it? 如果用户从地址XXXX登录(该地址保存在AuthUser对象中),则仅使用地址XXXX授予他的令牌授权。

My SecurityConfig.java: 我的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: 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: 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: 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);
}
}

I thought about creating method findByTokenAndIp in UUIDAuthenticationService, but I don't know how to find ip address of user sending request and how to get ip address while logging in login method in UUIDAuthenticationService (I need it while I'm creating AuthUser object). 我曾考虑过在UUIDAuthenticationService中创建方法findByTokenAndIp,但我不知道如何在UUIDAuthenticationService中登录登录方法时查找发送请求的用户的ip地址以及如何获取ip地址(创建AuthUser对象时需要它)。

You had access to HttpServletRequest request in your filter so you can extract the IP from it. 您可以访问过滤器中的HttpServletRequest request ,因此可以从中提取IP。

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

After having the IP, you can deny the request anyway that you want! 获得IP后,您可以随时拒绝请求!

I would briefly do the following steps: 我将简要地执行以下步骤:

save the IP in the UUIDAuthenticationService. 将IP保存在UUIDAuthenticationService中。 You can add HttpServletRequest request as a param, if you're using a controller/requestmapping, because it's auto-injected: 如果使用控制器/请求映射,则可以将HttpServletRequest request添加为参数,因为它是自动注入的:

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

Within the authentication filter, use the IP as the "username" for the UsernamePasswordAuthenticationToken and the token as the "password". 在身份验证筛选器中,将IP用作UsernamePasswordAuthenticationToken的“用户名”,并将令牌用作“ password”。 There is also already the HttpServletRequest request that gives you the IP by getRemoteAddr() . 还已经有HttpServletRequest request ,通过getRemoteAddr()为您提供IP。 It's also possible to create an own instance of AbstractAuthenticationToken or even UsernamePasswordAuthenticationToken , which explictly holds an IP or even the request for the authentication-manager. 也可以创建自己的AbstractAuthenticationToken甚至UsernamePasswordAuthenticationToken实例,该实例明确持有IP或什至对身份验证管理器的请求。

Then, you just need to adapt the changes to your retrieveUser method. 然后,您只需要使更改适应您的retrieveUser方法即可。

I modified controller to get ip address with HttpServletRequest parameter and add parameter ipAddress to login method. 我修改了控制器以使用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);
}

And modified method retrieveUser in TokenAuthenticationProvider 并在TokenAuthenticationProvider中修改了方法restoreUser

@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));
}

And it works. 而且有效。 Thans for help. 感谢您的帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM