【问题标题】:Spring Security Cookie + JWT authenticationSpring Security Cookie + JWT 认证
【发布时间】:2016-11-15 10:01:29
【问题描述】:

我必须说我对整个模型非常困惑,我需要帮助将所有浮动部分粘合在一起。

我没有做 Spring REST,只是简单的 WebMVC 控制器。

我的使命: 我想要一个带有用户名+通过身份验证的表单登录。我想针对 3rd 方服务进行身份验证。成功后我想返回一个 cookie 但不使用默认的 cookie 令牌机制。我希望 cookie 有一个 JWT 令牌。通过利用 cookie 机制,每个请求都将使用 JWT 发送。

因此,为了分解它,我需要处理以下模块:

  1. 在执行用户 + pas logi 时针对 3rd 方服务进行身份验证 n
  2. 验证成功后用我的自定义实现替换 cookie 会话令牌

  3. 在每次请求时从 cookie 中解析 JWT(使用过滤器)

  4. 从 JWT 中提取用户详细信息/数据以供控制器访问

什么令人困惑? (请指正我错的地方)

第三方认证

要针对第 3 方进行身份验证,我需要通过扩展 AuthenticationProvider 来获得自定义提供程序

public class JWTTokenAuthenticationProvider implements AuthenticationProvider { 

      @Override
      public Authentication authenticate( Authentication authentication ) throws AuthenticationException {

          // auth against 3rd party

          // return Authentication
          return new UsernamePasswordAuthenticationToken( name, password, new ArrayList<>() );

      }

      @Override
      public boolean supports(Class<?> authentication) {
          return authentication.equals( UsernamePasswordAuthenticationToken.class );
      }

}

问题:

  • 当用户提交表单用户+通过时,此提供程序是否在成功验证/登录时执行?如果是这样,那与 AbstractAuthenticationProcessingFilter#successfulAuthentication 有什么关系?
  • 我必须返回一个 UsernamePasswordAuthenticationToken 的实例吗?
  • 是否必须支持 UsernamePasswordAuthenticationToken 才能在此处获取用户 + 通行证?

用 JWT 替换 cookie 令牌

不知道如何优雅地做到这一点,我可以想到很多方法,但它们不是 Spring Security 的方法,我不想打破常规。非常感谢您在这里提出任何建议!

使用来自 cookie 的每个请求解析 JWT

据我了解,我需要像这样扩展 AbstractAuthenticationProcessingFilter

public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Override
    public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
            throws AuthenticationException, IOException, ServletException {

        String token = "";

        // get token from a Cookie

        // create an instance to Authentication
        TokenAuthentication authentication = new TokenAuthentication(null, null);

        return getAuthenticationManager().authenticate(tokenAuthentication);

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
                     FilterChain chain) throws IOException, ServletException {
        super.doFilter(req, res, chain);
    }

}

问题:

  • 何时调用 AbstractAuthenticationProcessingFilter#successfulAuthentication?是在用户登录时调用还是在成功验证 JWT 令牌时调用?
  • 此过滤器与我之前发布的自定义提供程序之间是否有任何关系?经理应该会根据令牌实例调用自定义提供者,该令牌实例与提供者通过支持方法支持的内容相匹配?

似乎我拥有了我需要的所有部分,除了 cookie 会话替换,但我无法将它们放入一个连贯的模型中,我需要一个对机制足够了解的人,这样我就可以将所有这些整合到单个模块。

更新 1

好的,我想我已经开始了……https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.java

此过滤器将自己注册到 POST -> "/login",然后创建一个 UsernamePasswordAuthenticationToken 实例并将控制权传递给下一个过滤器。

问题是设置 cookie 会话的位置......

更新 2

dos 的这一部分给出了我所缺少的顶级流程,任何正在经历这个的人都可以在这里查看...http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#tech-intro-authentication

本节关于 AuthenticationProvider...http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-authentication-manager

更新 3 - 工作案例,这是最好的方法吗?

