Spring-Bootspring boot

(译)Spring Boot + Spring Security

2019-12-10  本文已影响0人  StephenRo

原文链接: https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-1/

欢迎来到令人激动的系列博客第一部分,在此你将学会如何构建一个端到端的全栈投票APP,有点类似于Twitter Polls。

我们将使用Spring Boot来构建服务端接口,结合Spring Security与JWT来进行认证,使用MySQL数据库用来存储数据。

使用React构建前端程序,使用 Ant Design 来设计我们的用户界面。

在本系列教程的最后,你将从0到1构建一个功能完备的投票应用。

本项目的完整源码托管在Github,如果你碰到困难,可随时参考。

下面是我们应用最终版本的截图


spring-boot-spring-security-jwt-mysql-react-full-stack-polling-app.jpg

看起来不错吧,那让我们从零开始构建吧。

在本文中,我们将使用Spring Boot来构建后端项目,随后定义基础实体与数据仓库。

使用Spring Boot创建后端应用

让我们使用Spring Initialzr web tool来创建项目

  1. 打开https://start.spring.io/

  2. 输入polls在Artifact栏

  3. 在依赖块添加Web,JPA,MySQL和Security的依赖

  4. 点击Generate Project来生成并下载项目


    spring-boot-spring-security-mysql-jwt-react-polling-ap.jpg

等项目下载完成后解压,导入至你喜爱的IDE中,项目结构看起来是这样的-


spring-security-mysql-jwt-polling-app-directory-structure.jpg

增加额外的依赖

我们的项目还需要一些额外的依赖。从根目录打开 pom.xml并加入以下依赖块 -

<!-- For Working with Json Web Tokens (JWT) -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

<!-- For Java 8 Date/Time Support -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

配置Server,数据库,Hibernate和Jackson

让我们开始配置Server,数据库,Hibernate和Jackson,增加以下配置到src/main/resources/application.properties文件 -

## Server Properties
server.port= 5000
​
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url= jdbc:mysql://localhost:3306/polling_app?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username= root
spring.datasource.password= callicoder
​
## Hibernate Properties
​
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
​
## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG
​
# Initialize the datasource with available DDL and DML scripts
spring.datasource.initialization-mode=always
​
## Jackson Properties
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false
spring.jackson.time-zone= UTC

以上配置都是看名称就可以大概知晓含义的。我把hibernate的ddl-auto属性设为了update,将会根据 实体 自动在数据库中创建/更新表的结构。

Jackson的WRITE_DATES_AS_TIMESTAMPS属性是用来使Date/Time的值序列化为 ISO date/time 字符串格式,而不是Java 8 Date/Time的格式。

在做下一步之前,请先在MySQL中创建polling_app数据库并把spring.datasource.usernamespring.datasource.password的值替换成你自己的。

配置Java8 Date/Time - UTC转换器

我们将在领域模型中使用Java 8 Date/Time,需要注册 JPA2.1 转换器,以便将领域模型中的所有Java 8 Date / Time字段持久化为SQL类型后再将它们保存在数据库中。

此外,我们还需要在我们的应用中设置默认时区为 UTC。

打开PollsApplication.java,添加一下修改-

package com.example.polls;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
​
import javax.annotation.PostConstruct;
import java.util.TimeZone;
​
@SpringBootApplication
@EntityScan(basePackageClasses = { 
        PollsApplication.class,
        Jsr310JpaConverters.class 
})
public class PollsApplication {

    @PostConstruct
    void init() {
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }

    public static void main(String[] args) {
        SpringApplication.run(PollsApplication.class, args);
    }
}

创建领域模型

我们的应用允许用户注册,登入。每个用户有一个或多个角色。用户有什么角色决定了他能访问那些特定的资源与否。

在这一节,我们创建UserRole对象,所有的领域模型都放在名为model的包下,包名为 com.example.polls

1. User

用户模型包含以下几个项 -

1.id: 主键

  1. username: 一个唯一的名字
  2. email: 一个唯一的邮箱
  3. password: 加密后被存储的密码
  4. roles:一个角色集(与Role是多对多关系)
