【问题标题】:Spring Security with REST API带有 REST API 的 Spring 安全性
【发布时间】:2016-01-24 07:11:10
【问题描述】:

我正在尝试创建一个主要使用 Spring 访问 REST API 的应用程序,并尝试配置安全方面。尝试使用这张图片展示应用程序的实际结构:

  1. 请求可以来自任何平台到“abc.com/rest_api/”
  2. 请求将被发送到第 3 点或第 5 点。如果用户已经通过用户名和密码进行身份验证,则请求将根据 Token 进行验证,否则将被重定向到数据库。
  3. 如果用户名和密码必须通过数据库进行身份验证,那么将生成一个令牌并作为响应发回。
  4. 之后,只有基于令牌的身份验证才会起作用。

我试图创建一个基本结构,但我认为一定是犯了一个小错误,导致它没有按预期工作。

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled=true)
public class UserDetailsSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private NSecurityContextHolder securityContextHolder;

    @Autowired
    private NHttpServletRequestBinder<Authentication> authenticationBinder;

    public static final String DEF_USERS_BY_USERNAME_QUERY
            = "SELECT user ";


public static final String GROUPS_BY_USERNAME_QUERY =
        "SELECT groups by user";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
        "SELECT  authorities";


@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
                .usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
                authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
                groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);


    }
      private DataSource getDataSourceFromJndi() {
        try {


             DataSource dataSource = (DataSource) new InitialContext().lookup("DS");
            return dataSource;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.jdbcAuthentication().dataSource(getDataSourceFromJndi())
                .usersByUsernameQuery(DEF_USERS_BY_USERNAME_QUERY).
                authoritiesByUsernameQuery(DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY).
                groupAuthoritiesByUsername(GROUPS_BY_USERNAME_QUERY);

    }


      @Override
    protected void configure(HttpSecurity http) throws Exception {


                // The http.formLogin().defaultSuccessUrl("/path/") method is required when using stateless Spring Security
        // because the session cannot be used to redirect to the page that was requested while signed out. Unfortunately
        // using this configuration method will cause our custom success handler (below) to be overridden with the
        // default success handler. So to replicate the defaultSuccessUrl("/path/") configuration we will instead
        // correctly configure and delegate to the default success handler.
        final SimpleUrlAuthenticationSuccessHandler delegate = new SimpleUrlAuthenticationSuccessHandler();
        delegate.setDefaultTargetUrl("/api/");
          // Make Spring Security stateless. This means no session will be created by Spring Security, nor will it use any
        // previously existing session.
        http.sessionManagement().sessionCreationPolicy(STATELESS);
        // Disable the CSRF prevention because it requires the session, which of course is not available in a
        // stateless application. It also greatly complicates the requirements for the sign in POST request.
        http.csrf().disable();
        // Viewing any page requires authentication.
        http.authorizeRequests().anyRequest().authenticated();
        http
          .formLogin().loginPage("http://localhost/web/ui/#access/signin")
          .permitAll()
            // Override the sign in success handler with our stateless implementation. This will update the response
            // with any headers and cookies that are required for subsequent authenticated requests.
            .successHandler(new NStatelessAuthenticationSuccessHandler(authenticationBinder, delegate));
        http.logout().logoutUrl("http://localhost/web/ui/#access/signin").logoutSuccessUrl("http://localhost/web/ui/#access/signin");
        // Add our stateless authentication filter before the default sign in filter. The default sign in filter is
        // still used for the initial sign in, but if a user is authenticated we need to acknowledge this before it is
        // reached.
        http.addFilterBefore(
            new StatelessAuthenticationFilter(authenticationBinder, securityContextHolder),
            UsernamePasswordAuthenticationFilter.class
        );

} 

}

我有两种类型的 authenticationBinder,即 TokenBased 和 UserNameBased。

基于令牌:

@Component
public class NXAuthTokenHttpServletRequestBinder implements NHttpServletRequestBinder<String> {

    private static final String X_AUTH_TOKEN = "X-AUTH-TOKEN";
    private final NTokenFactory tokenFactory;

    @Autowired
    public NXAuthTokenHttpServletRequestBinder(NTokenFactory tokenFactory) {
        this.tokenFactory = tokenFactory;
    }

    @Override
    public void add(HttpServletResponse response, String username) {

        final String token = tokenFactory.create(username);

        response.addHeader(X_AUTH_TOKEN, token);
        response.addCookie(new Cookie(X_AUTH_TOKEN, token));
    }

    @Override
    public String retrieve(HttpServletRequest request) {

        final String cookieToken = findToken(request);

        if (cookieToken != null) {
            return tokenFactory.parseUsername(cookieToken);
        }

        return null;
    }

    private static String findToken(HttpServletRequest request) {
        Enumeration<String> it = request.getHeaderNames();
        while(it.hasMoreElements()){
            System.out.println(it.nextElement());
        }
        final String headerToken = request.getHeader(X_AUTH_TOKEN);

        if (headerToken != null) {
            return headerToken;
        }

        final Cookie[] cookies = request.getCookies();

        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (X_AUTH_TOKEN.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }

        return null;
    }
}

