作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Ioram Gordadze's profile image

Ioram Gordadze

Ioram拥有计算机科学硕士学位和十多年的Java专业经验. 他专门研究企业级应用程序.

Expertise

Previously At

WorkSpan
Share

免责声明:Spring Security 5+已经发布了OAuth JWT支持. 建议使用最新版本的OAuth来支持JWT,而不是使用自定义安全性或过滤器.

Spring 被认为是Java生态系统中的可信框架,并且被广泛使用. 将Spring称为框架已不再有效, 因为它更像是一个涵盖各种框架的总称. One of these frameworks is Spring Security,这是一个功能强大且可自定义的身份验证和授权框架. 它被认为是保护基于spring的应用程序的事实上的标准, 因此,如果您希望实现Spring JWT令牌解决方案, 基于Spring Security是有意义的.

尽管它很受欢迎,但我必须承认,当谈到 single-page applicationsSpring的配置并不简单和直接. 我怀疑原因是,它更像是一种 MVC application-oriented framework, 网页渲染发生在服务器端,通信是基于会话的.

如果后端是基于Java和Spring的,那么使用Spring是有意义的 Security with JWT 用于身份验证/授权,并将其配置为无状态通信. 虽然有很多文章解释了如何做到这一点, for me, 第一次设置的时候还是很沮丧, 我必须阅读和总结来自多个来源的信息. 这就是我决定编写这个Spring安全性教程的原因, 我将尝试总结并涵盖您在配置过程中可能遇到的所有必要的细微细节和缺点.

Defining Terminology

在深入技术细节之前, 我想明确地定义Spring Security上下文中使用的术语,以确保我们使用的是同一种语言.

这些是我们需要解决的问题:

  • Authentication 指根据提供的凭据验证用户身份的过程. 一个常见的例子是在登录网站时输入用户名和密码. 你可以把它看作问题的答案 Who are you?.
  • Authorization 指确定用户是否具有执行特定操作或读取特定数据的适当权限的过程, 假设用户已成功通过身份验证. 你可以把它看作问题的答案 Can a user do/read this?.
  • Principle 指当前认证的用户.
  • Granted authority 通过认证的用户的权限.
  • Role 指通过认证的用户的一组权限.

Creating a Basic Spring Application

在开始配置Spring Security框架之前, 让我们创建一个基本的Spring web应用程序. For this, we can use a Spring Initializr and generate a template project. 对于一个简单的web应用,只需要一个Spring web框架依赖就足够了:


    
        org.springframework.boot
        spring-boot-starter-web
    

一旦我们创建了项目,我们可以添加一个简单的REST控制器,如下所示:

@RestController @RequestMapping(“hello”)
public class HelloRestController {

    @GetMapping("user")
    public String helloUser() {
        return "Hello User";
    }

    @GetMapping("admin")
    public String helloAdmin() {
        return "Hello Admin";
    }

}

在此之后,如果我们构建并运行项目,我们可以在web浏览器中访问以下url:

  • http://localhost:8080/hello/user will return the string Hello User.
  • http://localhost:8080/hello/admin will return the string Hello Admin.

Now, 我们可以将Spring Security框架添加到项目中, 我们可以通过将以下依赖项添加到 pom.xml file:


    
      org.springframework.boot
      spring-boot-starter-security
    

在我们提供相应的配置之前,添加其他Spring框架依赖通常不会立即对应用程序产生影响, 但Spring Security的不同之处在于它确实有立竿见影的效果, and this usually confuses new users. After adding it, 如果我们重新构建并运行项目,然后尝试访问上述url之一,而不是查看结果, we will be redirected to http://localhost:8080/login. 这是默认行为,因为Spring Security框架要求对所有url进行开箱即用的身份验证.

为了通过身份验证,我们可以使用默认用户名 user 在控制台中找到一个自动生成的密码:

使用生成的安全密码:1fc15145-dfee-4bec-a009-e32ca21c77ce