package com.example.polls.model;
​
import com.example.polls.model.audit.DateAudit;
import org.hibernate.annotations.NaturalId;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
​
@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(columnNames = {
            "username"
        }),
        @UniqueConstraint(columnNames = {
            "email"
        })
})
public class User extends DateAudit {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 40)
    private String name;

    @NotBlank
    @Size(max = 15)
    private String username;

    @NaturalId
    @NotBlank
    @Size(max = 40)
    @Email
    private String email;

    @NotBlank
    @Size(max = 100)
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_roles",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles = new HashSet<>();

    public User() {

    }

    public User(String name, String username, String email, String password) {
        this.name = name;
        this.username = username;
        this.email = email;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

User类继承了DateAudit类,我们稍后再定义。DateAudit类有 createdAtupdateAt这2个字段 主要用于记录创建时间和更新时间。

2. Role

Role类包含了idname字段。name字段是一个枚举。我们有一组预定义的角色,所以我们把角色定义为枚举。

package com.example.polls.model;
​
import org.hibernate.annotations.NaturalId;
import javax.persistence.*;
​
@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    @NaturalId
    @Column(length = 60)
    private RoleName name;

    public Role() {

    }

    public Role(RoleName name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public RoleName getName() {
        return name;
    }

    public void setName(RoleName name) {
        this.name = name;
    }
}

RoleName enum

package com.example.polls.model;
​
public enum  RoleName {
    ROLE_USER,
    ROLE_ADMIN
}

我定义了2个角色为ROLE_USERROLE_ADMIN,你可以根据项目需求自我增删角色。

3. DateAudit

OK,让我们开始定义DateAudit。他有createdAtupdatedAt字段。其他的类需要这2个字段可以很简单的通过继承来获得。

当我们持久化实体时,JPA的AuditingEntityListener可以自动往createdAtupdatedAt字段填充值。

以下是一个完整的DateAudit类,我们在model包下再创建一个audit包来存放相关的模型。

package com.example.polls.model.audit;
​
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.time.Instant;
​
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
        value = {"createdAt", "updatedAt"},
        allowGetters = true
)
public abstract class DateAudit implements Serializable {

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private Instant createdAt;

    @LastModifiedDate
    @Column(nullable = false)
    private Instant updatedAt;

    public Instant getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Instant createdAt) {
        this.createdAt = createdAt;
    }

    public Instant getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Instant updatedAt) {
        this.updatedAt = updatedAt;
    }
}

为了开始JPA Auditing,我们需要增加@EnableJpaAuditing注解在我们的主类上或其他任何配置类上。

让我们创建一个AuditingConfig配置类并在类头上加上@EnableJpaAuditing注解。

我们单独创建这个类是因为我们后续会增加更多的Auditing相关的配置类。

我们把所有配置相关的类放在config包下,现在就让我们去com.example.polls下创建config包并创建AuditingConfig

package com.example.polls.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class AuditingConfig {
    // That's all here for now. We'll add more auditing configurations later.
}

创建可访问UserRole的 Repositories

现在我们已经定义好了领域模型,下面就可以定义Repositories,Repositories的作用是持久化数据和查询数据。

所有的Repositories应该在名为repository的包下。现在就让我们去com.example.polls下创建 repository包吧。

1. UserRepository

以下就是UserRepository的完整的代码,他继承了Spring Data JPA的JpaRepository接口。

package com.example.polls.repository;

import com.example.polls.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
      Optional<User> findByEmail(String email);

      Optional<User> findByUsernameOrEmail(String username, String email);

      List<User> findByIdIn(List<Long> userIds);

      Optional<User> findByUsername(String username);

      Boolean existsByUsername(String username);

      Boolean existsByEmail(String email);
}

2. RoleRepository

以下就是RoleRepository的接口,他包含了一个方法:通过RoleName查询Role。

package com.example.polls.repository;

import com.example.polls.model.Role;
import com.example.polls.model.RoleName;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
      Optional<Role> findByName(RoleName roleName);
}

检测当前配置并启动程序

在创建以上模型,仓库和配置后,我们的项目结构看起来应该如下


spring-boot-spring-security-jwt-user-role-directory-structure-part-1.jpg

你可以在项目根目录下通过键入以下命令来启动项目

mvn spring-boot:run

检查日志并确保服务已成功启动

2018-02-24 22:40:44.998  INFO 33708 --- Tomcat started on port(s): 5000 (http)
2018-02-24 22:40:45.008  INFO 33708 --- Started PollsApplication in 7.804 seconds (JVM running for 27.193)

创建默认角色

我们得有一些预定义的角色,才得以在用户注册完赋予它ROLE_USER的角色。
我们得在MySQL中执行以下sql来创建这些初始的角色。

INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');

下一步是什么?

在本系列文章的下一章,我们将学习在项目中如何配置Spring Security并加一些功能,比如用户注册与用户登录。

上一篇下一篇

猜你喜欢

热点阅读