SpringBoot REST API + JWT + Spring Security教程

18-11-25 banq
              

如果您是JWTs Token的新手,我们将学习如何使用Spring安全性和JWT(JSON Web令牌)保护Spring启动REST API。

首先从Github Spring Boot REST API克隆以下springBoot项目,步骤:

  1. 添加Maven依赖项。
  2. 更新现有用户模型
  3. 更新用户存储库
  4. 使用继承Spring UserDetailsS​​ervice实现UserDetailService
  5. 实现JWTAuthenticationFilter
  6. 实现JWTAuthorizationFilter
  7. 覆盖Spring Boot默认的Web安全配置
  8. 构建并运行项目。
  9. 测试

添加所需的Maven依赖关系

我们需要为现有的项目pom添加两个依赖项

  1. spring-boot-starter-security
  2. java-jwt

<!-- Spring Boot Security dependency-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Java JWT dependency-->
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>3.4.1</version>
</dependency>

更新现有用户模型

接下来使用新属性用户名和密码更新现有用户模型,如下所示:

@Column(name = "username", nullable = false, unique = true)
private String username;

@Column(name = "password", nullable = false)
private String password;

更新现有的用户存储库

使用findByUsername方法更新现有的用户存储库。

public interface UserRepository extends JpaRepository<User, Long> {

  /**
   * Find by username user.
   *
   * @param username the username
   * @return the user
   */
  User findByUsername(String username);
}

使用继承Spring UserDetailsS​​ervice实现UserDetailService

为了使Spring Security能够认证用户详细信息,我们需要实现UserDetailService并覆盖loadUserByUsername以根据数据库验证用户详细信息。

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    private UserRepository userRepository;

  /**
   * Instantiates a new User detail service.
   *
   * @param userRepository the user repository
   */
  public UserDetailServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userDetails = userRepository.findByUsername(username);
        if(userDetails == null){
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(userDetails.getUsername(),userDetails.getPassword(), new ArrayList<>());
    }
}

实现JWTAuthenticationFilter:

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  public static final String SECRET = "SecretKeyToGenJWTs";
  public static final long EXPIRATION_TIME = 864_000_000; // 10 days
  public static final String TOKEN_PREFIX = "Bearer ";
  public static final String HEADER_STRING = "Authorization";

  private AuthenticationManager authenticationManager;

  public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
  }

  @Override
  public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    try {

      com.staxrt.tutorial.model.User loginUser = new ObjectMapper().readValue(request.getInputStream(), com.staxrt.tutorial.model.User.class);

      return authenticationManager.authenticate(
          new UsernamePasswordAuthenticationToken(
              loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()));
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  protected void successfulAuthentication(
      HttpServletRequest request,
      HttpServletResponse response,
      FilterChain chain,
      Authentication authResult)
      throws IOException, ServletException {
    String token =
        JWT.create()
            .withSubject(
                ((User) authResult.getPrincipal()).getUsername()) // Payload register sub claim
            .withExpiresAt(
                new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // JWT token validity time
            .sign(Algorithm.HMAC512(SECRET.getBytes())); // JWT Signature
    response.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
  }
}

实现JWTAuthorizationFilter

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

  public static final String SECRET = "SecretKeyToGenJWTs";
  public static final long EXPIRATION_TIME = 864_000_000; // 10 days
  public static final String TOKEN_PREFIX = "Bearer ";
  public static final String HEADER_STRING = "Authorization";

  private AuthenticationManager authenticationManager;

  public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
  }

  @Override
  public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    try {

      com.staxrt.tutorial.model.User loginUser = new ObjectMapper().readValue(request.getInputStream(), com.staxrt.tutorial.model.User.class);

      return authenticationManager.authenticate(
          new UsernamePasswordAuthenticationToken(
              loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()));
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  protected void successfulAuthentication(
      HttpServletRequest request,
      HttpServletResponse response,
      FilterChain chain,
      Authentication authResult)
      throws IOException, ServletException {
    String token =
        JWT.create()
            .withSubject(
                ((User) authResult.getPrincipal()).getUsername()) // Payload register sub claim
            .withExpiresAt(
                new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // JWT token validity time
            .sign(Algorithm.HMAC512(SECRET.getBytes())); // JWT Signature
    response.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
  }
}

覆盖Spring Boot默认的Web安全配置

这样我们可以将自己的身份认证逻辑注入JWT toknbase

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

  public static final String SIGN_UP_URL = "/api/v1/users";

  @Autowired private UserDetailServiceImpl userDetailService;

  @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;

  public WebSecurity(
      UserDetailServiceImpl userDetailService, BCryptPasswordEncoder bCryptPasswordEncoder) {
    this.userDetailService = userDetailService;
    this.bCryptPasswordEncoder = bCryptPasswordEncoder;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.cors()
        .and()
        .csrf()
        .disable()
        .authorizeRequests() // Add a new custom security filter
        .antMatchers(HttpMethod.POST, SIGN_UP_URL)
        .permitAll() // Only Allow Permission for create user endpoint
        .anyRequest()
        .authenticated()
        .and()
        .addFilter(this.getJWTAuthenticationFilter()) // Add JWT Authentication Filter
        .addFilter(
            new JWTAuthorizationFilter(authenticationManager())) // Add JWT Authorization Filter
        .sessionManagement()
        .sessionCreationPolicy(
            SessionCreationPolicy.STATELESS); // this disables session creation on Spring Security
  }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration(
        "/**",
        new CorsConfiguration()
            .applyPermitDefaultValues()); // Allow/restrict our CORS permitting requests from any
                                          // source
    return source;
  }

  public JWTAuthenticationFilter getJWTAuthenticationFilter() throws Exception {
    final JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
    filter.setFilterProcessesUrl("/api/v1/auth/login"); // override the default spring login url
    return filter;
  }
}

构建并运行项目

mvn package

java -jar target/spring-boot-rest-api-auth-jwt-tutorial-0.0.1-SNAPSHOT.jar

或者,您可以在不使用package 的情况下运行应用程序 -

mvn spring-boot:run

运行http://localhost:8080.

测试:

POST http://localhost:8080/api/v1/users

Request
{
   "username": "givantha90",
   "password": "welcome@123",
    "firstName": "Givantha",
    "lastName": "Kalansuriya",
    "email": "givanhta@gmail.com",
    "createdBy": "Givantha",
    "updatedBy": "Givantha"
}

Resonse
{
    "id": 13,
    "username": "givantha9110",
    "firstName": "Givantha",
    "lastName": "Kalansuriya",
    "email": "givanhta@gmail.com",
    "createdAt": "2018-11-24T15:20:19.463+0000",
    "createdBy": "Givantha",
    "updatedAt": "2018-11-24T15:20:19.463+0000",
    "updatedBy": "Givantha"
}

POST  http://localhost:8080/api/v1/auth/login

Request

{
    "username": "givantha12",
    "password": "welcome@123"
}