【问题标题】:Setup Stateless Authentication using Spring Boot使用 Spring Boot 设置无状态身份验证
【发布时间】:2016-02-20 11:12:45
【问题描述】:

我正在使用最新版本的 Spring Boot,并且正在尝试设置 StatelessAuthenticaion。到目前为止,我一直在阅读的教程非常模糊,我不确定我做错了什么。我正在使用的教程是...

http://technicalrex.com/2015/02/20/stateless-authentication-with-spring-security-and-jwt/

我的设置的问题是,除了 TokenAuthenticationService::addAuthentication 从未被调用因此我的令牌从未设置,因此它在调用 TokenAuthenticationService::getAuthentication 时返回 null 并因此返回 401即使我成功登录(因为永远不会调用 addAuthentication 来设置标头中的令牌)。我试图想办法添加TokenAuthenticationService::addAuthentication,但我觉得这很困难。

在教程中,他将类似于WebSecurityConfig::UserDetailsService.userService 的内容添加到auth.userDetailsService() 中。我遇到的唯一问题是,当我这样做时,它会引发 CastingErrorException。它仅在我使用 UserDetailsService customUserDetailsService 时才有效...

WebSecurityConfig

package app.config;

import app.repo.User.CustomUserDetailsService;
import app.security.RESTAuthenticationEntryPoint;
import app.security.RESTAuthenticationFailureHandler;
import app.security.RESTAuthenticationSuccessHandler;
import app.security.TokenAuthenticationService;
import app.security.filters.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static PasswordEncoder encoder;
    private final TokenAuthenticationService tokenAuthenticationService;

    private final CustomUserDetailsService userService;

    @Autowired
    private UserDetailsService customUserDetailsService;

    @Autowired
    private RESTAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    private RESTAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private RESTAuthenticationSuccessHandler authenticationSuccessHandler;

    public WebSecurityConfig() {
        this.userService = new CustomUserDetailsService();
        tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
    }

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/**").authenticated();
        http.csrf().disable();
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        http.formLogin().defaultSuccessUrl("/").successHandler(authenticationSuccessHandler);
        http.formLogin().failureHandler(authenticationFailureHandler);
        //This is ho
        http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
                UsernamePasswordAuthenticationFilter.class);

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService);
    }

    @Bean
    @Override
    public CustomUserDetailsService userDetailsService() {
        return userService;
    }

    @Bean
    public TokenAuthenticationService tokenAuthenticationService() {
        return tokenAuthenticationService;
    }
}

TokenAuthenticationService 成功调用了getAuthentication 方法,但在我阅读的教程中,没有正确解释如何调用addAuthentication

令牌认证服务

package app.security;

import app.repo.User.CustomUserDetailsService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

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

public class TokenAuthenticationService {


    private static final String AUTH_HEADER_NAME = "X-AUTH-TOKEN";

    private final TokenHandler tokenHandler;
    //This is called in my WebSecurityConfig() constructor 
    public TokenAuthenticationService(String secret, CustomUserDetailsService userService) {
        tokenHandler = new TokenHandler(secret, userService);
    }

    public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {
        final UserDetails user = authentication.getDetails();
        response.addHeader(AUTH_HEADER_NAME, tokenHandler.createTokenForUser(user));
    }

    public Authentication getAuthentication(HttpServletRequest request) {
        final String token = request.getHeader(AUTH_HEADER_NAME);
        if (token != null) {
            final UserDetails user = tokenHandler.parseUserFromToken(token);
            if (user != null) {
                return new UserAuthentication(user);
            }
        }
        return null;
    }
}

令牌处理程序

package app.security;

import app.repo.User.CustomUserDetailsService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;

public final class TokenHandler {

    private final String secret;
    private final CustomUserDetailsService userService;

    public TokenHandler(String secret, CustomUserDetailsService userService) {
        this.secret = secret;
        this.userService = userService;
    }

     public UserDetails parseUserFromToken(String token) {
         String username = Jwts.parser()
        .setSigningKey(secret)
                 .parseClaimsJws(token)
                 .getBody()
                 .getSubject();
         return userService.loadUserByUsername(username);
    }

    public String createTokenForUser(UserDetails user) {
    return Jwts.builder()
            .setSubject(user.getUsername())
            .signWith(SignatureAlgorithm.HS512, secret)
            .compact();
    }
}

在我的 WebServiceConfig 中。我添加以下内容

http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
        UsernamePasswordAuthenticationFilter.class);

它调用以下类作为过滤器。它获得了身份验证,但没有实际添加它的位置。

StatelessAuthenticationFilter

package app.security.filters;

import app.security.TokenAuthenticationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * Created by anthonygordon on 11/17/15.
 */
public class StatelessAuthenticationFilter extends GenericFilterBean {

    private final TokenAuthenticationService authenticationService;

