使用Spring Boot设置无状态身份验证

时间:2015-11-18 16:03:10

标签: java spring-boot restful-authentication jwt

我正在使用最新版本的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的正确解释

TokenAuthenticationService

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

TokenHandler

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

中传递的内容

UserAuthentication

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

2 个答案:

答案 0 :(得分:2)

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

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

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,那么您可以通过调用tokenAuthenticationService明确查看WebApplicationContextUtils.getRequiredWebApplicationContext.getBean(TokenAuthenticationService.class)

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

答案 1 :(得分:1)

你可以定义一个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);
}
}