4.5、定制化security的数据库

一、使用H2内嵌数据库

1.1、添加依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

1.2、添加依赖

spring:
  datasource:
    initialization-mode: embedded
    driver-class-name: org.h2.Driver
    password: ''
    # 数据库连接URL,为了兼容Mysql,添加;MODE=MySQL
    # ;DATABASE_TO_LOWER=TRUE 让表名转为小写
    # ;CASE_INSENSITIVE_IDENTIFIERS=TRUE 不区分大小写
    # ;DB_CLOSE_DELAY=-1 不自动关闭数据库连接
    url: jdbc:h2:mem:test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1
    # 数据库用户名
    username: sa
  h2:
    console:
      # 显示 H2 嵌入式 UI 管理界面
      enabled: true
      # 访问 H2 管理界面的路由
      path: /h2-console
      settings:
        # 禁止输出trace信息
        trace: false
        # 禁止远程访问 H2 管理界面
        web-allow-others: false

在这里插入图片描述

1.3、配置使用基于H2的内存数据库

注入DataSource
在这里插入图片描述

配置使用基于jdbc的存储方式

在这里插入图片描述
配置h2的浏览器访问路径不被security拦截
在这里插入图片描述

1.4、二次启动项目出现报错问题(未整理完善)

这个配置可能在第二次启动h2的时候再此去创建了数据库表
在这里插入图片描述

二、定制化数据库

2.1、schema.sql(在引入jpa后未使用,jpa可自动根据data.sql创建表结构)

