Гибридная аутентификация - на основе сеанса Spring MVC + на основе токена JWT - PullRequest
1 голос
/ 07 октября 2019

У меня есть ситуация, я использую Spring MVC (jsp, контроллеры, сервис, dao) и сеансовую аутентификацию. Но теперь несколько URL-адресов, которые я использую в качестве веб-службы RESTful для целей интеграции.

Только для этих запросов мне нужно использовать аутентификацию на основе токена (например, JWT).

Итак, есть ли возможность, что я могу использовать оба типа аутентификации в одном проекте.

1 Ответ

2 голосов
/ 07 октября 2019

есть ли возможность, что я могу использовать оба типа аутентификации в одном проекте.

Да, вы можете. Наличие двух фильтров обработки аутентификации.

Фильтр - 1 : для API покоя (JwtAuthTokenFilter), который должен быть без состояний и идентифицироваться токеном авторизации, отправляемым в запросе каждый раз.
Фильтр - 2 : вам нужен другой фильтр (UsernamePasswordAuthenticationFilter). По умолчанию spring-security обеспечивает это, если вы настраиваете его с помощью http.formLogin(). Здесь каждый запрос идентифицируется связанным сеансом (JSESSIONID cookie). Если запрос не содержит допустимого сеанса, он будет перенаправлен на точку входа аутентификации (скажем, login-page).

Рекомендуемый шаблон URL
api-url-pattern    = "/api/**"
webApp-url-pattern = "/**"
Как это работает
  • URL с /api/** будут передаваться через JwtAuthTokenFilter, где он будет читать токен и, если он имеет действительный токен, устанавливает объект аутентификации ицепочка продолжается. Если у него нет действительного запроса, цепочка будет разорвана, и ответ будет отправлен со статусом 401 (неавторизовано).

  • URL, отличные от /api/**, будут обработаны UsernamePasswordAuthenticationFilter [который по умолчанию в весенней безопасности настроен конфигурацией .formLogin(). Он проверит допустимый сеанс, если он не содержит действительный сеанс, он перенаправит на настроенный logoutSuccessUrl.

Примечание: Ваше Webapp не может получить доступ к API через существующий сеанс. Какой вариант у вас есть, это использовать токен Jwt для доступа к API из веб-приложения.

Как настроить

Чтобы получить два разных фильтра обработки аутентификации, вам следуетнастроить несколько настроек безопасности http с другим порядком
Конфигурацию защиты нескольких http можно настроить, объявив статические классы в своем классе конфигурации безопасности, как указано ниже.
(Несмотря на то, что OP запросил концепцию, разумно представить код мудрым. может помочь вам для справки)

Конфигурация безопасности Spring
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = "com.gmail.nlpraveennl")
public class SpringSecurityConfig
{
    @Bean
    public PasswordEncoder passwordEncoder() 
    {
        return new BCryptPasswordEncoder();
    }

    @Configuration
    @Order(1)
    public static class RestApiSecurityConfig extends WebSecurityConfigurerAdapter
    {
        @Autowired
        private JwtAuthenticationTokenFilter jwtauthFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http
                .csrf().disable()
                .antMatcher("/api/**")
                .authorizeRequests()
                .antMatchers("/api/authenticate").permitAll()
                .antMatchers("/api/**").hasAnyRole("APIUSER")
            .and()
                .addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);

            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }

    @Configuration
    @Order(2)
    public static class LoginFormSecurityConfig extends WebSecurityConfigurerAdapter
    {
        @Autowired
        private PasswordEncoder passwordEncoder;

        @Autowired
        public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
        {
            auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin@123#")).roles("ADMIN");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http
                .csrf().disable()
                .antMatcher("/**").authorizeRequests()
                .antMatchers("/resources/**").permitAll()
                .antMatchers("/**").hasRole("ADMIN")
            .and().formLogin();

            http.sessionManagement().maximumSessions(1).expiredUrl("/customlogin?expired=true");
        }
    }
}
Фильтр токенов аутентификации JWT
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
    {
        final String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) 
        {
            String authToken = header.substring(7);
            System.out.println(authToken);

            try
            {
                String username = jwtTokenUtil.getUsernameFromToken(authToken);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
                {
                    if (jwtTokenUtil.validateToken(authToken, username))
                    {
                        List<GrantedAuthority> authList = new ArrayList<>();
                        authList.add(new SimpleGrantedAuthority("ROLE_APIUSER"));

                        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, authList);
                        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                    }
                }
            }
            catch (Exception e)
            {
                System.out.println("Unable to get JWT Token, possibly expired");
            }
        }

        chain.doFilter(request, response);
    }
}
Класс утилиты токена JWT
@Component
public class JwtTokenUtil implements Serializable
{
    private static final long   serialVersionUID    = 8544329907338151549L;
    public static final long    JWT_TOKEN_VALIDITY  = 5 * 60 * 60;
    private String              secret              = "my-secret";

    public String getUsernameFromToken(String token)
    {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token)
    {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver)
    {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token)
    {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token)
    {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public String generateToken(String username)
    {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, username);
    }

    private String doGenerateToken(Map<String, Object> claims, String subject)
    {
        return "Bearer "+Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    public Boolean validateToken(String token, String usernameFromToken)
    {
        final String username = getUsernameFromToken(token);
        return (username.equals(usernameFromToken) && !isTokenExpired(token));
    }
}
Конфигурация сервлета диспетчера
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.gmail.nlpraveennl") //Do not skip componentscan
public class ServletConfiguration implements WebMvcConfigurer
{
     @Bean
     public ViewResolver configureViewResolver() 
     {
         InternalResourceViewResolver viewResolve = new InternalResourceViewResolver();
         viewResolve.setPrefix("/WEB-INF/jsp/");
         viewResolve.setSuffix(".jsp");

         return viewResolve;
     }

    @Bean
    public ResourceBundleMessageSource messageSource()
    {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setUseCodeAsDefaultMessage(true);
        return messageSource;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)
    {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...