Spring Security MultiHttpSecurity配置,这样我就可以执行两种身份验证JWT令牌和会话Cookie

问题描述

我有你的要求

  1. 您需要在请求标头(针对每个请求)中公开应通过JWT令牌访问的API。

  2. Web应用程序也应通过基于表单的身份验证机制来保护,该机制应基于http会话进行工作。

您可以通过两个身份验证过滤器来实现。

:用于Rest API(JwtAuthTokenFilter),该API应该是无状态的,并由每次请求中发送的Authorization令牌标识。 :您需要另一个过滤器(UsernamePasswordAuthenticationFilter)认情况下,如果通过进行配置,spring- security将提供此过滤器http.formLogin()在这里,每个请求都由JSESSIONID关联的session(cookie)标识。如果请求中不包含有效的会话,则它将被重定向到身份验证入口点(例如:login- page)。

api-url-pattern    = "/api/**" [strictly for @order(1)]
webApp-url-pattern = "/**" [ wild card "/**" always used for higer order otherwise next order configuration becomes dead configuration]

方法

  • 定义主配置类 @EnableWebSecurity

  • 创建两个内部静态类,它们应WebSecurityConfigurerAdapter使用@Configuration和@Order进行扩展和注释。在此,REST API配置的顺序应为1,Web应用程序配置的顺序应大于1

  • 请参阅 ,其中 解释了必要的代码。如果需要,请随时从github存储库中请求可下载的链接

在这里,两个过滤器将并排(平行)工作。我的意思是从Web应用程序开始,即使用户通过会话进行了身份验证,但如果没有JWT令牌,他也无法访问API。

OP的要求,即他不想定义任何角色,但允许经过身份验证的用户进行API访问。根据他的要求修改了以下配置。

http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatcher("/web/umgmt/**").authenticated() // use this

解决方法

我已经为我的应用程序安装了Spring Security
Cookie机制,现在仅针对API,我需要添加基于JWT令牌的身份验证机制。我正在使用带有两个嵌套类的Spring
Security的MultiHttpSecurityConfiguration。

会话和JWT令牌机制是否应该一起包含在一个应用程序中是一个完全不同的问题,我需要实现两件事。

  1. Spring Security的基于cookie的基于会话的身份验证将像以前一样工作。

  2. 需要为API添加身份验证标头

    package com.leadwinner.sms.config;

    import java.util.Collections;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.ProviderManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

    import com.leadwinner.sms.CustomAuthenticationSuccessHandler;
    import com.leadwinner.sms.CustomLogoutSuccessHandler;
    import com.leadwinner.sms.config.jwt.JwtAuthenticationProvider;
    import com.leadwinner.sms.config.jwt.JwtAuthenticationTokenFilter;
    import com.leadwinner.sms.config.jwt.JwtSuccessHandler;

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
    @ComponentScan(basePackages = "com.leadwinner.sms")
    public class MultiHttpSecurityConfig {

        @Autowired
        @Qualifier("userServiceImpl")
        private UserDetailsService userServiceImpl;

        @Autowired
        private JwtAuthenticationProvider authenticationProvider;

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userServiceImpl).passwordEncoder(passwordEncoder());
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            return  new BCryptPasswordEncoder();
        }

        @Bean
        public AuthenticationManager authenticationManager() {
            return new ProviderManager(Collections.singletonList(authenticationProvider));
        }

        @Configuration
        @Order(1)
        public static class JwtSecurityConfig extends WebSecurityConfigurerAdapter {

             @Autowired
             private JwtAuthenticationTokenFilter jwtauthFilter;

            @Override
            public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                .antMatcher("/web/umgmt/**").authorizeRequests()
                .antMatchers("/web/umgmt/**").authenticated()
                .and()
                .addFilterBefore(jwtauthFilter,UsernamePasswordAuthenticationFilter.class);
             http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            }
        }

        @Configuration
        @Order(2)
        public static class SecurityConfig extends WebSecurityConfigurerAdapter {
            private  final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

            @Bean
            public CustomAuthenticationEntryPoint getBasicAuthEntryPoint() {
                return new CustomAuthenticationEntryPoint();
            }

            @Override
            public void configure(HttpSecurity http) throws Exception {

                logger.info("http configure");
                http
                .antMatcher("/**").authorizeRequests()          
                .antMatchers("/login/authenticate").permitAll()
                        .antMatchers("/resources/js/**").permitAll()
                        .antMatchers("/resources/css/**").permitAll()
                        .antMatchers("/resources/images/**").permitAll()
                        .antMatchers("/web/initial/setup/**").permitAll()
                        .antMatchers("/dsinput/**").permitAll().antMatchers("/dsoutput/**").permitAll()                 

                        .and()
                    .formLogin()
                        .loginPage("/login").usernameParameter("employeeId").passwordParameter("password")
                        .successForwardUrl("/dashboard")
                        .defaultSuccessUrl("/dashboard",true)
                        .successHandler(customAuthenticationSuccessHandler())
                        .failureForwardUrl("/logout")
                        .loginProcessingUrl("/j_spring_security_check")
                        .and().logout()
                        .logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
                        .logoutSuccessHandler(customLogoutSuccessHandler())
                        .permitAll()
                        .invalidateHttpSession(true)
                        .deleteCookies("JSESSIONID")
                        .and().sessionManagement()
                        .sessionFixation().none()
                        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                        .invalidSessionUrl("/logout")
                        .and().exceptionHandling().accessDeniedPage("/logout").and().csrf().disable();
                http.authorizeRequests().anyRequest().authenticated();


            }

            @Bean
            public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
                return new CustomAuthenticationSuccessHandler();
            }

            @Bean
            public LogoutSuccessHandler customLogoutSuccessHandler() {
                return new CustomLogoutSuccessHandler();
            }
        }
    }

JwtAuthenticationTokenFilter.java

    package com.leadwinner.sms.config.jwt;

    import java.io.IOException;

    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.web.filter.OncePerRequestFilter;

    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)) {
                            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                                    username,null,null);
                            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);
        }
    }

JwtTokenUtil.java

    package com.leadwinner.sms.config.jwt;

    import java.io.Serializable;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;

    import org.springframework.stereotype.Component;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;

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

看来JwtSecurityConfig筛选器现在并未应用于我提到的路径。任何帮助将不胜感激。

我已经读过这个问题。我也一样。

带有Spring Boot的SpringSecurity:将基本身份验证与JWT令牌身份验证混合

编辑:添加了JwtAuthenticationTokenFilter,JwtTokenUtil