Бесконечный l oop при аутентификации пользователя: реализация JWT с Spring Security - PullRequest
1 голос
/ 14 марта 2020

РЕДАКТИРОВАТЬ: теперь я получаю 403 запрещенных, доступ запрещен для всех запросов, кроме регистрации и входа в систему

Я проверяю свои конечные точки для приложения после реализации аутентификации JWT и весны безопасность, основанная на этой статье: https://medium.com/@hantsy / protect-rest-apis-with-spring-security-and-jwt-5fbc90305cc5

Аналогично, мои другие источники вдохновения включают в себя: https://www.youtube.com/watch?v=X80nJ5T7YpE

Регистрация и вход в систему пользователя работоспособны, но когда я выполняю последующие запросы, требующие JWT в заголовке, я получаю бесконечный цикл / рекурсию и ошибку переполнения стека. Однако, когда я специально ввожу несуществующий JWT, он возвращает соответствующее сообщение об ошибке и работает хорошо. Таким образом, проблема заключается в успешной аутентификации действительного JWT. После просмотра в Интернете различных ответов о переполнении стека, таких как: Бесконечный l oop в пользовательском приложении Spring Security Бесконечный l oop в пользовательском приложении Spring Security В Spring Security 3.2 .5, что вызывает бесконечное l oop внутри реализации AuthenticationManager?

Кажется, что все они говорят одно и то же: класс ProviderManager просматривает список поставщиков аутентификации, чтобы найти тот, который может аутентифицировать данную аутентификацию, и он не находит тот, который вызывает эту ошибку, и они предлагают реализовать ваш собственный поставщик аутентификации. Ниже приведен мой код для различных соответствующих классов. Обратите внимание, что я не использую класс userDetailsService; вместо этого у меня есть свой собственный класс UserService, который традиционно имеет функциональность userDetailsService, а также код приложения c. Я также не реализовал свой собственный поставщик аутентификации, следуя приведенным выше примерам кода, поэтому я хотел бы избежать реализации поставщиков аутентификации, поскольку упомянутые примеры кода работают и являются более простыми.

Класс JwtProvider

@Component
public class JwtProvider {
    @Value("${security.jwt.token.secret-key:secret}")
    private String secretKey = "secret";

    @Value("${security.jwt.token.expire-length:3600000}")
    private long validityInMilliseconds = 3600000; // 1h

    @Autowired
    private AppUserService appUserService;

    // encoding the secret used to create the signature for the JWT
    @PostConstruct
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    public String createToken(String userEmail) {
        Claims claims = Jwts.claims().setSubject(userEmail); // subject is the person who is being authenticated
        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);
        return Jwts.builder()//
                .setClaims(claims)//
                .setIssuedAt(now)//
                .setExpiration(validity)//
                .signWith(SignatureAlgorithm.HS256, secretKey)//
                .compact();
    }

    public Authentication getAuthentication(String token) {
        AppUser user = this.appUserService.getAppUserByEmail(getUserEmail(token));
        return new UsernamePasswordAuthenticationToken(user, "");
    }

    // get userEmail in the jwt token that we receive
    public String getUserEmail(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
    }

    // validate a token that we have: not expired, and matches the userEmail
    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            if (claims.getBody().getExpiration().before(new Date())) {
                return false;
            }
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }

}

Класс JwtConfigurer

package ca.mcgill.ecse321.petadoption.controller;

import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

// configures how we filter http requests
// specifies order of applying filters
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private JwtProvider jwtProvider;
    public JwtConfigurer(JwtProvider jwtProvider) {
        this.jwtProvider = jwtProvider;
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtFilter customFilter = new JwtFilter(jwtProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); 
    }
}

Класс JwtFilter

// Job: intercept a request and look at the header to get the JWT
public class JwtFilter extends GenericFilterBean {
    private JwtProvider jwtProvider;

    public JwtFilter(JwtProvider jwtProvider) {
        this.jwtProvider = jwtProvider;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) 
            throws IOException, ServletException {
        String token = resolveToken((HttpServletRequest) req);
            if (token != null && jwtProvider.validateToken(token)) { // if token is not null and it isn't expired and it is valid 
                Authentication auth = jwtProvider.getAuthentication(token); // returning the UsernamePasswordAuthenticationToken
                SecurityContextHolder.getContext().setAuthentication(auth); 
            }
        filterChain.doFilter(req, res);
    }

    public String resolveToken(HttpServletRequest req) {
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
}

Класс SecurityConfigurer

@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    JwtProvider jwtProvider;

    @Autowired
    AppUserService appUserService;

    // this is because we cannot perform @autowired authManager anymore cuz its only compatible w/ older Spring Boot
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .csrf().disable() // this disables cross-site request forgery where an attacker manages to get an authenticated user to perform a malicious request to his liking using diff links and such
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // tell it to use the filterChain that intercepts requests and checks the authorization header for a jwt
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/login/").permitAll()
                .antMatchers("/register").permitAll()
                .antMatchers("/register/").permitAll()
                .anyRequest().authenticated()
                .and()
                .apply(new JwtConfigurer(jwtProvider)); // this specifies to add our custom filter before the usernamepasswordauthenticationfilter
    }

}

Вот ошибка, которую я получаю:

  2020-03-14 12:14:27.183 ERROR 784 --- [nio-8081-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause

java.lang.StackOverflowError: null
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at com.sun.proxy.$Proxy120.authenticate(Unknown Source) ~[na:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501) ~[spring-security-config-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at sun.reflect.GeneratedMethodAccessor107.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at com.sun.proxy.$Proxy120.authenticate(Unknown Source) ~[na:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501) ~[spring-security-config-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at sun.reflect.GeneratedMethodAccessor107.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at com.sun.proxy.$Proxy120.authenticate(Unknown Source) ~[na:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501) ~[spring-security-config-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at sun.reflect.GeneratedMethodAccessor107.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205) ~[spring-aop-5.2.3.RELEASE.jar:5.2.3.RELEASE]
    at com.sun.proxy.$Proxy120.authenticate(Unknown Source) ~[na:na]
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195) ~[spring-security-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501) ~[spring-security-config-5.2.2.RELEASE.jar:5.2.2.RELEASE]
    at sun.reflect.GeneratedMethodAccessor107.invoke(Unknown Source) ~[na:na]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]

Я относительно новичок в Spring и Spring Boot, поэтому любая помощь приветствуется спасибо

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...