security|spring security入门--从数据库读取数据实现用户登录访问简单示例四

1.说明
之前的几个示例都是从内存中获取的数据,这里改写为从数据库获取数据实现用户登录访问功能
从内存读取数据的代码可参看
地址:https://blog.csdn.net/qq_32224047/article/details/108604598
2.代码示例
数据库建表语句

CREATE DATABASE /*!32312 IF NOT EXISTS*/`security` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `security`; DROP TABLE IF EXISTS `tb_permission`; CREATE TABLE `tb_permission` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`user_id` BIGINT(20) DEFAULT NULL COMMENT '父权限',`name` VARCHAR(64) NOT NULL COMMENT '权限名称',`authority` VARCHAR(64) NOT NULL COMMENT '权限英文名称',`created` DATETIME NOT NULL,`updated` DATETIME NOT NULL,PRIMARY KEY (`id`)) ENGINE=INNODB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8 COMMENT='权限表'; /*Data for the table `tb_permission` */ INSERTINTO `tb_permission`(`id`,`user_id`,`name`,`authority`,`created`,`updated`) VALUES (1,1,'写入','write','2019-06-07 15:00:00','2019-06-07 15:00:00'),(2,1,'更新','update','2019-06-07 15:00:00','2019-06-07 15:00:00'),(3,1,'删除','delete','2019-06-07 15:00:00','2019-06-07 15:00:00'),(4,1,'读取','read','2019-06-07 15:00:00','2019-06-07 15:00:00'),(5,2,'读取','read','2019-06-07 15:00:00','2019-06-07 15:00:00'); /*Table structure for table `tb_user` */ DROP TABLE IF EXISTS `tb_user`; CREATE TABLE `tb_user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT,`username` VARCHAR(50) NOT NULL COMMENT '用户名',`password` VARCHAR(64) NOT NULL COMMENT '密码,加密存储',`phone` VARCHAR(20) DEFAULT NULL COMMENT '注册手机号',`created` DATETIME NOT NULL,`updated` DATETIME NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `username` (`username`) USING BTREE,UNIQUE KEY `phone` (`phone`) USING BTREE) ENGINE=INNODB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8 COMMENT='用户表'; /*Data for the table `tb_user` */ INSERTINTO `tb_user`(`id`,`username`,`password`,`phone`,`created`,`updated`) VALUES (1,'admin','123456','18510270606','2019-06-15 10:00:58','2019-06-15 10:00:58'),(2,'user','123456','18610270607','2019-06-15 10:00:58','2019-06-15 10:00:58');

表的机构信息如下图
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

项目结构如下
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

启动类StarterSecurity1 添加了Mapper包的扫描
@SpringBootApplication @MapperScan("cn.wy.mapper") public class StarterSecurity1 { public static void main(String[] args) { SpringApplication.run(StarterSecurity1.class,args); } }

配置文件数据
#datasource配置 spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql:///security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.driver-class-name=com.mysql.jdbc.Driver#mybatis mybatis.type-aliases-package=cn.wy.domain #将带有下划线的表字段映射为驼峰格式的实体类属性 #添加了该项配置后,在开发中只需要根据查询返回的字段,创建好实体类就可以了 mybatis.configuration.map-underscore-to-camel-case=true

控制器类AdminController
@RestController public class AdminController { @RequestMapping("/admin/write") public String write() { return "写入数据"; }@RequestMapping("/admin/update") public String update() { return "更新数据"; }@RequestMapping("/admin/delete") public String delete() { return "删除数据"; }@RequestMapping("/user/read") public String read() { return "读取数据,不需要权限"; } @RequestMapping("/other") public String other() { return "其他权限"; } }

配置类MyWebSecurityConfig
import cn.wy.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { //明文加密器,只需要在内存中有这个管理对象,如果不添加,从前端登录时会抛出异常 @Bean public PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } //构造一个内存框架对象,获取数据库中的数据 @Bean public UserDetailsService myUserDetailsService(){ return new UserService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //获取数据库中的数据 auth.userDetailsService(myUserDetailsService()); }@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()// 开启权限控制,通过ANT规范,自定义逻辑//当访问/admin/write时必须有write权限 .antMatchers("/admin/write").hasAuthority("write") .antMatchers("/admin/update").hasAuthority("update") .antMatchers("/admin/delete").hasAuthority("delete") .antMatchers("/user/read").hasAuthority("read") .anyRequest()//任意请求 .authenticated(); //必须经过认证 只要登陆就能访问 http.formLogin(); //开启表单认证 http.httpBasic(); //开启http 基本认证 }}

实体类User和Permission类
package cn.wy.domain; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.List; public class User implements Serializable { private Long id; private String username; private String password; private String phone; private Date created; private Date updated; private List authorities; public Long getId() { return id; }public void setId(Long id) { this.id = id; } @Override public String getUsername() { return username; }public void setUsername(String username) { this.username = username; } public void setAuthorities(List authorities) { this.authorities = authorities; }@Override public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }public String getPhone() { return phone; }public void setPhone(String phone) { this.phone = phone; }public Date getCreated() { return created; }public void setCreated(Date created) { this.created = created; }public Date getUpdated() { return updated; }public void setUpdated(Date updated) { this.updated = updated; }}


package cn.wy.domain; import java.io.Serializable; import java.util.Date; public class Permission implements Serializable { private Long id; private Long userId; private String name; private String authority; private Date created; private Date updated; public Long getId() { return id; }public void setId(Long id) { this.id = id; }public Long getUserId() { return userId; }public void setUserId(Long userId) { this.userId = userId; }public String getName() { return name; }public void setName(String name) { this.name = name; }public String getAuthority() { return authority; }public void setAuthority(String authority) { this.authority = authority; }public Date getCreated() { return created; }public void setCreated(Date created) { this.created = created; }public Date getUpdated() { return updated; }public void setUpdated(Date updated) { this.updated = updated; } }

接口类UserMapper、PermissionMapper
直接在接口类上编写sql语句,不在创建对应的映射文件,注意sql 语句中换行后要添加一个空格,防止代码粘粘在一起,导致sql语句错误
package cn.wy.mapper; import cn.wy.domain.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface UserMapper { //利用username读取user对象 @Select("select * from tb_user " + "where username=#{username}") public User selectUserByUsername( @Param("username") String username); }

package cn.wy.mapper; import cn.wy.domain.Permission; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; public interface PermissionMapper { //利用user_id搜索的对应permission list //注意 下面的sql中表名和 where 之间需要加一个空格,否则会粘粘在一起,导致sql语句错误 @Select("select * from tb_permission" + " where user_id=#{userId}") public List selectPermissionsByUserid( @Param("userId") Long userId); }

UserService类用于读取Mapper数据库数据,要和Security整合,需要实现接口UserDetailsService,注意自定义的User和Security提供的User
package cn.wy.service; import cn.wy.domain.Permission; import cn.wy.mapper.PermissionMapper; import cn.wy.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.ArrayList; import java.util.List; /** * 用于读取Mapper数据库数据,要和Security整合,需要 * 实现接口UserDetailsService * */ public class UserService implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private PermissionMapper permissionMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //username参数,是在登陆时,用户传递的表单数据username //主要读取数据库3个值 username password authorities //这里自定义的User和security中的User重名,且那个包名很长所以这里写自定义User的包路径 cn.wy.domain.User user = userMapper.selectUserByUsername(username); Long userId=user.getId(); List permissions = permissionMapper.selectPermissionsByUserid(userId); //为了返回一个UserDetails 使用User List authorities=new ArrayList<>(); for (Permission p:permissions) { //循环一次,就拿到了表格权限,使用authority字段 String authorityName = p.getAuthority(); GrantedAuthority authority= new SimpleGrantedAuthority(authorityName); authorities.add(authority); }//这里的User 是这个包下的 org.springframework.security.core.userdetails.User; User userDetails=new User( user.getUsername(), user.getPassword(), authorities); user.setAuthorities(authorities); return userDetails; }}

测试验证:
登录user访问write功能
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

【security|spring security入门--从数据库读取数据实现用户登录访问简单示例四】测试正常,无权限
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

更换为admin用户
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

测试正确
security|spring security入门--从数据库读取数据实现用户登录访问简单示例四
文章图片

其他测试均正常,这里不再列出

    推荐阅读