spring-security 如何使用Sping Boot 和Spring Security保护REST API?

bxpogfeg  于 2022-11-11  发布在  Spring
关注(0)|答案(5)|浏览(184)

我知道保护REST API是一个广受评论的主题,但是我不能创建一个满足我的标准的小原型(我需要确认这些标准是现实的)。如何保护资源和如何使用Spring安全性有很多选择,我需要澄清我的需求是否现实。

我的要求

  • 基于令牌的身份验证器-用户将提供其凭据,并获得唯一的和时间有限的访问令牌。我想管理令牌的创建,检查有效性,在我自己的实现到期。
  • 一些REST资源将是公共的-根本不需要进行身份验证,
  • 某些资源只有具有管理员权限的用户才能访问,
  • 其他资源将在所有用户授权后可访问。
  • 我不想使用基本身份验证
  • Java代码配置(非XML)
    当前状态

我的REST API运行得很好,但现在我需要保护它。当我在寻找解决方案时,我创建了一个javax.servlet.Filter过滤器:

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

        HttpServletRequest request = (HttpServletRequest) req;

        String accessToken = request.getHeader(AUTHORIZATION_TOKEN);
        Account account = accountDao.find(accessToken);

        if (account == null) {    
            throw new UnauthorizedException();    
        }

        chain.doFilter(req, res);

    }

但是这个javax.servlet.filters的解决方案并不像我所需要的那样工作,因为通过Spring servlet dispatcher@ControllerAdvice进行异常处理有一个问题。
"我所需要的"
我想知道这些标准是否现实,并获得任何帮助,如何开始使用Spring Security保护REST API。我阅读了许多教程(例如Spring Data REST + Spring Security),但都在非常基本的配置中工作-用户与他们的凭据存储在内存中在配置中,我需要使用DBMS并创建自己的身份验证器。
"请给予我一些如何开始的想法"

bz4sfanl

bz4sfanl1#

基于令牌的身份验证-用户将提供其凭据,并获得唯一的和有时间限制的访问令牌。我想管理令牌的创建,检查有效性,在我自己的实现到期。
实际上,使用筛选器进行令牌身份验证-在这种情况下是最好的方法
最后,您可以通过Spring Data创建CRUD,用于管理令牌的属性,如到期时间等。
以下是我的令牌过滤器:http://pastebin.com/13WWpLq2
和令牌服务实现
http://pastebin.com/dUYM555E
一些REST资源将是公共的-根本不需要进行身份验证
这不是问题,您可以通过Spring安全配置来管理您的资源,如下所示:.antMatchers("/rest/blabla/**").permitAll()
某些资源只有具有管理员权限的用户才能访问,
看一下类的@Secured注解。例如:

@Controller
@RequestMapping(value = "/adminservice")
@Secured("ROLE_ADMIN")
public class AdminServiceController {

其他资源将在所有用户获得授权后可访问。
返回Spring Security配置,您可以如下配置您的url:

http
            .authorizeRequests()
            .antMatchers("/openforall/**").permitAll()
            .antMatchers("/alsoopen/**").permitAll()
            .anyRequest().authenticated()

我不想使用基本身份验证
是的,通过令牌过滤器,您的用户将被验证。
Java代码配置(非XML)
回到上面的话,看看@EnableWebSecurity。你的类将是:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {}

你必须重写configure方法。下面的代码,只是举例说明,如何配置匹配器。它来自另一个项目。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/assets/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
                .usernameParameter("j_username")
                .passwordParameter("j_password")
                .loginPage("/login")
                .defaultSuccessUrl("/", true)
                .successHandler(customAuthenticationSuccessHandler)
                .permitAll()
            .and()
                .logout()
                .logoutUrl("/logout")
                .invalidateHttpSession(true)
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID")
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .and()
                .csrf();
}
gwbalxhn

gwbalxhn2#

Spring安全性对于提供RESTURL的身份验证和授权也非常有用。我们不需要指定任何自定义实现。
首先,您需要在安全配置中指定restAuthenticationEntryPoint的入口点-ref,如下所示。

<security:http pattern="/api/**" entry-point-ref="restAuthenticationEntryPoint" use-expressions="true" auto-config="true" create-session="stateless" >

    <security:intercept-url pattern="/api/userList" access="hasRole('ROLE_USER')"/>
    <security:intercept-url pattern="/api/managerList" access="hasRole('ROLE_ADMIN')"/>
    <security:custom-filter ref="preAuthFilter" position="PRE_AUTH_FILTER"/>
</security:http>

restAuthenticationEntryPoint的实现可能如下所示。

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

   public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException ) throws IOException {
      response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
   }
}

在这之后你需要指定RequestHeaderAuthenticationFilter。它包含RequestHeader键。这基本上是用于识别用户的身份验证。通常RequestHeader在进行REST调用时携带此信息。例如,考虑下面的代码

<bean id="preAuthFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
    <property name="principalRequestHeader" value="Authorization"/>
    <property name="authenticationManager" ref="authenticationManager" />
  </bean>

在这里,

<property name="principalRequestHeader" value="Authorization"/>

“Authorization”是传入请求的密钥。它包含所需的用户身份验证信息。同时,您还需要配置PreAuthenticatedAuthenticationProvider来满足我们的要求。

<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
  <bean id="userDetailsServiceWrapper"
      class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
    <property name="userDetailsService" ref="authenticationService"/>
  </bean>
