Spring Security使用详解3(基于数据库的用户角色配置)

Stella981
• 阅读 569

之前的文章样例中,认证数据都是定义在内存里。而在真实项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证。本文通过样例进行演示。

三、基于数据库的用户角色配置

1、添加依赖、配置数据库

本次样例使用 MyBatis 来操作数据库,首先在项目中添加 MyBatis 相关依赖,并进行数据库连接配置。

(1)在pom文件添加依赖:

<!-- MyBatis依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
 
<!-- 数据库驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
 
<!-- 数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>
  • mybatis-spring-boot-starter:MyBatis 依赖
  • mysql-connector-java:MySQL 数据库驱动
  • druid:Druid 是阿里巴巴开发的号称为监控而生的数据库连接池,也是目前最好的数据库连接池。

(2)接着在 application.properties 中配置数据库连接信息: 

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/hangge?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=hangge1234

2、创建数据表

(1)首先创建相关的用户角色表,共三张:分别是用户表、角色表以及用户角色关联表。

Spring Security使用详解3(基于数据库的用户角色配置)

(2)然后在表中添加一些测试数据。注意:角色名需要有一个默认的前缀“ROLE_”

Spring Security使用详解3(基于数据库的用户角色配置)

3、创建实体类

(1)首先创建一个角色表对应的实体类。

@Setter
@Getter
@NoArgsConstructor
public class Role {
    private Integer id;
    private String name;
    private String nameZh;
}

(2)接着创建用户表对应的实体类。用户实体类需要实现 UserDetails 接口,并实现该接口中的 7 个方法:

  • getAuthorities():获取当前用户对象所具有的角色信息
  • getPassword():获取当前用户对象的密码
  • getUsername():获取当前用户对象的用户名
  • isAccountNonExpired():当前账户是否未过期
  • isAccountNonLocked():当前账户是否未锁定
  • isCredentialsNonExpired():当前账户密码是否未过期
  • isEnabled():当前账户是否可用

(1)用户根据实际情况设置这 7 个方法的返回值。默认情况下不需要开发者自己进行密码角色等信息的比对,开发者只需要提供相关信息即可,例如:

  • getPassword() 方法返回的密码和用户输入的登录密码不匹配,会自动抛出 BadCredentialsException 异常
  • isAccountNonLocked() 方法返回了 false,会自动抛出 AccountExpiredException 异常。
  • 本案例因为数据库中只有 enabled 和 locked 字段,故账户未过期和密码未过期两个方法都返回 true.

(2)getAuthorities 方法用来获取当前用户所具有的角色信息,本案例中,用户所具有的角色存储在 roles 属性中,因此该方法直接遍历 roles 属性,然后构造 SimpleGrantedAuthority 集合并返回。 

@NoArgsConstructor
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;
 
    @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 String getPassword() {
        return password;
    }
 
    @Override
    public String getUsername() {
        return username;
    }
 
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
 
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
 
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
 
    @Override
    public boolean isEnabled() {
        return enabled;
    }
 
    /** get、set 方法 **/
 
    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 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;
    }
}

4、创建数据库访问层

(1)首先创建 UserMapper 接口: 

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role> getUserRolesByUid(Integer id);
}

(2)接着在 UserMapper 相同的位置创建 UserMapper.xml 文件,内容如下:

<?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">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="loadUserByUsername" resultType="com.example.demo.bean.User">
        select * from user where username=#{username}
    </select>
    <select id="getUserRolesByUid" resultType="com.example.demo.bean.Role">
        select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
    </select>
</mapper>

(3)由于在 Maven 工程中,XML 配置文件建议写在 resources 目录下,但上面的 UserMapper.xml 文件写在包下,Maven 在运行时会忽略包下的 XML 文件。因此需要在 pom.xml 文件中重新指明资源文件位置,配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
    <!-- 重新指明资源文件位置 -->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
</build>

5、创建 UserService

定义的 UserService 实现 UserDetailsService 接口,并实现该接口中的 loadUserByUsername 方法,该方法将在用户登录时自动调用。

loadUserByUsername 方法的参数就是用户登录时输入的用户名,通过用户名去数据库中查找用户:

  • 如果没有查找到用户,就抛出一个账户不存在的异常。
  • 如果查找到了用户,就继续查找该用户所具有的角色信息,并将获取到的 user 对象返回,再由系统提供的 DaoAuthenticationProvider 类去比对密码是否正确。