基于用户:

@Component
@Primary
public class NUserAuthenticationFactory implements NHttpServletRequestBinder<Authentication> {

    private final NHttpServletRequestBinder<String> httpServletRequestBinder;

    @Autowired

    public NUserAuthenticationFactory(NHttpServletRequestBinder<String> httpServletRequestBinder) {
        this.httpServletRequestBinder = httpServletRequestBinder;
    }

    @Override
    public void add(HttpServletResponse response, Authentication authentication) {
        httpServletRequestBinder.add(response, authentication.getName());
    }

    @Override
    public UserAuthentication retrieve(HttpServletRequest request) {

        final String username = httpServletRequestBinder.retrieve(request);

        if (username != null) {

            return new UserAuthentication(new CustomJDBCDaoImpl().loadUserByUsername(username));
        }

        return null;
    }
}

问题 每当我加载我的应用程序时,它都会进入基于用户的身份验证,然后尝试从令牌中获取用户名,而不是从数据库中验证它。但是,那时还没有令牌,因为这是我从 UI 发出的第一个发布请求。它会将我重定向回同一个登录页面。

日志:

Fine: / 在附加过滤器链中的第 12 个位置;射击 过滤器:'WebAsyncManagerIntegrationFilter' 精细:/ 在位置 2 12 个附加过滤器链;发射过滤器: 'SecurityContextPersistenceFilter' Fine: / 在第 3 位,共 12 位 额外的过滤器链;触发过滤器:'HeaderWriterFilter' 很好:
不注入 HSTS 标头,因为它与 requestMatcher 不匹配 org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@a837508 精细:/在附加过滤器链中的 12 的第 4 位;射击 Filter: 'LogoutFilter' Fine: Checking match of request : '/'; 反对'http://localhost/web/ui/#access/signin'罚款:/在位置 附加过滤器链中的 12 个中的 5 个;发射过滤器: 'StatelessAuthenticationFilter' 精细:/在 12 中的第 6 位 额外的过滤器链;发射过滤器: 'UsernamePasswordAuthenticationFilter' 很好:请求 'GET /' 没有 匹配'POST http://localhost/web/ui/#access/signin Fine: / at 在附加过滤器链中的 12 位中的第 7 位;发射过滤器: 'RequestCacheAwareFilter' Fine: / 在第 8 位,共 12 位 过滤链;触发过滤器:'SecurityContextHolderAwareRequestFilter' Fine: / 在附加过滤器链中的 12 的第 9 位;射击 过滤器:'AnonymousAuthenticationFilter' 精细:填充 带有匿名令牌的 SecurityContextHolder: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: 委托人:anonymousUser;凭证:[受保护];已认证: 真的;细节: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: 远程IP地址:127.0.0.1;会话ID:空;授予权限: ROLE_ANONYMOUS' Fine: / 在附加过滤器中的 12 的位置 10 链;触发过滤器:“SessionManagementFilter”罚款:已请求 会话 ID 3e2c15a2a427bf47e51496d2a186 无效。罚款:/ 在 在附加过滤器链中的 12 中的第 11 位;发射过滤器: 'ExceptionTranslationFilter' 很好:/在第 12 位,共 12 位 额外的过滤器链;触发过滤器:'FilterSecurityInterceptor' 罚款:安全对象:FilterInvocation:URL:/;属性: [已验证] 很好:先前已验证: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: 委托人:anonymousUser;凭证:[受保护];已认证: 真的;细节: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: 远程IP地址:127.0.0.1;会话ID:空;授予权限: ROLE_ANONYMOUS 罚款:选民: org.springframework.security.web.access.expression.WebExpressionVoter@2ac71565, 返回:-1 Fine:访问被拒绝(用户是匿名的);重定向 到认证入口点 org.springframework.security.access.AccessDeniedException:访问是 拒绝

【问题讨论】:

    标签: java spring rest spring-mvc spring-security


    【解决方案1】:

    可以逐步解决您的问题..

    1. 使用spring securityUsernamePasswordAuthenticationFilter通过用户名和密码对用户进行身份验证,并生成唯一令牌。
    2. 编写另一个自定义安全过滤器和AuthenticationProvider 实现来验证用户的连续请求。
    3. UsernamePasswordAuthenticationFilter 之前放置自定义安全过滤器,如下所示

      http.addFilterBefore(CustomTokenBasedAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    4. AuthenticationManager注册你的AuthenticationProvider实现

    5. 就是这样!!!

    注意:- 保护您的 REST API 的更好方法是使用一些标准协议,如 oauth1a、oauth2.0 等。Spring 提供了 oauth1a 和 oauth2.0 协议的新颖实现。

    【讨论】:

      猜你喜欢
      • 2015-03-18
      • 2017-12-05
      • 2015-04-15
      • 2014-04-27
      • 2014-02-24
      • 2012-03-06
      • 2015-05-22
      • 2015-09-08
      • 2023-03-31
      相关资源
      最近更新 更多