</property>
</bean>

这段代码将用于通过身份验证和授权来保护REST URL,而无需任何自定义实现。
如需完整代码,请找到以下链接:
https://github.com/srinivas1918/spring-rest-security

yx2lnoni

yx2lnoni3#

我也搜索了很长时间。我正在做一个类似的项目。我发现Spring有一个模块可以通过redis实现会话。它看起来很简单也很有用。我也会添加到我的项目中。可以帮助:
http://docs.spring.io/spring-session/docs/1.2.1.BUILD-SNAPSHOT/reference/html5/guides/rest.html

i34xakig

i34xakig4#

http.addFilterBefore()与自定义筛选器一起使用的另一种方法

这个解决方案更像是一个帮助你建立基础的框架。我已经创建了一个working demo,并添加了一些必要的注解来帮助你理解这个过程。它附带了一些简单的role-basedpermission-based身份验证/授权,一个publically accessable endpoint设置,你可以很容易地选择和使用。

因此,最好查看完整的代码,然后运行应用程序:github repo

用户类设置:

public class User implements UserDetails {

  private final String username;
  private final String password;
  private final List<? extends GrantedAuthority> grantedAuthorities;

  public User(
    String username,
    String password,
    List<? extends GrantedAuthority> grantedAuthorities
  ) {
    this.username = username;
    this.password = password;
    this.grantedAuthorities = grantedAuthorities;
  }

  // And other default method overrides
}

通过addFilterBefore()方法添加自定义过滤器:

http
    .authorizeRequests()
    .antMatchers("/") 
    .permitAll()
    .addFilterBefore( // Filter login request only
        new LoginFilter("login", authenticationManager()),
        UsernamePasswordAuthenticationFilter.class
    )
    .addFilterBefore( // Filter logout request only
        new LogoutFilter("logout"),
        UsernamePasswordAuthenticationFilter.class
    )
    .addFilterBefore( // Verify user on every request
        new AuthenticationFilter(),
        UsernamePasswordAuthenticationFilter.class
    );

自定义LoginFilter扩展了AbstractAuthenticationProcessingFilter,并覆盖了三种方法,来处理身份验证:

public class LoginFilter extends AbstractAuthenticationProcessingFilter {

  public LoginFilter(String url, AuthenticationManager authManager) {
    super(url, authManager);
  }

  @Override
  public Authentication attemptAuthentication(
    HttpServletRequest req,
    HttpServletResponse res
  )
    throws AuthenticationException, IOException {
    LoginUserDto loginUserDto = new ObjectMapper() // this dto is a simple {username, password} object
    .readValue(req.getInputStream(), LoginUserDto.class);

    return getAuthenticationManager()
      .authenticate(
        new UsernamePasswordAuthenticationToken(
          loginUserDto.getUsername(),
          loginUserDto.getPassword()
        )
      );
  }

  @Override
  protected void successfulAuthentication(
    HttpServletRequest req,
    HttpServletResponse res,
    FilterChain chain,
    Authentication auth
  )
    throws IOException, ServletException {
    User user = (User) auth.getPrincipal();

    req.getSession().setAttribute(UserSessionKey, user); // Simply put it in session

    res.getOutputStream().print("You are logged in as " + user.getUsername());
  }

  @Override
  protected void unsuccessfulAuthentication(
    HttpServletRequest request,
    HttpServletResponse response,
    AuthenticationException failed
  )
    throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType("text/plain");
    response.getOutputStream().print(failed.getMessage());
  }
}

对会话中存储的auth info进行自定义AuthenticationFilter检查并传递给SecurityContext

public class AuthenticationFilter extends GenericFilterBean {

  @Override
  public void doFilter(
    ServletRequest request,
    ServletResponse response,
    FilterChain filterChain
  )
    throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpSession session = req.getSession();

    User user = (User) session.getAttribute(UserSessionKey);

    if (user != null) {
      UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
        user,
        user.getPassword(),
        user.getAuthorities()
      );

      SecurityContextHolder.getContext().setAuthentication(authToken);
    }

    // Either securityContext has authToken or not, we continue the filter chain
    filterChain.doFilter(request, response);
  }
}

自定义LogoutFilter相当简单直接,使会话无效并终止身份验证过程:

public class LogoutFilter extends AbstractAuthenticationProcessingFilter {

  public LogoutFilter(String url) {
    super(url);
  }

  @Override
  public Authentication attemptAuthentication(
    HttpServletRequest req,
    HttpServletResponse res
  )
    throws AuthenticationException, IOException {
    req.getSession().invalidate();
    res.getWriter().println("You logged out!");

    return null;
  }
}

解释一下:

这三个自定义筛选器的作用是,loginlogout筛选器仅侦听其各自的端点。
在登录过滤器中,我们获取从客户端发送的username and password,并根据数据库(在真实的世界中)对其进行检查以进行验证,如果它是有效用户,则将其放入会话中并将其传递到SecurityContext
在注销过滤器中,我们只需invalidate the session并返回一个字符串。
而自定义的AuthenticationFilter将验证每个传入的请求,以尝试从会话中获取用户信息,然后将其传递到SecurityContext

new9mtju

new9mtju5#

要验证REST API,有两种方法
1 -使用在www.example.com文件中设置的默认用户名和密码进行基本身份验证application.properties
Basic Authentication
2 -使用数据库(userDetailsService)和实际的用户名和口令进行验证
Advanced Authentication

相关问题