    public StatelessAuthenticationFilter(TokenAuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        Authentication authentication = authenticationService.getAuthentication(httpRequest);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        SecurityContextHolder.getContext().setAuthentication(null);
    }
}

以下类是在TokenAuthenticationService::addAuthentication 中传递的内容

用户认证

package app.security;

import app.repo.User.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class UserAuthentication implements Authentication {

    private final UserDetails user;
    private boolean authenticated = true;

    public UserAuthentication(UserDetails user) {
        this.user = user;
    }

     @Override
    public String getName() {
        return user.getUsername();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getAuthorities();
    }

     @Override
    public Object getCredentials() {
        return user.getPassword();
    }

     @Override
    public UserDetails getDetails() {
        return user;
    }

     @Override
    public Object getPrincipal() {
        return user.getUsername();
    }

     @Override
    public boolean isAuthenticated() {
        return authenticated;
    }

     @Override
    public void setAuthenticated(boolean authenticated) {
        this.authenticated = authenticated;
    }
 }

就是这样……

我的解决方案(但需要帮助)...

我的解决方案是在我的成功处理程序中设置TokenAuthenticationService::addAuthentication 方法...唯一的问题是教程将TokenAuthenticationService 类添加到WebServiceConfig 类中。那是它唯一可以访问的地方。如果有办法在我的 successHandler 中获取它,我也许可以设置令牌。

package app.security;

import app.controllers.Requests.TriviaResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * Created by anthonygordon on 11/12/15.
 */
@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
        TriviaResponse tresponse = new TriviaResponse();
        tresponse.setMessage("You have successfully logged in");
        String json = ow.writeValueAsString(tresponse);
        response.getWriter().write(json);
        clearAuthenticationAttributes(request);
    }
}

【问题讨论】:

    标签: java spring-boot restful-authentication jwt


    【解决方案1】:

    当用户首次提供登录凭据时,您必须自己致电 TokenAuthenticationService.addAuthentication()

    在用户使用其 Google 帐户成功登录后,本教程会在 GoogleAuthorizationResponseServlet 中调用 addAuthentication()。以下是相关代码:

    private String establishUserAndLogin(HttpServletResponse response, String email) {
        // Find user, create if necessary
        User user;
        try {
            user = userService.loadUserByUsername(email);
        } catch (UsernameNotFoundException e) {
            user = new User(email, UUID.randomUUID().toString(), Sets.<GrantedAuthority>newHashSet());
            userService.addUser(user);
        }
    
        // Login that user
        UserAuthentication authentication = new UserAuthentication(user);
        return tokenAuthenticationService.addAuthentication(response, authentication);
    }
    

    如果您已经有一个身份验证成功处理程序,那么我认为您在正确的轨道上,您需要从那里调用TokenAuthenticationService.addAuthentication()。将tokenAuthenticationService bean 注入您的处理程序,然后开始使用它。如果您的成功处理程序最终不是 Spring bean,那么您可以通过调用 WebApplicationContextUtils.getRequiredWebApplicationContext.getBean(TokenAuthenticationService.class) 显式查看 tokenAuthenticationService

    本教程的 GitHub 存储库中还有一个 issue,它将解决用户提供的初始登录与所有后续请求中发生的无状态身份验证之间的混淆。

    【讨论】:

      【解决方案2】:

      您可以像下面这样定义 StatelessLoginFilter

      .addFilterBefore(
                          new StatelessLoginFilter("/api/signin",
                                  tokenAuthenticationService, userDetailsService,
                                  authenticationManager()),
                          UsernamePasswordAuthenticationFilter.class)
      

      然后像这样写类

      class StatelessLoginFilter extends AbstractAuthenticationProcessingFilter {
      
      private final TokenAuthenticationService tokenAuthenticationService;
      private final UserDetailsService userDetailsService;
      
      protected StatelessLoginFilter(String urlMapping,
              TokenAuthenticationService tokenAuthenticationService,
              UserDetailsService userDetailsService,
              AuthenticationManager authManager) {
          super(new AntPathRequestMatcher(urlMapping));
          this.userDetailsService = userDetailsService;
          this.tokenAuthenticationService = tokenAuthenticationService;
          setAuthenticationManager(authManager);
      }
      
      @Override
      protected void successfulAuthentication(HttpServletRequest request,
              HttpServletResponse response, FilterChain chain,
              Authentication authentication) throws IOException, ServletException {
      
          final User authenticatedUser = userDetailsService
                  .loadUserByUsername(authentication.getName());
          final UserAuthentication userAuthentication = new UserAuthentication(
                  authenticatedUser);
      
          tokenAuthenticationService.addAuthentication(response,
                  userAuthentication);
      
          SecurityContextHolder.getContext()
                  .setAuthentication(userAuthentication);
      }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-29
        • 2021-05-27
        • 2011-11-12
        • 2018-02-27
        • 2017-04-12
        • 2022-01-20
        • 1970-01-01
        • 2015-06-20
        相关资源
        最近更新 更多