请记住,每次重新运行应用程序时,密码都会更改. 如果我们想要更改此行为并将密码设置为静态, 我们可以将以下配置添加到 application.properties file:

spring.security.user.password=Test12345_

Now, 如果我们在登录表单中输入凭据, 我们将被重定向回我们的URL,我们将看到正确的结果. 请注意,开箱即用的身份验证过程是基于会话的, and if we want to log out, we can access the following URL: http://localhost:8080/logout

这种开箱即用的行为可能对经典MVC web应用程序有用,因为我们有基于会话的身份验证, 但是对于单页应用程序, 它通常没有用,因为在大多数用例中, 我们有客户端呈现和基于jwt的无状态身份验证. In this case, 我们将不得不大量定制Spring Security框架, 我们将在本文的其余部分做什么.

作为一个例子,我们将实现一个经典的 bookstore web application 并创建一个后端,提供用于创建作者和图书的CRUD api,以及用于用户管理和身份验证的api.

Spring Security Architecture Overview

在我们开始定制配置之前, 让我们首先讨论Spring Security身份验证在幕后是如何工作的.

下图展示了流程并显示了如何处理身份验证请求:

Spring Security Architecture

Spring Security Architecture

现在,让我们将此图分解为组件,并分别讨论每个组件.

Spring Security Filters Chain

将Spring Security框架添加到应用程序时, 它会自动注册一个过滤器链,拦截所有传入的请求. 这个链由各种过滤器组成,每个过滤器处理一个特定的用例.

For example:

  • 根据配置检查请求的URL是否可公开访问.
  • 在基于会话的身份验证情况下, 检查用户是否已经在当前会话中进行了身份验证.
  • 检查用户是否被授权执行所请求的操作,等等.

我想提到的一个重要细节是,Spring Security过滤器以最低顺序注册,并且是第一个调用的过滤器. For some use cases, 如果你想把你的自定义过滤器放在它们前面, 您将需要为它们的顺序添加填充. 这可以通过以下配置来完成:

spring.security.filter.order=10

Once we add this configuration to our application.properties 文件,我们将有空间在Spring Security过滤器前面放置10个自定义过滤器.

AuthenticationManager

You can think of AuthenticationManager 作为一个协调器,你可以注册多个提供者, and based on the request type, 它将向正确的提供者传递身份验证请求.

AuthenticationProvider

AuthenticationProvider 处理特定类型的认证. 它的接口只暴露了两个函数:

  • authenticate 对请求执行身份验证.
  • supports 检查此提供程序是否支持指定的身份验证类型.

我们在示例项目中使用的接口的一个重要实现是 DaoAuthenticationProvider, which retrieves user details from a UserDetailsService.

UserDetailsService

UserDetailsService 在Spring文档中被描述为加载用户特定数据的核心接口.

In most use cases, 身份验证提供程序根据数据库中的凭据提取用户身份信息,然后执行验证. Because this use case is so common, Spring开发人员决定将其提取为一个单独的接口, which exposes the single function:

  • loadUserByUsername 接受username作为参数并返回用户标识对象.

使用JWT和Spring安全性进行身份验证

在讨论了Spring Security框架的内部之后, 让我们将其配置为无状态身份验证 JWT token.

要为JWT的使用定制Spring Security,我们需要一个带注释的配置类 @EnableWebSecurity annotation in our classpath. 此外,为了简化定制过程,框架还公开了一个 WebSecurityConfigurerAdapter class. 我们将扩展这个适配器并覆盖它的两个功能,以便:

  1. 使用正确的提供程序配置身份验证管理器
  2. 配置web安全(公共url、私有url、授权等).)
@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    @Override
    被保护的无效配置(AuthenticationManagerBuilder auth)抛出异常{
        // TODO配置认证管理器
    }

    @Override
    (HttpSecurity)抛出异常{
        // TODO configure web security
    }

}

在我们的样例应用程序中,我们将用户身份存储在MongoDB数据库中 users collection. These identities are mapped by the User 实体,它们的CRUD操作由 UserRepo Spring Data repository.