@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;
    }
}

6、配置 Spring Security
Spring Security 大部分配置与前文一样,只不过这次没有配置内存用户,而是将刚刚创建好的 UserService 配置到 AuthenticationManagerBuilder 中。

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
 
    // 指定密码的加密方式
    @SuppressWarnings("deprecation")
    @Bean
    PasswordEncoder passwordEncoder(){
        // 不对密码进行加密
        return NoOpPasswordEncoder.getInstance();
    }
 
    // 配置用户及其对应的角色
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
 
    // 配置 URL 访问权限
    @Override
    protected  void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() // 开启 HttpSecurity 配置
            .antMatchers("/admin/**").hasRole("ADMIN") // admin/** 模式URL必须具备ADMIN角色
            .antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')") // 该模式需要ADMIN或USER角色
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // 需ADMIN和DBA角色
            .anyRequest().authenticated() // 用户访问其它URL都必须认证后访问(登录后访问)
            .and().formLogin().loginProcessingUrl("/login").permitAll() // 开启表单登录并配置登录接口
            .and().csrf().disable(); // 关闭csrf
    }
}

7、运行测试

(1)首先在 Conctoller 中添加如下接口进行测试:

@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 db() {
        return "hello db";
    }
 
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

(2)接下来测试一下,我们使用 admin 用户进行登录,由于该用户具有 ADMIN 和 USER 这两个角色,所以登录后可以访问 /hello、/admin/hello 以及 /user/hello 这三个接口。

Spring Security使用详解3(基于数据库的用户角色配置)

(3)而由于 /db/hello 接口同时需要 ADMIN 和 DBA 角色,因此 admin 用户仍然无法访问。

Spring Security使用详解3(基于数据库的用户角色配置)

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Spring Boot 集成 Mybatis 实现双数据源
这里用到了SpringBootMybatisDynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离。添加依赖加入Mybatis启动器,这里添加了Druid连接池、Oracle数据库驱动为例。<dependency<groupIdorg.mybatis.spring
Stella981 Stella981
3年前
Spring Security使用详解9(密码加密配置)
在之前的文章中密码都是使用明文的方式进行存储,但这样会有很大的安全隐患。所以正常做系统时,密码都要加密处理。而在SpringBoot中配置密码加密非常容易,下面通过样例进行演示。九、密码加密配置1、样例代码(1)要配置密码加密只需要修改两个地方。首先要修改HttpSecurity配置中的PasswordEncoder
Stella981 Stella981
3年前
Spring Security使用详解4(基于数据库的URL权限规则配置)
虽然前面我们实现了通过数据库来配置用户与角色,但认证规则仍然是使用HttpSecurity进行配置,还是不够灵活,无法实现资源和角色之间的动态调整。要实现动态配置 URL权限,就需要开发者自定义权限配置,具体步骤如下。四、基于数据库的URL权限规则配置 1、数据库设计这里的数据库在前文(点击查看)的基础上增加一张资
Stella981 Stella981
3年前
Django之Django模板
1、问:html页面从数据库中读出DateTimeField字段时,显示的时间格式和数据库中存放的格式不一致,比如数据库字段内容为2012082616:00:00,但是页面显示的却是Aug.26,2012,4p.m.答:为了页面和数据库中显示一致,需要在页面格式化时间,需要添加<td{{dayrecord.p\_time|date:
Stella981 Stella981
3年前
Spring Security使用详解2(基于内存的用户、URL权限配置 )
二、基于内存的用户、URL权限配置1、用户角色配置(1)我们可以通过自定义类继承WebSecurityConfigurerAdapter,从而实现对SpringSecurity更多的自定义配置。比如下面样例我们就配置了两个用户,以及他们对应的角色。注意:基于内存的用户配置在配置角色时不需要添加“ROLE\_”前缀,而
Stella981 Stella981
3年前
Spring Security使用详解10(通过注解配置方法安全)
在之前的文章样例中,认证和授权都是基于URL的。开发者也可以通过注解来灵活地配置方法安全,下面通过样例进行演示。 十、通过注解配置方法安全1、样例代码(1)首先我们要通过@EnableGlobalMethodSecurity注解开启基于注解的安全配置:@EnableGlobalMethodSecurity注解参