java - Spring Security - 记住我自定义服务 onLoginSuccess 未使用 oauth2 调用

标签 java spring-security oauth-2.0

我正在尝试使用 jHipster 生成的 webapp 实现记住我的功能。我正在更新一个旧的项目登录页面,其中会有一个记住我的复选框。

我遇到的问题是我正在实现的 RememberMeServices 不起作用。准确的说,没有调用onLoginSuccess方法,也没有进行持久化token的初始化。

然而,CustomPersistentRememberMeServices 的注销方法被调用。

我有一个自定义记住我的服务,定义如下:

@Service("rememberMeServices")
public class CustomPersistentRememberMeServices extends
 AbstractRememberMeServices {

private final Logger log = LoggerFactory.getLogger(CustomPersistentRememberMeServices.class);

// Token is valid for one month
private static final int TOKEN_VALIDITY_DAYS = 31;

private static final int TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * TOKEN_VALIDITY_DAYS;

private static final int DEFAULT_SERIES_LENGTH = 16;

private static final int DEFAULT_TOKEN_LENGTH = 16;

private SecureRandom random;

@Inject
private PersistentTokenRepository persistentTokenRepository;

@Inject
private UserRepository userRepository;

@Inject
public CustomPersistentRememberMeServices(Environment env, org.springframework.security.core.userdetails
    .UserDetailsService userDetailsService) {

    super(env.getProperty("jhipster.security.rememberme.key"), userDetailsService);
    random = new SecureRandom();
}

@Override
@Transactional
protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
    HttpServletResponse response) {

    PersistentToken token = getPersistentToken(cookieTokens);
    String login = token.getUser().getLogin();

    // Token also matches, so login is valid. Update the token value, keeping the *same* series number.
    log.debug("Refreshing persistent login token for user '{}', series '{}'", login, token.getSeries());
    token.setTokenDate(LocalDate.now());
    token.setTokenValue(generateTokenData());
    token.setIpAddress(request.getRemoteAddr());
    token.setUserAgent(request.getHeader("User-Agent"));
    try {
        persistentTokenRepository.saveAndFlush(token);
        addCookie(token, request, response);
    } catch (DataAccessException e) {
        log.error("Failed to update token: ", e);
        throw new RememberMeAuthenticationException("Autologin failed due to data access problem", e);
    }
    return getUserDetailsService().loadUserByUsername(login);
}

@Override
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication
    successfulAuthentication) {

    String login = successfulAuthentication.getName();

    log.debug("Creating new persistent login for user {}", login);
    PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
        PersistentToken t = new PersistentToken();
        t.setSeries(generateSeriesData());
        t.setUser(u);
        t.setTokenValue(generateTokenData());
        t.setTokenDate(LocalDate.now());
        t.setIpAddress(request.getRemoteAddr());
        t.setUserAgent(request.getHeader("User-Agent"));
        return t;
    }).orElseThrow(() -> new UsernameNotFoundException("User " + login + " was not found in the database"));
    try {
        persistentTokenRepository.saveAndFlush(token);
        addCookie(token, request, response);
    } catch (DataAccessException e) {
        log.error("Failed to save persistent token ", e);
    }
}

/**
 * When logout occurs, only invalidate the current token, and not all user sessions.
 * <p/>
 * The standard Spring Security implementations are too basic: they invalidate all tokens for the
 * current user, so when he logs out from one browser, all his other sessions are destroyed.
 */
@Override
@Transactional
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
    String rememberMeCookie = extractRememberMeCookie(request);
    if (rememberMeCookie != null && rememberMeCookie.length() != 0) {
        try {
            String[] cookieTokens = decodeCookie(rememberMeCookie);
            PersistentToken token = getPersistentToken(cookieTokens);
            persistentTokenRepository.delete(token);
        } catch (InvalidCookieException ice) {
            log.info("Invalid cookie, no persistent token could be deleted");
        } catch (RememberMeAuthenticationException rmae) {
            log.debug("No persistent token found, so no token could be deleted");
        }
    }
    super.logout(request, response, authentication);
}

/**
 * Validate the token and return it.
 */