Now, 当我们接受身份验证请求时, 我们需要使用提供的凭据从数据库检索正确的标识,然后对其进行验证. 为此,我们需要实现 UserDetailsService 接口,定义如下:

public interface UserDetailsService {

    loadUserByUsername(字符串用户名)
            throws UsernameNotFoundException;

}

在这里,我们可以看到需要返回实现的对象 UserDetails interface, and our User 实体实现它(有关实现细节,请参阅示例项目的存储库). 考虑到它只暴露了单一功能的原型, 我们可以将其视为函数式接口,并将其作为lambda表达式提供实现.

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    private final UserRepo userRepo;

    public SecurityConfig(UserRepo) {
        this.userRepo = userRepo;
    }

    @Override
    被保护的无效配置(AuthenticationManagerBuilder auth)抛出异常{
        auth.userDetailsService(username -> userRepo
            .findByUsername(username)
            .orElseThrow(
                () -> new UsernameNotFoundException(
                    format("User: %s, not found", username)
                )
            ));
    }

    // Details omitted for brevity

}

Here, the auth.userDetailsService function call will initiate the DaoAuthenticationProvider 的实现实例 UserDetailsService 接口,并在身份验证管理器中注册它.

Along with the authentication provider, 我们需要用正确的密码编码模式配置身份验证管理器,该模式将用于凭证验证. 的首选实现 PasswordEncoder interface as a bean.

In our sample project, we will use the bcrypt password-hashing algorithm.

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    private final UserRepo userRepo;

    public SecurityConfig(UserRepo) {
        this.userRepo = userRepo;
    }

    @Override
    被保护的无效配置(AuthenticationManagerBuilder auth)抛出异常{
        auth.userDetailsService(username -> userRepo
            .findByUsername(username)
            .orElseThrow(
                () -> new UsernameNotFoundException(
                    format("User: %s, not found", username)
                )
            ));
    }

    @Bean
    public PasswordEncoder () {
        return new BCryptPasswordEncoder();
    }

    // Details omitted for brevity

}

配置了身份验证管理器之后,我们现在需要配置web安全性. We are implementing a REST API and need stateless authentication with a JWT token; therefore, we need to set the following options:

  • Enable CORS and disable CSRF.
  • Set session management to stateless.
  • 设置未授权请求异常处理程序.
  • Set permissions on endpoints.
  • Add JWT token filter.

该配置的实现如下:

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    private final UserRepo userRepo;
    JwtTokenFilter;

    UserRepo (UserRepo)
                          JwtTokenFilter jwtTokenFilter) {
        this.userRepo = userRepo;
        this.jwtTokenFilter = jwtTokenFilter;
    }

    // Details omitted for brevity

    @Override
    (HttpSecurity)抛出异常{
        // Enable CORS and disable CSRF
        http = http.cors().and().csrf().disable();

        // Set session management to stateless
        http = http
            .sessionManagement()
            .sessionCreationPolicy (sessionCreationPolicy.STATELESS)
            .and();

        //设置未授权请求异常处理程序
        http = http
            .exceptionHandling()
            .authenticationEntryPoint(
                (request, response, ex) -> {
                    response.sendError(
                        HttpServletResponse.SC_UNAUTHORIZED,
                        ex.getMessage()
                    );
                }
            )
            .and();

        // Set permissions on endpoints
        http.authorizeRequests()
            // Our public endpoints
            .antMatchers("/api/public/**").permitAll()
            .antMatchers(HttpMethod.GET, "/api/author/**").permitAll()
            .antMatchers(HttpMethod.POST, "/api/author/search").permitAll()
            .antMatchers(HttpMethod.GET, "/api/book/**").permitAll()
            .antMatchers(HttpMethod.POST, "/api/book/search").permitAll()
            // Our private endpoints
            .anyRequest().authenticated();

        // Add JWT token filter
        http.addFilterBefore(
            jwtTokenFilter,
            UsernamePasswordAuthenticationFilter.class
        );
    }

    //启用CORS时由Spring Security使用.
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource =
            new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration(“/ * *”,配置);
        return new CorsFilter(source);
    }

}