因此,在深入研究了 Spring Security 文档及其源代码后,我得到了初始模型。现在,这样做,我意识到有不止一种方法可以做到这一点。关于为什么选择这种方式 VS Denys 下面提出的任何建议?

下面的工作示例...

【问题讨论】:

    标签: java spring authentication spring-security


    【解决方案1】:

    要让它按照原始帖子中描述的方式工作,这就是需要发生的事情......

    自定义过滤器

    public class CookieAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
            public CookieAuthenticationFilter( RequestMatcher requestMatcher ) {
    
                super( requestMatcher );
                setAuthenticationManager( super.getAuthenticationManager() );
    
            }
    
            @Override
            public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response )
                throws AuthenticationException, IOException, ServletException {
    
                String token = "";
    
                // get token from a Cookie
                Cookie[] cookies = request.getCookies();
    
                if( cookies == null || cookies.length < 1 ) {
                    throw new AuthenticationServiceException( "Invalid Token" );
                }
    
                Cookie sessionCookie = null;
                for( Cookie cookie : cookies ) {
                    if( ( "someSessionId" ).equals( cookie.getName() ) ) {
                    sessionCookie = cookie;
                    break;
                    }
                }
    
                // TODO: move the cookie validation into a private method
                if( sessionCookie == null || StringUtils.isEmpty( sessionCookie.getValue() ) ) {
                    throw new AuthenticationServiceException( "Invalid Token" );
                }
    
                JWTAuthenticationToken jwtAuthentication = new JWTAuthenticationToken( sessionCookie.getValue(), null, null );
    
                return jwtAuthentication;
    
            }
    
    
            @Override
            public void doFilter(ServletRequest req, ServletResponse res,
                     FilterChain chain) throws IOException, ServletException {
                super.doFilter(req, res, chain);
            }
    
    }
    

    身份验证提供者

    将提供者附加到由 UsernamePasswordAuthenticationFilter 生成的 UsernamePasswordAuthenticationToken,后者将自身附加到“/login” POST。对于 POST 到“/login”的 formlogin 将生成 UsernamePasswordAuthenticationToken 并且您的提供者将被触发

    @Component
    public class ApiAuthenticationProvider implements AuthenticationProvider {
    
            @Autowired
            TokenAuthenticationService tokenAuthService;
    
            @Override
            public Authentication authenticate( Authentication authentication ) throws AuthenticationException {
    
                String login = authentication.getName();
                String password = authentication.getCredentials().toString();
    
                // perform API call to auth against a 3rd party
    
                // get User data
                User user = new User();
    
                // create a JWT token
                String jwtToken = "some-token-123"
    
                return new JWTAuthenticationToken( jwtToken, user, new ArrayList<>() );
    
            }
    
            @Override
            public boolean supports( Class<?> authentication ) {
                    return authentication.equals( UsernamePasswordAuthenticationToken.class );
            }
    }
    

    自定义身份验证对象

    对于 JWT,我们希望拥有自己的身份验证令牌对象,以便在堆栈中携带我们想要的数据。

    public class JWTAuthenticationToken extends AbstractAuthenticationToken {
    
            User principal;
            String token;
    
            public JWTAuthenticationToken( String token, User principal, Collection<? extends GrantedAuthority> authorities ) {
                super( authorities );
                this.token = token;
                this.principal = principal;
            }
    
            @Override
            public Object getCredentials() {
                return null;
            }
    
            @Override
            public Object getPrincipal() {
                return principal;
            }
    
            public void setToken( String token ) {
                this.token = token;
            }
    
            public String getToken() {
                return token;
            }
    }
    

    身份验证成功处理程序

    当我们的自定义提供程序通过针对第 3 方对用户进行身份验证并生成 JWT 令牌来完成工作时,就会调用它,这是 Cookie 进入响应的地方。

    @Component
    public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
        @Override
        public void onAuthenticationSuccess(
                    HttpServletRequest request, 
                    HttpServletResponse response, 
                    Authentication authentication) throws IOException, ServletException {
    
            if( !(authentication instanceof JWTAuthenticationToken) ) {
                return;
            }
    
            JWTAuthenticationToken jwtAuthenticaton =    (JWTAuthenticationToken) authentication;
    
            // Add a session cookie
            Cookie sessionCookie = new Cookie( "someSessionId", jwtAuthenticaton.getToken() );
            response.addCookie( sessionCookie );
    
            //clearAuthenticationAttributes(request);
    
            // call the original impl
            super.onAuthenticationSuccess( request, response, authentication );
    }
    

    }

    将这一切联系在一起

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired @Required
        ApiAuthenticationProvider apiAuthProvider;
    
        @Autowired @Required
        AuthenticationSuccessHandler authSuccessHandler;
    
        @Autowired @Required
        SimpleUrlAuthenticationFailureHandler authFailureHandler;
    
        @Override
        protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
        auth.authenticationProvider( apiAuthProvider );
        }
    
        @Override
        protected void configure( HttpSecurity httpSecurity ) throws Exception {
    
                httpSecurity
    
                // don't create session
                .sessionManagement()
                    .sessionCreationPolicy( SessionCreationPolicy.STATELESS )
                    .and()
    
                .authorizeRequests()
                    .antMatchers( "/", "/login", "/register" ).permitAll()
                    .antMatchers( "/js/**", "/css/**", "/img/**" ).permitAll()
                    .anyRequest().authenticated()
                    .and()
    
                // login
                .formLogin()
                    .failureHandler( authFailureHandler )
                    //.failureUrl( "/login" )
                    .loginPage("/login")
                    .successHandler( authSuccessHandler )
                            .and()
    
                // JWT cookie filter
                .addFilterAfter( getCookieAuthenticationFilter(
                        new AndRequestMatcher( new AntPathRequestMatcher( "/account" ) )
                ) , UsernamePasswordAuthenticationFilter.class );
        }
    
    
        @Bean
        SimpleUrlAuthenticationFailureHandler getAuthFailureHandler() {
    
                SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler( "/login" );
                handler.setDefaultFailureUrl( "/login" );
                //handler.setUseForward( true );
    
                return handler;
    
        }
    
        CookieAuthenticationFilter getCookieAuthenticationFilter( RequestMatcher requestMatcher ) {
    
                CookieAuthenticationFilter filter = new CookieAuthenticationFilter( requestMatcher );
                filter.setAuthenticationFailureHandler( authFailureHandler );
                return filter;
        }
    }
    

    【讨论】:

    • 这个例子看起来并不完整。它在“没有 JWTAuthenticationToken 的 AuthenticationProvider”上引发错误。我正在尝试执行与您相同的流程,但找不到任何有用的资源。
    • 你有github链接吗?
    • 希望我能多次对此表示赞同。非常有帮助,谢谢@AssafMoldavsky。
    • CookieAuthenticationFilter.attemptAuthentication 被调用了两次,知道为什么会发生这种情况。我按原样实现了上述解决方案。
    • @AssafMoldavsky - 你在 github 上做这个例子吗?能否提供完整代码?
    【解决方案2】:

    最简单的方法是将 Spring Session 添加到您的项目中并扩展 HttpSessionStrategy,它为会话创建/销毁的事件提供了方便的钩子,并具有从 HttpServletRequest 中提取会话的方法。

    【讨论】:

    • 感谢您的提示 Denys!所以这就是我感到困惑的地方,有很多方法可以实现相同的目标,我现在可以通过三种不同的方式来做到这一点,包括你提出的方法。有人可以解释为什么我会按照我提出的问题与您提出的问题相比吗?
    猜你喜欢
    • 2022-01-15
    • 2012-08-15
    • 2016-08-11
    • 2015-03-17
    • 2013-11-03
    • 1970-01-01
    • 2019-01-07
    • 2020-10-02
    • 2022-08-16
    相关资源
    最近更新 更多