当前位置: 首页 > 知识库问答 >
问题:

Spring Security MultiHttpSecurity配置,以便我可以执行两种类型的身份验证。JWT令牌和会话Cookie

呼延骏俊
2023-03-14

我已经为我的应用程序准备了Spring Security Cookie机制,现在只为API准备了,我需要添加基于JWT令牌的身份验证机制。我使用Spring Security的MultiHttpSecurityConfiguration和两个嵌套类。

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

    null
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筛选器没有应用于我提到的路径。任何帮助都将不胜感激。

共有1个答案

梁烨
2023-03-14

我得到你的要求了。

  1. 您需要公开应通过请求头中的JWT令牌访问的API(对于每个请求)。
  2. 以及web应用程序应该通过基于表单的身份验证机制来保护,该机制应该基于http会话。

您可以通过两个身份验证筛选器来实现这一点。

筛选器-1:用于Rest API(JwtAuthTokenFilter),它应该是无状态的,并由每次在请求中发送的授权令牌标识。
筛选器-2:您需要另一个筛选器(UsernamePasswordAuthenticationFilter),如果您通过http.formlogin()配置它,则默认情况下spring-security会提供这个功能。在这里,每个请求由关联的会话(jsessionidcookie)标识。如果请求不包含有效的会话,那么它将被重定向到authentication-entry-point(例如: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存储库中获取可下载的链接。

    编辑
    查看OP要求,其中他不想定义任何角色,但允许通过身份验证的用户访问API。为他的要求修改了以下配置。

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

  •  类似资料:
    • 我们正在Spring Boot2应用程序中使用基于令牌的身份验证(带有Spring Security)。现在我介绍Spring引导执行器到它。我想配置endpoint,使其在没有任何权限的情况下可见,但仅在经过授权后才显示健康检查详细信息。 我找到了属性,该属性应该很有帮助,但现在我正在与Spring Security配置作斗争,以便让每个人都能看到: 在下,使用令牌授权的用户应看到: 你也面临过

    • 问题内容: 我正在PHP Lumen中构建一个应用程序,该应用程序在登录时会返回令牌。我不知道如何继续进行。 我应该如何使用这些令牌维护会话? 具体来说,如果我使用reactjs或原始HTML / CSS / jQuery,如何将令牌存储在客户端,并在我为Web应用程序的安全部分提出的每个请求中发送令牌? 问题答案: 我通常要做的是将令牌保留在本地存储中,这样即使用户离开站点,我也可以保留令牌。

    • 我正在PHP Lumen中构建一个应用程序,它在登录时返回令牌。我不知道如何继续。 我应该如何使用这些令牌维护会话? 具体来说,如果我使用reactjs或vanilla HTML/CSS/jQuery,我如何在客户端存储令牌,并在我为web应用程序的安全部分发出的每个请求中发送它们?

    • 我在做一个全堆栈的web应用程序。我的前端由angular-cli组成,后端由node+Express构建。

    • 一切工作都很正常,只是随机/偶尔地,尽管用户登录了,会话超时发生,系统注销了用户,尽管令牌有7天的有效期。 所以我决定尝试使系统没有任何会话而无状态。为此,我使用了以下命令: 根据禁用主题状态会话存储 然而,现在我根本无法登录。我得到了 我还没有找到任何完整的会话少四郎的例子。对我的代码有什么建议让它工作吗??我一定是错过了什么,但我不知道是什么。 为什么禁用会话后MyRealm无法从Userna

    • 在身份验证等情况下,与会话相比,使用JWTs有什么优势? 它是作为独立方法使用还是在会话中使用?