美文网首页Spring-Bootspring boot
(译)Spring Boot + Spring Security

(译)Spring Boot + Spring Security

作者: StephenRo | 来源:发表于2019-12-10 12:16 被阅读0次

    原文链接: 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并加一些功能,比如用户注册与用户登录。

    相关文章

      网友评论

        本文标题:(译)Spring Boot + Spring Security

        本文链接:https://www.haomeiwen.com/subject/tzowgctx.html