Spring Boot 之 Spring Security基于数据库的认证

Spring Security基于数据库的认证

上文向大家介绍的认证数据都是定义在内存中的,在真实项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证。本节将向读者介绍如何使用数据库中的数据进行认证和授权。

1. 设计数据表

首先需要设计一个基本的用户角色表,如图所示。一共三张表,分别是用户表、角色表以及用户角色关联表。

在这里插入图片描述

为了方便测试,预置几条测试数据,如图所示。

mysql> select * from role;
+----+------------+--------------------+
| id | name       | nameZh             |
+----+------------+--------------------+
|  1 | ROLE_dba   | 数据库管理员       |
|  2 | ROLE_admin | 系统管理员         |
|  3 | ROLE_user  | 用户               |
+----+------------+--------------------+
3 rows in set (0.00 sec)

mysql> select * from user;
+----+----------+--------------------------------------------------------------+--------+--------+
| id | username | password                                                     | enable | locked |
+----+----------+--------------------------------------------------------------+--------+--------+
|  1 | root     | $2a$10$Y7TCbZ.De3suSD.4boCArugDit4hyOgkywUMuLUquc7OZuL6sTAR. |      1 |      0 |
|  2 | admin    | $2a$10$Y7TCbZ.De3suSD.4boCArugDit4hyOgkywUMuLUquc7OZuL6sTAR. |      1 |      0 |
|  3 | suo      | $2a$10$Y7TCbZ.De3suSD.4boCArugDit4hyOgkywUMuLUquc7OZuL6sTAR. |      1 |      0 |
+----+----------+--------------------------------------------------------------+--------+--------+
3 rows in set (0.00 sec)

mysql> select * from user_role;
+----+------+------+
| id | uid  | rid  |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    1 |    2 |
|  3 |    2 |    2 |
|  4 |    3 |    3 |
+----+------+------+
4 rows in set (0.00 sec)

注意:

角色名有一个默认的前缀"ROLE_"

2.创建项目

Mybatis灵活,JPA便利,本案例选择前者,因此创建Spring Boot Web添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*</include>
            </includes>
        </resource>
    </resources>
</build>

3. 配置数据库

在application.properties中进行数据库连接配置:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://120.55.61.170:3306/fristweb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=fristweb
spring.datasource.password=dTNFJW4B5MrwT4KS
spring.datasource.initialSize= 5
spring.datasource.minIdle=5
spring.datasource.maxActive= 20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true

mybatis.type-aliases-package=suohechuan.testforever.model
mybatis.mapper-locations=classpath:mapper/*.xml

4. 创建实体类

分别创建角色表和用户表对应的实体类,代码如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    private Integer id;
    private String name;
    private String nameZh;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enable;
    private Boolean locked;
    private List<Role> roles;

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public String getPassword() {
        return password;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

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

    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

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

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

}

代码解释:

  • 用户 实体类需要实现UserDetails接口,并实现该接口中的7个方法,如表所示。

    方法名解释
    getAuthorities();获取当前用户对象所具有的角色信息
    getPassword();获取当前用户对象的密码
    getUsername();获取当前用户对象的用户名
    isAccountNonExpired();当前账户是否未过期
    isAccountNonLocked();当前账户是否未锁定
    isCredentialsNonExpired();当前账号密码是否未过期
    idEnabled();当前账号是否可用
    • 用户根据实际情况设置这7个方法的返回值。因为默认情况下不需要开发者自己进行密码角色等信息的比对,开发者只需要提供相关信息即可,例如getPassword()方法返回的密码和用户输入的登录密码不匹配,会自动抛出BadCredentialsException异常,isAccountNonExpired()方法返回了false, 会自动抛出AccountExpiredException异常,因此对开发者而言,只需要按照数据库中的数据在这里返回相应的配置即可。本案例因为数据库中只有enabled和locked字段,故账户未过期和密码未过期两个方法都返回true。
    • getAuthorities()方法用 来获取当前用户所具有的角色信息,本案例中,用户所具有的角色存储在roles属性中,因此该方法直接遍历roles 属性,然后构造SimpleGrantedAuthority集合并返回。

5. 创建UserService

接下来创建UserService,代码如下:

@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.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

代码解释:

  • 定义UserService实现UserDetailService接口,并实现该接口中的loadUserByUsername方法,该方法的参数就是用户登录时输入的用户名,通过用户名去数据库中查找用户,如果没有查找到用户,就抛出一个账户不存在的异常,如果查找到了用户,就继续查找该用户所具有的角色信息,并将获取到的user对象返回,再由系统提供的DaoAuthenticationProvider类去比对密码是否正确。
  • loadUserByUsername 方法将在用户登录时自动调用。

当然,这里还涉及UserMapper和UserMapper.xml,相关源码如下:

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);

    List<Role> getUserRolesByUid(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
 namespace:必须有值,自定义的唯一字符串 
 推荐使用:dao 接口的全限定名称 
-->
<mapper namespace="suohechuan.testforever.UserMapper">
    <select id="loadUserByUsername" resultType="suohechuan.testforever.model.User">
        select *
        from user
        where username = #{username}
    </select>
    <select id="getUserRolesByUid" resultType="suohechuan.testforever.model.Role">
        select *
        from role r,
             user_role ur
        where r.id = ur.rid and ur.uid =#{id}
    </select>
</mapper>

6. 配置Spring Security

接下来对Spring Security进行配置,代码如下:

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(10);
    }

    @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();
    }
}

这里大部分配置和Security上节介绍的一致,唯一不同的是没有配置内存用户,而是将刚刚创建好的UserService配置到AuthenticationManagerBuilder中。配置完成后,接下来就可以创建Controller进行测试了,测试方式与Security上节致,这里不再赘述。

@RestController
public class HelloController {
    @GetMapping("/admin/hello")

    public String admin() {
        return "hello admin!";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user! ";
    }

    @GetMapping(" /db/hello")
    public String dba() {
        return "hello dba!";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello!";
    }
}
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值