本章概要

  • 基于数据库的认证

10.2 基于数据库的认证

在真实的项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证和授权。

1. 设计数据表

用户表、角色表、用户角色关联表

建表语句

CREATE TABLE `role` (`id` int(11) NOT NULL,`name` varchar(32) DEFAULT NULL,`nameZh` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` (`id` int(11) NOT NULL,`username` varchar(32) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`enabled` varchar(1) DEFAULT NULL,`locked` varchar(1) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_role` (`id` int(11) NOT NULL,`uid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

初始化数据

注意:角色名有一个默认的前缀 ROLE_

INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (1, 'root', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0');
INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (2, 'admin', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0');
INSERT INTO `user`(`id`, `username`, `password`, `enabled`, `locked`) VALUES (3, 'tangsan', '$2a$10$x6CBW1qnQqKPIxUSefZN7ebfTEiYNnfzzVjPJzlRhg5XyMzSWoO4e', '1', '0');
INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (1, 'ROLE_dba', '数据库管理员');
INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `role`(`id`, `name`, `nameZh`) VALUES (3, 'ROLE_user', '普通用户');
INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (1, 1, 1);
INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (2, 1, 2);
INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (3, 2, 2);
INSERT INTO `user_role`(`id`, `uid`, `rid`) VALUES (4, 3, 3);

2. 创建项目

MyBatis 灵活,JPA 便利,此处选择前者,创建 Spring Boot Web 项目添加如下依赖:

<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>1.3.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.1.10</version>
</dependency>

3. 配置数据库

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

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/jpa

4. 创建实体类

Role

public class Role {private Integer id;private String name;private String nameZh;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNameZh() {return nameZh;}public void setNameZh(String nameZh) {this.nameZh = nameZh;}
}

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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return !locked;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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;}
}

代码解释:

  • 用户实体类需要实现 UserDetails 接口,并且实现该接口中的 7 个方法
    | 方法名 | 解释 |
    | — | — |
    | getAuthorities() | 获取当前用户对象所具有的角色信息 |
    | getPassword() | 获取当前用户对象的密码 |
    | getUsername() | 获取当前用户对象的用户名 |
    | isAccountNonExpired() | 当前账号是否未过期 |
    | isAccountNonLocked() | 当前账号是否未锁定 |
    | isCredentialsNonExpired() | 当前账号密码是否未过期 |
    | isEnabled() | 当前账号是否可用 |

  • 用户根据实际情况设置这 7 个方法的返回值。因为默认情况下不需要开发者自己进行密码角色等信息的比对,开发者只需要提供相关信息即可,例如 getPassword() 方法返回的密码和用户输入的密码不匹配,会自动抛出 BadCredentialsException 异常,isAccountNonExpired() 方法返回了 false ,会自动抛出 AccountExpiredException 异常,因此对开发者而言,只需要按照数据库中的数据在这里返回相应的配置即可。此处因为数据库中只有 enabled 和 locked 字段,故帐号未过期和密码未过期两个方法都返回 true

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

5. 创建 UserService

@Service
public class UserService implements UserDetailsService {@AutowiredUserMapper userMapper;@Overridepublic 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 实现 UserDetailsService 接口,并实现该接口中的 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 mapperPUBLIC "-//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.model.User">select * from user where username=#{username}</select><select id="getUserRolesByUid" resultType="org.sang.model.Role">select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}</select>
</mapper>

6. 配置 Spring Security

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredUserService userService;@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}@Overrideprotected 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();}
}

此处的配置 与 10.1 节 介绍的一致,唯一不同的是没有配置内存用户,而是将刚刚创建好的 UserService 配置到 AuthenticationManagerBuilder 中。

7.创建 Controller

@RestController
public class HelloController {@GetMapping("/admin/hello")public String admin() {return "hello admin";}@GetMapping("/db/hello")public String dba() {return "hello dba";}@GetMapping("/user/hello")public String user() {return "hello user";}
}

8. 测试

登录 admin 用户,访问 /admin/hello,报了以下错误

Invalid bound statement (not found)

pom.xml 新增以下配置

<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory></resource></resources>
</build>

再次登录访问 /admin/hello,报了以下错误

Caused by: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Illegal overloaded getter method with ambiguous type for property enabled in class class org.sang.model.User. This breaks the JavaBeans specification and can cause unpredictable results.

去掉 User 实体类中 enabled 属性的 get set 方法,如下

public class User implements UserDetails {private Integer id;private String username;private String password;private Boolean enabled;private Boolean locked;private List<Role> roles;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getName()));}return authorities;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return !locked;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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 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;}
}

再次登录访问 /admin/hello

访问 /db/hello

访问 /user/hello

十、Spring Boot 安全管理(2)相关推荐

  1. Spring Boot安全管理

    Spring Boot安全管理 本项目内容出自书籍<SpringBoot企业级开发教程>中第七章的项目内容,有兴趣的同学可以网上搜索一下,这里仅仅作为项目记录,将不会进行代码解释 sql数 ...

  2. freemarker ftl模板_Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker

    今天来聊聊 Spring Boot 整合 Freemarker. Freemarker 简介 这是一个相当老牌的开源的免费的模版引擎.通过 Freemarker 模版,我们可以将数据渲染成 HTML ...

  3. Spring Boot安全管理—基于数据库的认证

    基于数据库的认证 1. 设计数据库表 首先设计一个基本的用户角色表,一共三张表,分别是用户表.角色表以及用户角色关联表. SET FOREIGN_KEY_CHECKS=0;-- ----------- ...

  4. 精选Spring Boot三十五道必知必会知识点!

    Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家.本文精选了三十五个常见的Spring Boot知识点,祝你一臂之力! 问题一 Spr ...

  5. Spring Boot Ajax实例(十六)

    这篇博文主要用于新手学习Spring Boot,同时也记录自己学习的过程- 文章内容主要来源于易百教程 本文将展示如何使用jQuery.ajax将HTML表单请求发送到Spring REST API并 ...

  6. 读书笔记《Spring Boot+Vue全栈开发实战》(下)

    本书将带你全面了解Spring Boot基础与实践,带领读者一步步进入 Spring Boot 的世界. 前言 第九章 Spring Boot缓存 第十章 Spring Boot安全管理 第十一章 S ...

  7. Spring Boot面试题

    转载自 Spring Boot面试题 Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家. 问题一Spring Boot.Spring ...

  8. docker 打包镜像_Spring Boot2 系列教程(四十一)部署 Spring Boot 到远程 Docker 容器

    不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案. 不同于传 ...

  9. 关于Spring Boot 这可能是全网最好的知识点总结

    专注于Java领域优质技术号,欢迎关注 作者:Arain Spring Boot 是微服务中最好的 Java 框架. 我们建议你能够成为一名 Spring Boot 的专家.本文精选了三十五个常见的S ...

最新文章

  1. python2.7读汉字的时候出现乱码,如何解决
  2. python——常用内置函数(enumerate、map、zip、eval)用法
  3. WebLogic8.1 配置SSL/HTTPS单向认证
  4. ubuntu下交叉编译环境构建
  5. react打包后图片丢失_手搭一个 React,Typescript,Koa,GraphQL 环境
  6. 使用IntelliJ IDEA 2020 高效开发 springboot项目
  7. .net环境下如何使用MySql数据库
  8. java虚拟机学习笔记(五)---运行时的数据区域
  9. ios-绘制-小知识点(裁减)
  10. Ubuntu开机直接进入tty1,无法直接进入的图形化界面的解决方法
  11. 写给年轻的我们:我们工作到底为了什么
  12. AD模块电压采集电路
  13. 苹果ipad怎么刷机_苹果手机:iOS12刷机iOS12正式版刷机教程
  14. 闲聊:Android 平台网络游戏加速器·一(科普文)
  15. 尚学堂马士兵老师的JAVA自学之路
  16. python pip install pil_python安装PIL库
  17. win10资源管理器explorer总是崩溃重启
  18. 深入理解Android之Xposed详解
  19. javascript 实现中文按照拼音首字母排序
  20. 运动无线蓝牙耳机推荐、运动健身必备的运动耳机

热门文章

  1. PAT乙级 1012 数字分类
  2. 常用的一些拓展:Spring拓展接口分门别类
  3. 关于异鬼II bootkit病毒的关情况
  4. xcode编程c语言,使用xcode编写c语言的方法介绍
  5. Mockito中ArgumentCaptor的使用方法
  6. Java中比较器的使用匿名内部类的写法
  7. Ubuntu如何获得管理员权限
  8. 量化系统数据的频率-tick和挂单数据、日内Bars、分钟线、每日周月数据
  9. 如何改php网线的字,家里安装路由器,拉网线太难看,教你不用线的方法
  10. 软件开发人员的三条职业路径