CREATE TABLE IF NOT EXISTS mooc_roles (
                                          id BIGINT NOT NULL AUTO_INCREMENT,
                                          role_name VARCHAR(50) NOT NULL,
                                          PRIMARY KEY (id),
                                          CONSTRAINT uk_mooc_roles_role_name UNIQUE (role_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mooc_users (
                                          id BIGINT NOT NULL AUTO_INCREMENT,
                                          account_non_expired BIT NOT NULL,
                                          account_non_locked BIT NOT NULL,
                                          credentials_non_expired BIT NOT NULL,
                                          email VARCHAR(254) NOT NULL,
                                          enabled BIT NOT NULL,
                                          mobile VARCHAR(11) NOT NULL,
                                          name VARCHAR(50) NOT NULL,
                                          password_hash VARCHAR(80) NOT NULL,
                                          username VARCHAR(50) NOT NULL,
                                          PRIMARY KEY (id),
                                          CONSTRAINT uk_mooc_users_username UNIQUE (username),
                                          CONSTRAINT uk_mooc_users_mobile UNIQUE (mobile),
                                          CONSTRAINT uk_mooc_users_email UNIQUE (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS mooc_users_roles (
                                                user_id BIGINT NOT NULL,
                                                role_id BIGINT NOT NULL,
                                                PRIMARY KEY (user_id, role_id),
                                                CONSTRAINT fk_users_roles_user_id_mooc_users_id FOREIGN KEY (user_id) REFERENCES mooc_users (id),
                                                CONSTRAINT fk_users_roles_role_id_mooc_roles_id FOREIGN KEY (role_id) REFERENCES mooc_roles (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2、data.sql

insert into mooc_users(id, username, `name`, mobile, password_hash, enabled, account_non_expired, account_non_locked, credentials_non_expired, email)
values (1, 'user', 'Zhang San', '13012341234', '{bcrypt}$2a$10$jhS817qUHgOR4uQSoEBRxO58.rZ1dBCmCTjG8PeuQAX4eISf.zowm', 1, 1, 1, 1, 'zhangsan@local.dev'),
       (2, 'old_user', 'Li Si', '13812341234', '{SHA-1}7ce0359f12857f2a90c7de465f40a95f01cb5da9', 1, 1, 1, 1, 'lisi@local.dev');
insert into mooc_roles(id, role_name) values (1, 'ROLE_USER'), (2, 'ROLE_ADMIN');
insert into mooc_users_roles(user_id, role_id) values (1, 1), (1, 2), (2, 1);

2.3、User

在这个类中需要注意lombok的使用:

  1. 对于基础类型的boolean,“enabled”lombok会自动转成isEnabled
  2. 对于默认值,可以使用@Builder.Default
  3. 对于@with注解,lombok会对使用with复制返回一个新的对象,参见UserDetailsPasswordServiceImpl
package com.moss.uaa_security.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.moss.uaa_security.annotation.ValidEmail;
import lombok.*;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.Set;

/**
 * 用户实体类,实现了 UserDetails 接口
 */
@With
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode
@Entity
@Table(name = "mooc_users")
public class User implements UserDetails, Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 自增长 ID,唯一标识
     */
    @Getter
    @Setter
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 用户名
     */
    @Getter
    @Setter
    @NotNull
    @Size(max = 50)
    @Column(length = 50, unique = true, nullable = false)
    private String username;

    /**
     * 手机号
     */
    @Getter
    @Setter
    @NotNull
    @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$")
    @Size(min = 11, max = 11)
    @Column(length = 11, unique = true, nullable = false)
    private String mobile;

    /**
     * 姓名
     */
    @Getter
    @Setter
    @NotNull
    @Size(max = 50)
    @Column(length = 50)
    private String name;

    /**
     * 是否激活,默认激活
     */
    @Builder.Default
    @Setter
    @NotNull
    @Column(nullable = false)
    private Boolean enabled = true;

    /**
     * 账户是否未过期,默认未过期
     */
    @Builder.Default
    @Setter
    @NotNull
    @Column(name = "account_non_expired", nullable = false)
    private Boolean accountNonExpired = true;

    /**
     * 账户是否未锁定,默认未锁定
     */
    @Builder.Default
    @Setter
    @NotNull
    @Column(name = "account_non_locked", nullable = false)
    private Boolean accountNonLocked = true;

    /**
     * 密码是否未过期,默认未过期
     */
    @Builder.Default
    @Setter
    @NotNull
    @Column(name = "credentials_non_expired", nullable = false)
    private Boolean credentialsNonExpired = true;

    /**
     * 密码哈希
     */
    @Getter
    @Setter
    @JsonIgnore
    @NotNull
    @Size(min = 40, max = 80)
    @Column(name = "password_hash", length = 80, nullable = false)
    private String password;

    /**
     * 电邮地址
     */
    @Getter
    @Setter
    @ValidEmail
    @Size(min = 5, max = 254)
    @Column(length = 254, unique = true, nullable = false)
    private String email;

    /**
     * 角色列表,使用 Set 确保不重复
     */
    @Getter
    @Setter
    @JsonIgnore
    @ManyToMany
    @Fetch(FetchMode.JOIN)
    @JoinTable(
            name = "mooc_users_roles",
            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
    private Set<Role> authorities;

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

2.4、Role

package com.moss.uaa_security.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

/**
 * 角色实体类,实现 GrantedAuthority 接口
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "mooc_roles")
public class Role implements GrantedAuthority, Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 自增长 ID,唯一标识
     */
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    /**
     * 角色名称,有唯一约束,不能重复
     */
    @NotNull
    @Size(max = 50)
    @Column(name = "role_name", unique = true, nullable = false, length = 50)
    private String authority;
}

2.5、UserRepo

package com.moss.uaa_security.repository;

import com.moss.uaa_security.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

/**
 * 用户对象repository
 *
 * @author lwj
 */
@Repository
public interface UserRepo extends JpaRepository<User, Long> {

    Optional<User> findOptionalByUsername(String username);

}

2.6、RoleRepo

package com.moss.uaa_security.repository;

import com.moss.uaa_security.domain.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleRepo extends JpaRepository<Role, Long> {

}

2.7、application.yml

logging:
  level:
    org:
      springframework:
        security: debug
    com:
      moss: debug
  pattern:
    console: '%clr(%d{E HH:mm:ss.SSS}){blue} %clr(%-5p) %clr(${PID}){faint} %clr(---){faint}
                %clr([%8.15t]){cyan} %clr(%-40.40logger{0}){blue} %clr(:){red} %clr(%m){faint}%n'

spring:
  messages:
    #    always-use-message-format: false
    basename: messages
    encoding: UTF-8
  #    fallback-to-system-locale: true
  #    use-code-as-default-message: false
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false
  datasource:
    # 在使用内存数据库的时候启动初始化脚本 /resource/data.sql
    initialization-mode: embedded
    driver-class-name: org.h2.Driver
    password: ''
    # 数据库连接URL,为了兼容Mysql,添加;MODE=MySQL
    # ;DATABASE_TO_LOWER=TRUE 让表名转为小写
    # ;CASE_INSENSITIVE_IDENTIFIERS=TRUE 不区分大小写
    # ;DB_CLOSE_DELAY=-1 不自动关闭数据库连接
    # mem:test: 基于内存的数据库; ~/test:基于文件的数据库
    url: jdbc:h2:mem:test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1
    # 数据库用户名
    username: sa
  h2:
    console:
      # 显示 H2 嵌入式 UI 管理界面
      enabled: true
      # 访问 H2 管理界面的路由
      path: /h2-console
      settings:
        # 禁止输出trace信息
        trace: false
        # 禁止远程访问 H2 管理界面
        web-allow-others: false

  jpa:
    database: h2
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
#  profiles:
#    active: dev

server:
  error:
    whitelabel:
      enabled: true
  servlet:
    encoding:
      force: true

三、定制化登录逻辑

3.1、实现UserDetailsService接口

package com.moss.uaa_security.security.userdetails;

import com.moss.uaa_security.repository.UserRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @author 自定义获取用户信息实现类
 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepo userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepo.findOptionalByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("未找到用户名为:" + username + "的用户"));
    }
}

3.2、配置

在这里插入图片描述

3.3、测试

四、实现登录后的密码升级

4.1、配置密码转换器

在这里插入图片描述

4.2、实现UserDetailsPasswordService

package com.moss.uaa_security.security.userdetails;

import com.moss.uaa_security.repository.UserRepo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @author 自定义获取用户信息实现类
 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepo userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepo.findOptionalByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("未找到用户名为:" + username + "的用户"));
    }
}

4.3、测试

old_user登录前
在这里插入图片描述
使用页面登录
在这里插入图片描述
old_user登录后
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值