private PersistentToken getPersistentToken(String[] cookieTokens) {
    if (cookieTokens.length != 2) {
        throw new InvalidCookieException("Cookie token did not contain " + 2 +
            " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
    }
    String presentedSeries = cookieTokens[0];
    String presentedToken = cookieTokens[1];
    PersistentToken token = persistentTokenRepository.findOne(presentedSeries);

    if (token == null) {
        // No series match, so we can't authenticate using this cookie
        throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
    }

    // We have a match for this user/series combination
    log.info("presentedToken={} / tokenValue={}", presentedToken, token.getTokenValue());
    if (!presentedToken.equals(token.getTokenValue())) {
        // Token doesn't match series value. Delete this session and throw an exception.
        persistentTokenRepository.delete(token);
        throw new CookieTheftException("Invalid remember-me token (Series/token) mismatch. Implies previous " +
            "cookie theft attack.");
    }

    if (token.getTokenDate().plusDays(TOKEN_VALIDITY_DAYS).isBefore(LocalDate.now())) {
        persistentTokenRepository.delete(token);
        throw new RememberMeAuthenticationException("Remember-me login has expired");
    }
    return token;
}

private String generateSeriesData() {
    byte[] newSeries = new byte[DEFAULT_SERIES_LENGTH];
    random.nextBytes(newSeries);
    return new String(Base64.encode(newSeries));
}

private String generateTokenData() {
    byte[] newToken = new byte[DEFAULT_TOKEN_LENGTH];
    random.nextBytes(newToken);
    return new String(Base64.encode(newToken));
}

private void addCookie(PersistentToken token, HttpServletRequest request, HttpServletResponse response) {
    setCookie(
        new String[]{token.getSeries(), token.getTokenValue()},
        TOKEN_VALIDITY_SECONDS, request, response);
}

并且这样定义了一个安全配置类:

@Configuration
public class OAuth2ServerConfiguration {

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Inject
    private Http401UnauthorizedEntryPoint authenticationEntryPoint;

    @Inject
    private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;

    @Inject
    private RememberMeServices rememberMeServices;

    @Inject
    private Environment env;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
            .and()
                .rememberMe()
                .rememberMeServices(rememberMeServices)
                .rememberMeParameter("remember-me")
                .key(env.getProperty("jhipster.security.rememberme.key"))
            .and()
                .logout()
                .logoutUrl("/api/logout")
                .logoutSuccessHandler(ajaxLogoutSuccessHandler)
                .deleteCookies("JSESSIONID", "CSRF-TOKEN")
            .and()
                .csrf()
                .disable()
                .headers()
                .frameOptions().disable()
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .requestMatchers()
                .antMatchers("/oauth/token")
            .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .antMatchers("/api/authenticate").permitAll()
                .antMatchers("/api/register").permitAll()
                .antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/api/**").authenticated()
                .antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/liquibase/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/protected/**").authenticated();
    }
}

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Inject
    private DataSource dataSource;

    @Inject
    private JHipsterProperties jHipsterProperties;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Inject
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {

        endpoints
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient(jHipsterProperties.getSecurity().getAuthentication().getOauth().getClientid())
                .scopes("read", "write")
                .authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
                .authorizedGrantTypes("password", "refresh_token", "authorization_code", "implicit")
                .secret(jHipsterProperties.getSecurity().getAuthentication().getOauth().getSecret())
                .accessTokenValiditySeconds(jHipsterProperties.getSecurity().getAuthentication().getOauth().getTokenValidityInSeconds());
    }
}

谢谢。

最佳答案

在 spring security oauth 中,由于 RememberMeAuthenticationFilter 未关联到登录 url (/oauth/token),记住我服务无法工作,即使在使用 rememberMe 配置器之后也是如此。

为了能够具有记住我的功能,必须使用刷新 token 来不断更新访问 token ,然后才能被记住。

您可以阅读这两篇文章以了解如何做到这一点。 http://www.baeldung.com/rest-api-spring-oauth2-angularjs http://www.baeldung.com/spring-security-oauth2-remember-me

关于java - Spring Security - 记住我自定义服务 onLoginSuccess 未使用 oauth2 调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46084050/

相关文章:

java - 如何从java中的枚举中获取名称

java - 圆碰撞错误,圆绕不动的圆运行

java - printf ("%+f", ..) 仍然显示负数

spring - 我可以在颁发访问 token 时包含用户信息吗?

java - Spring Boot 2 kerberos 身份验证

javascript - 使用不记名 token 在 JavaScript 中加载图像

java - 如何将房间数据库中的列表数据添加到android中的警报对话框

Spring Security - 在应用程序上下文中找不到可见的 WebSecurityExpressionHandler 实例

spring - 调用 spring 授权服务器 OAuth2 REST 端点

oauth-2.0 - OAuth 授权流程 - token 过期