SpringBoot 学习笔记_安全管理 —— 基于数据库的认证

SpringBoot 学习笔记_安全管理 —— 基于数据库的认证

声明:

本次学习参考 《SpringBoot + Vue 开发实战》 · 王松(著) 一书。

本文的目的是记录我学习的过程和遇到的一些问题以及解决办法,其内容主要来源于原书。

如有侵权,请联系我删除

SpringBoot 安全管理 —— 基于数据库的认证

实际项目中,用户的基本认证信息都是存储在数据库中,因此需要从数据中获取数据进行认证。

设计数据表

CREATE DATABASE `security` DEFAULT CHARACTER 
SET utf8;
USE `security`;

# 创建用户表
CREATE TABLE USER (
	`id` INT ( 11 ) NOT NULL auto_increment,
	`username` VARCHAR ( 32 ) DEFAULT NULL,
	`password` VARCHAR ( 255 ) DEFAULT NULL,
	`enabled` TINYINT ( 1 ) NOT NULL DEFAULT 1,
	`locked` TINYINT ( 1 ) NOT NULL DEFAULT 0,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT charset = utf8;

# 创建角色表
CREATE TABLE role (
	`id` INT ( 11 ) NOT NULL auto_increment,
	`name` VARCHAR ( 32 ) DEFAULT NULL,
	`nameZH` VARCHAR ( 32 ) DEFAULT NULL,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;

# 创建关联表
CREATE TABLE user_role (
	`id` INT ( 11 ) NOT NULL auto_increment,
	`uid` INT ( 11 ) NOT NULL,
	`rid` INT ( 11 ) NOT NULL,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;

# 插入示例数据	(密文可以通过代码 Utils 类中的 EncryptBCrypt 方法获取)
INSERT INTO `USER` ( `id`, `username`, `password` )
VALUES
	( 1, 'root', '$2a$10$prmKKnrA38zwSXuK35P/Wejk2OxagVOb13c4OOnl3/5w4ft61g/fG' ),
	( 2, 'admin', '$2a$10$gSYELUIs8RzegyPGREIvWOUNXjuqBLk0qwmTSLRSEDDw1iwZIWhri' ),
	( 3, 'sang', '$2a$10$uurxv5sKDcOPImZJWJ9hcO2auxOu8k58i/MBVQ40gzE6WWYln8AOG' );
	
insert into `ROLE` ( `id`, `name`, `nameZH` )
VALUES
	(1, 'ROLE_dba', '数据库管理员'),	# 角色名默认前缀 ROLE_
	(2, 'ROLE_admin', '系统管理员'),
	(3, 'ROLE_user', '用户');
	
INSERT INTO `USER_ROLE` ( `id`, `uid`, `rid` )
VALUES
	(1, 1, 1), # root   数据库管理员
	(2, 1, 2), # root   系统管理员
	(3, 2, 2), # admin  系统管理员
	(4, 3, 3); # sang 用户

创建 SpringBoot 项目,添加依赖

<!--  添加 security 依赖  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--    添加 mybatis 依赖    -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

<!--    添加 mysql 依赖    -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<build>
	...
	<!-- 整合 MyMyBatis 配置 -->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
	...
</build>

配置数据库

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///security?serverTimezone=GMT

创建实体类

  • User

    public class User implements UserDetails {
        private Integer id;
        private String username;
        private String password;
        private Boolean enabled;
        private Boolean locked;
        private List<Role> roles;
    
        /**
         * 获取当前用户对象所具有的的角色信息
         * @return
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (Role role : roles){
                authorities.add(new SimpleGrantedAuthority(role.getName()));
            }
            return authorities;
        }
    
        /**
         * 获取当前用户对象的密码
         * @return
         */
        @Override
        public String getPassword() {
            return password;
        }
    
        /**
         * 获取当前用户对象的用户名
         * @return
         */
        @Override
        public String getUsername() {
            return username;
        }
    
        /**
         * 当前账户是否未过期
         * @return
         */
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 当前账户是否未锁定
         * @return
         */
        @Override
        public boolean isAccountNonLocked() {
            return !locked;
        }
    
        /**
         * 当前账户密码是否未过期
         * @return
         */
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 当前账户是否可用
         * @return
         */
        @Override
        public boolean isEnabled() {
            return enabled;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        //public Boolean getEnabled() {
        //    return enabled;
        //}
    
        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }
    
        public Boolean getLocked() {
            return locked;
        }
    
        public void setLocked(Boolean locked) {
            this.locked = locked;
        }
    
        public List<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(List<Role> roles) {
            this.roles = roles;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", enabled=" + enabled +
                    ", locked=" + locked +
                    ", roles=" + roles +
                    '}';
        }
    }
    
  • Role

    public class Role {
        private Integer id;
        private String name;
        private String nameZH;
        /* Setter & Getter */
    }
    

创建 Mapper 和 Service

@Repository
@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role> getUserRolesByUid(Integer id);
}
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper
namespace="org.sang.mapper.UserMapper">
    <select id="loadUserByUsername" resultType="org.sang.bean.User">
        select * from user where username=#{username}
    </select>

    <select id="getUserRolesByUid" resultType="org.sang.bean.Role">
        select * from role r , user_role ur where r.id = ur.rid and ur.uid = #{id}
    </select>
</mapper>
@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (user == null)
            throw new UsernameNotFoundException("User Not Found");
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

配置 Spring Security

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected  void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasRole("admin")
                .antMatchers("/db/**")
                .hasRole("dba")
                .antMatchers("/user/**")
                .hasRole("user")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

创建工具类

public class BCryptUtils {

    static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    /**
     * 加密 BCrypt	获取指定字符串对应的密文
     * @return
     */
    public static String EncryptBCrypt(String s){
        System.out.println(passwordEncoder.encode(s));
        return  passwordEncoder.encode(s);
    }
}

创建 Controller 测试

@RestController
public class LoginController {

    @Autowired
    UserService service;

    @GetMapping("/login")
    public String login(String username){
        UserDetails userDetails = service.loadUserByUsername(username);
        return userDetails.getUsername();
    }
}

启动项目,访问登录页面测试

localhost:8080

输入用户名密码进行测试

遇到的问题 1

  • 问题描述:

    Invalid bound statement (not found)

  • 问题原因:

    pom.xml 中遗漏了 MyBatis 配置

  • 解决方法:

    <build>
    	...
    	<!-- 整合 MyMyBatis 配置 -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
        </resources>
    	...
    </build>
    

遇到的问题 2

  • 问题描述:

    Illegal overloaded getter method with ambiguous type for property enabled in class class org.sang.bean.User ...

  • 问题原因:

    MyBatic 反射异常。 Java 内部 Boolean 类型属性的 getter 方法默认为 is 开头或者 get 开头的。

    我们自定义的 User 类继承了 UserDetails 接口,实现了 isEnabled 方法。

    而在使用快捷键生成 setter 和 getter 方法时,又自动生成了 getEnabled 方法。

    此时,Java 会将这两个方法都认为是 bean 的属性封装,在反射的时候,就不知道使用哪个了,所以抛出了异常。

  • 解决方法:

    屏蔽/删除快捷键自动生成的 getEnabled 方法

遇到的问题 3

  • 问题描述:

    Encoded password does not look like BCrypt

  • 问题原因:

    用户名/密码校验失败。 admin 用户的密码在数据库中首次创建时输入了 admin 明文,而登录校验时使用了 BCrypt 加密规则校验。

  • 解决方法:

    将数据库中存储的明文密码修改成对应密文。

    可以通过上面工具类中的 EncryptBCrypt 方法对明文密码进行 BCrypt 规则的加密,并将结果更新到数据库。

    BCrypt 是强哈希函数,明文密码相同,但加密后的密文并不一定一样,需要注意

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HolaSecurity

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值