Please note that we added the JwtTokenFilter before the Spring Security internal UsernamePasswordAuthenticationFilter. 这样做是因为我们需要在此时访问用户标识以执行身份验证/授权, 它的提取在基于提供的JWT令牌的JWT令牌过滤器中进行. This is implemented as follows:

@Component
JwtTokenFilter扩展OncePerRequestFilter

    private final JwtTokenUtil;
    private final UserRepo userRepo;

    JwtTokenUtil; JwtTokenUtil;
                          UserRepo userRepo) {
        this.jwtTokenUtil = jwtTokenUtil;
        this.userRepo = userRepo;
    }

    @Override
    doFilterInternal(HttpServletRequest)
                                    HttpServletResponse response,
                                    FilterChain chain)
            throws ServletException, IOException {
        //获取授权头并验证
        final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (isEmpty(header) || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        // Get jwt token and validate
        final String token = header.split(" ")[1].trim();
        if (!jwtTokenUtil.validate(token)) {
            chain.doFilter(request, response);
            return;
        }

        //获取用户身份并在spring安全上下文中设置
        UserDetails userDetails = userRepo
            .findByUsername(jwtTokenUtil.getUsername(token))
            .orElse(null);

        UsernamePasswordAuthenticationToken
            authentication = 新UsernamePasswordAuthenticationToken ()
                userDetails, null,
                userDetails == null ?
                    List.of() : userDetails.getAuthorities()
            );

        authentication.setDetails(
            new WebAuthenticationDetailsSource().buildDetails(request)
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

}

在实现我们的登录API函数之前, 我们还需要处理另一个步骤—我们需要访问身份验证管理器. By default, it’s not publicly accessible, 我们需要在配置类中将其显式地公开为bean.

This can be done as follows:

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    // Details omitted for brevity

    @Override @Bean
    公共AuthenticationManager authenticationManagerBean()抛出异常{
        return super.authenticationManagerBean();
    }

}

现在,我们准备实现我们的登录API函数:

@Api(tags = "Authentication")
@RestController @RequestMapping(path = "api/public")
public class AuthApi {

    private final AuthenticationManager AuthenticationManager
    private final JwtTokenUtil;
    private final UserViewMapper UserViewMapper

    AuthApi(AuthenticationManager)
                   JwtTokenUtil jwtTokenUtil,
                   UserViewMapper userViewMapper) {
        this.authenticationManager = authenticationManager;
        this.jwtTokenUtil = jwtTokenUtil;
        this.userViewMapper = userViewMapper;
    }

    @PostMapping("login")
    public ResponseEntity login(@RequestBody @Valid AuthRequest request) {
        try {
            鉴权authenticate = authenticationManager
                .authenticate(
                    新UsernamePasswordAuthenticationToken (
                        request.getUsername(), request.getPassword()
                    )
                );

            User user = (User) authenticate.getPrincipal();

            return ResponseEntity.ok()
                .header(
                    HttpHeaders.AUTHORIZATION,
                    jwtTokenUtil.generateAccessToken(user)
                )
                .body(userViewMapper.toUserView(user));
        } catch (BadCredentialsException ex) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
    }

}

Here, 我们使用身份验证管理器验证提供的凭据, and in case of success, 我们生成JWT令牌,并将其作为响应头返回,同时返回响应体中的用户身份信息.

JWT Authorization with Spring Security

In the previous section, 我们设置了一个Spring JWT认证过程,并配置了公共/私有url. 这对于简单的应用程序来说可能已经足够了, but for most real-world use cases, 对于用户,我们总是需要基于角色的访问策略. In this chapter, 我们将解决这个问题,并使用Spring Security框架设置基于角色的授权模式.

在我们的示例应用程序中,我们定义了以下三个角色:

  • USER_ADMIN allows us to manage application users.
  • AUTHOR_ADMIN allows us to manage authors.
  • BOOK_ADMIN allows us to manage books.

现在,我们需要将它们应用到相应的url:

  • api/public is publicly accessible.
  • api/admin/user can access users with the USER_ADMIN role.
  • api/author can access users with the AUTHOR_ADMIN role.
  • api/book can access users with the BOOK_ADMIN role.

Spring Security框架为我们提供了两种设置授权模式的选项:

  • URL-based configuration
  • Annotation-based configuration

首先,让我们看看基于url的配置是如何工作的. 可应用于如下web安全配置:

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    // Details omitted for brevity

    @Override
    (HttpSecurity)抛出异常{
        // Enable CORS and disable CSRF
        http = http.cors().and().csrf().disable();

        // Set session management to stateless
        http = http
            .sessionManagement()
            .sessionCreationPolicy (sessionCreationPolicy.STATELESS)
            .and();

        //设置未授权请求异常处理程序
        http = http
            .exceptionHandling()
            .authenticationEntryPoint(
                (request, response, ex) -> {
                    response.sendError(
                        HttpServletResponse.SC_UNAUTHORIZED,
                        ex.getMessage()
                    );
                }
            )
            .and();

        // Set permissions on endpoints
        http.authorizeRequests()
            // Our public endpoints
            .antMatchers("/api/public/**").permitAll()
            .antMatchers(HttpMethod.GET, "/api/author/**").permitAll()
            .antMatchers(HttpMethod.POST, "/api/author/search").permitAll()
            .antMatchers(HttpMethod.GET, "/api/book/**").permitAll()
            .antMatchers(HttpMethod.POST, "/api/book/search").permitAll()
            // Our private endpoints
            .antMatchers("/api/admin/user/**").hasRole(Role.USER_ADMIN)
            .antMatchers("/api/author/**").hasRole(Role.AUTHOR_ADMIN)
            .antMatchers("/api/book/**").hasRole(Role.BOOK_ADMIN)
            .anyRequest().authenticated();

        // Add JWT token filter
        http.addFilterBefore(
            jwtTokenFilter,
            UsernamePasswordAuthenticationFilter.class
        );
    }

    // Details omitted for brevity

}

正如您所看到的,这种方法简单而直接,但是它有一个缺点. 我们应用程序中的授权模式可能很复杂, 如果我们在一个地方定义所有的规则, it will become very big, complex, and hard to read. 因此,我通常更喜欢使用基于注释的配置.

Spring Security框架为web安全定义了以下注解:

  • @PreAuthorize supports Spring Expression Language 并用于提供基于表达式的访问控制 before executing the method.
  • @PostAuthorize supports Spring Expression Language 并用于提供基于表达式的访问控制 after 执行方法(提供访问方法结果的能力).
  • @PreFilter supports Spring Expression Language 并用于筛选集合或数组 before 根据我们定义的自定义安全规则执行方法.
  • @PostFilter supports Spring Expression Language 并用于筛选返回的集合或数组 after 根据我们定义的自定义安全规则执行方法(提供访问方法结果的能力).
  • @Secured doesn’t support Spring Expression Language 并用于指定方法上的角色列表.
  • @RolesAllowed doesn’t support Spring Expression Language and is the JSR 250’s equivalent annotation of the @Secured annotation.

这些注释默认是禁用的,可以在我们的应用程序中启用,如下所示:

@EnableWebSecurity
@EnableGlobalMethodSecurity(
    securedEnabled = true,
    jsr250Enabled = true,
    prePostEnabled = true
)
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    // Details omitted for brevity

}


securedEnabled = true enables @Secured annotation.
jsr250Enabled = true enables @RolesAllowed annotation.
prePostEnabled = true enables @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter annotations.

启用它们后,我们可以在API端点上执行基于角色的访问策略,如下所示:

@Api(tags = "UserAdmin")
@RestController @RequestMapping(path = "api/admin/user")
@RolesAllowed(Role.USER_ADMIN)
public class UserAdminApi {

	// Details omitted for brevity

}

@Api(tags = "Author")
@RestController @RequestMapping(path = "api/author")
public class AuthorApi {

	// Details omitted for brevity

	@RolesAllowed(Role.AUTHOR_ADMIN)
	@PostMapping
	public void create() { }

	@RolesAllowed(Role.AUTHOR_ADMIN)
	@PutMapping("{id}")
	public void edit() { }

	@RolesAllowed(Role.AUTHOR_ADMIN)
	@DeleteMapping("{id}")
	public void delete() { }

	@GetMapping("{id}")
	public void get() { }

	@GetMapping("{id}/book")
	public void getBooks() { }

	@PostMapping("search")
	public void search() { }

}

@Api(tags = "Book")
@RestController @RequestMapping(path = "api/book")
public class BookApi {

	// Details omitted for brevity

	@RolesAllowed(Role.BOOK_ADMIN)
	@PostMapping
	public BookView create() { }

	@RolesAllowed(Role.BOOK_ADMIN)
	@PutMapping("{id}")
	public void edit() { }

	@RolesAllowed(Role.BOOK_ADMIN)
	@DeleteMapping("{id}")
	public void delete() { }

	@GetMapping("{id}")
	public void get() { }

	@GetMapping("{id}/author")
	public void getAuthors() { }

	@PostMapping("search")
	public void search() { }

}

请注意,安全注释可以在类级别和方法级别上提供.

演示的示例很简单,并不代表真实的场景, 但是Spring Security提供了一组丰富的注释, 如果选择使用它们,还可以处理复杂的授权模式.

Role Name Default Prefix

In this separate subsection, 我想强调一个让很多新用户感到困惑的微妙细节.

Spring Security框架区分了两个术语:

  • Authority represents an individual permission.
  • Role represents a group of permissions.

两者都可以用一个接口来表示 GrantedAuthority 然后在Spring Security注释中使用Spring Expression Language进行检查,如下所示:

  • Authority: @PreAuthorize(“hasAuthority (EDIT_BOOK)”)
  • Role: @PreAuthorize(“hasRole (BOOK_ADMIN)”)

为了使这两个术语之间的区别更加明确,Spring Security框架添加了一个 ROLE_ prefix to the role name by default. 因此,不检查名为 BOOK_ADMIN, it will check for ROLE_BOOK_ADMIN.

就我个人而言,我发现这种行为令人困惑,并倾向于在我的应用程序中禁用它. 它可以在Spring Security配置中禁用,如下所示:

@EnableWebSecurity
公共类SecurityConfig扩展WebSecurityConfigurerAdapter

    // Details omitted for brevity

    @Bean
    GrantedAuthorityDefaults () {
        return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
    }

}

测试Spring安全JWT解决方案

在使用Spring Security框架时,通过单元测试或集成测试来测试我们的端点, we need to add spring-security-test dependency along with the spring-boot-starter-test. Our pom.xml build file will look like this:


    org.springframework.boot
    spring-boot-starter-test
    test
    
        
            org.junit.vintage
            junit-vintage-engine
        
    



    org.springframework.security
    spring-security-test
    test

这个依赖关系使我们能够访问一些注释,这些注释可用于向测试函数添加安全上下文.

These annotations are:

  • @WithMockUser 可以添加到测试方法中以模拟与模拟用户一起运行吗.
  • @WithUserDetails 是否可以添加到测试方法中以模拟运行 UserDetails returned from the UserDetailsService.
  • @WithAnonymousUser 可以添加到测试方法中以模拟使用匿名用户运行吗. 当用户希望以特定用户的身份运行大多数测试并覆盖一些匿名方法时,这很有用.
  • @WithSecurityContext determines what SecurityContext ,上面描述的所有三个注释都基于它. 如果我们有一个特定的用例,我们可以创建我们自己的注释 @WithSecurityContext to create any SecurityContext we want. 它的讨论超出了我们Spring安全性教程的范围, 请参考Spring Security文档了解更多细节.

对特定用户运行测试的最简单方法是使用 @WithMockUser annotation. 我们可以用它创建一个mock用户,并像下面这样运行测试:

@Test @WithMockUser(用户名= " customUsername@example.io", roles={"USER_ADMIN"})
public void test() {
	// Details omitted for brevity
}

不过,这种方法有几个缺点. First, the mock user doesn’t exist, and if you run the integration test, 哪个稍后从数据库查询用户信息, the test will fail. 的实例 org.springframework.security.core.userdetails.User 类,它是Spring框架的内部实现 UserDetails interface, and if we have our own implementation, this can cause conflicts later, during test execution.

如果前面的缺点阻碍了我们的应用程序,那么 @WithUserDetails annotation is the way to go. It is used when we have custom UserDetails and UserDetailsService implementations. It assumes that the user exists, 所以我们要么在数据库中创建实际的行要么提供 UserDetailsService mock instance before running tests.

我们可以这样使用这个注释:

@Test @WithUserDetails(“customUsername@example.io")
public void test() {
	// Details omitted for brevity
}

在我们的示例项目的集成测试中,这是首选的注释,因为我们有上述接口的自定义实现.

Using @WithAnonymousUser allows running as an anonymous user. 当您希望以特定用户运行大多数测试,但以匿名用户运行少数测试时,这尤其方便. For example, the following will run test1 and test2 test cases with a mock user and test3 with an anonymous user:

@SpringBootTest
@AutoConfigureMockMvc
@WithMockUser
WithUserClassLevelAuthenticationTests {

    @Test
    public void test1() {
        // Details omitted for brevity
    }

    @Test
    public void test2() {
        // Details omitted for brevity
    }

    @Test @WithAnonymousUser
    public void test3() throws Exception {
        // Details omitted for brevity
    }
}

征服Spring安全JWT学习曲线

In the end, 我想提一下,Spring Security框架可能不会赢得任何选美比赛,而且它肯定有一个陡峭的学习曲线. 我遇到过很多这样的情况:由于初始配置的复杂性,它被一些自己开发的解决方案所取代. 但是,一旦开发人员了解了它的内部结构并设法设置初始配置, 它变得相对简单易用.

In this Spring Security tutorial, 我试图演示配置的所有细微细节, 我希望这些例子对你们有用. 完整的代码示例,请参考我的Git库 sample Spring Security project.

Understanding the basics

  • What is Spring Security?

    Spring Security是一个功能强大且高度可定制的身份验证和授权框架. 它是保护基于spring的应用程序的事实上的标准.

  • 如何将Spring安全性与REST API一起使用?

    Out of the box, Spring Security提供基于会话的身份验证, 这是有用的经典MVC web应用程序, 但我们可以将其配置为支持REST api的基于jwt的无状态身份验证.

  • How secure is Spring Security?

    Spring Security is quite secure. 它很容易与基于spring的应用程序集成, 开箱即用,支持多种类型的身份验证, 并且能够进行声明性安全编程.

  • Why is Spring Security used?

    因为它与其他Spring生态系统无缝集成, 许多开发人员更喜欢重用现有的解决方案,而不是重新发明轮子.

  • What is JWT?

    JSON Web令牌(JWT)是一种编码信息的标准,可以作为JSON对象安全地传输.

  • How does JWT work with Spring Security?

    我们为身份验证公开了一个公共POST API, 并通过正确的证书, it will generate a JWT. 如果用户试图访问受保护的API,则只有在请求具有有效JWT时才允许访问. 验证将在Spring Security过滤器链中注册的过滤器中进行.

Hire a Toptal expert on this topic.
Hire Now
Ioram Gordadze's profile image
Ioram Gordadze

Located in Tbilisi, Georgia

Member since June 3, 2017

About the author

Ioram拥有计算机科学硕士学位和十多年的Java专业经验. 他专门研究企业级应用程序.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

WorkSpan

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Toptal Developers

Join the Toptal® community.