Spring Boot + Spring Security通过数据库验证用户身份信息

2022年10月21日08:17:44

Spring Boot + Spring Security 实现通过数据库进行用户身份信息的认证以及授权


2020年12月24日更新:
最近在学习Spring Security时的一些问题总结,以及一个完整的demo示例。该demo示例,实现了通过mysql数据库进行用户身份信息的登录认证,以及页面的动态授权显示。
完整代码已上传至Gitee仓库,地址:Spring Boot-Spring Security-Demo

1.mysql数据库中的准备

1.1 建立数据库db_authority

1.2 建立表tb_user

表中字段为:id name password role

CREATETABLE`tb_user`(`id`bigint(20)NOTNULLAUTO_INCREMENT,`name`varchar(255)DEFAULTNULLCOMMENT'用户名',`password`varchar(255)DEFAULTNULLCOMMENT'用户密码',`role`varchar(255)DEFAULTNULLCOMMENT'用户角色,所拥有的权限',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_0900_ai_ci;

2 建立Spring Boot项目

版本为:Spring Boot 2.4.1
Spring Security版本为:Spring Security 5
Mysql 数据库:MySql 8.0

初始化所勾选依赖jar包:

Spring Boot + Spring Security通过数据库验证用户身份信息

项目初始化完成。

2.1 编写项目配置文件

在路径resources/application.yml编辑application.yml文件

spring:profiles:active: devjpa:show-sql:truehibernate:ddl-auto: nonethymeleaf:cache:falsedatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db_authority?serverTimezone=GMT%2B8username: rootpassword: root

2.2 建立包domain

在该包下,编写实体类User 映射数据库的表tb_user, 实现spring security中内置的用户信息类UserDetails

@Entity(name="tb_user")//映射数据库中的tb_user表@Data//Lombok注解,生成getter setterpublicclassUserimplementsUserDetails{@Id//标识为主键@GeneratedValue(strategy= GenerationType.IDENTITY)//主键自增private Long id;private String name;private String password;private String role;//返回用户信息中所有认证权限,用户权限集@Overridepublic Collection<?extendsGrantedAuthority>getAuthorities(){//获取用户所有权限
        String[] roles= role.split(",");//取出所有的权限,并且给每个权限都要进行认证,添加到 SimpleGrantedAuthority()
        List<SimpleGrantedAuthority> simpleGrantedAuthorities=newArrayList<>();//遍历roles,取出每一个权限,添加到 简单的授予认证类for(String s: roles){//添加了 ROLE_ 前缀 为了前端页面thymeleaf-security 中自带的 hasRole()方法,能得到ROLE_格式的角色信息
            simpleGrantedAuthorities.add(newSimpleGrantedAuthority("ROLE_"+s));}//返回到已经被 授予认证的权限集合 这里面的角色所拥有的权限都已经被spring security所知道return simpleGrantedAuthorities;}//返回用户名@Overridepublic StringgetUsername(){return name;}//账户是否过期@OverridepublicbooleanisAccountNonExpired(){returntrue;}//账户是否锁定@OverridepublicbooleanisAccountNonLocked(){returntrue;}//凭证是否过期,认证后的密码@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}//用户是否可用@OverridepublicbooleanisEnabled(){returntrue;}}

2.3 建立包 dao

在该包下面编写接口UserRepository继承接口JpaRepository ,就可以用Spring Data JPA 自带的单表查询方法。

/*数据仓库接口,实现对数据库的操作*/@RepositorypublicinterfaceUserRepositoryextendsJpaRepository<User,Long>{//按姓名查找用户的方法
    UserfindByName(String name);}

2.4 建立包 service

在该包下,编写接口UserService,并继承接口UserDetailsService

/*服务类接口,继承用户信息服务的接口 UserDetailsService*/publicinterfaceUserServiceextendsUserDetailsService{//按姓名查找用户
    UserfindByName(String name);//增加用户
    Userinsert(User user);//加载用户信息,按照姓名@Override
    UserDetailsloadUserByUsername(String name)throws UsernameNotFoundException;}

在该包下面 建立实现包impl,在实现包下面建立UserServiceImpl 对才编写的接口方法进行实现

@Service//注册到SprngBoot中的服务组件publicclassUserServiceImplimplementsUserService{@Autowiredprivate UserRepository userRepository;@Autowiredprivate PasswordEncoder passwordEncoder;//实现按照姓名查找用户@Overridepublic UserfindByName(String name){return userRepository.findByName(name);}//测试通过//在数据库中,新增一位用户,且密码以加盐形式保存@Overridepublic Userinsert(User user){//存在数据库中的密码应该为密文
        user.setPassword(passwordEncoder.encode(user.getPassword()));return userRepository.save(user);}//测试通过//返回认证用户信息对象。@Overridepublic UserDetailsloadUserByUsername(String name)throws UsernameNotFoundException{//根据姓名查找用户
        User user=findByName(name);if(user==null){
            System.out.println("查无此人");return null;//返回到null,就是认证失败}return user;//此时的user,是被UserDetails包装过具有可认证身份信息的用户对象。}//测试通过}

上面在数据库保存用户信息,密码以密文形式,的密码加盐 工具类,编写在utils包下

/*工具类,生成密文密码*/@ComponentpublicclassGeneratePassword{@Bean
    PasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}}

所有实现方法都应该先进行测试

/*对service中所 实现的方法都应该进行测试*/@SpringBootTestclassUserServiceImplTest{@Autowiredprotected UserServiceImpl userService;@TestvoidfindByName(){
        User admin= userService.findByName("admin");
        System.out.println(admin);}@Testvoidinsert(){//新增一个用户
        User user=newUser();
        user.setName("admin");
        user.setPassword("123");
        user.setRole("vip1,vip2,vip3");
        User user1= userService.insert(user);
        System.out.println(user1);}@TestvoidloadUserByUsername(){
        UserDetails admin= userService.loadUserByUsername("admin");
        System.out.println(admin);}}

2.5 建立包 controller

在controller包下,编写UserController。用来进行相关请求的处理

@Controller//注册到springboot,并返回页面视图(.html)publicclassUserController{@GetMapping("/index")public StringtoIndex(){return"index";}//返回到登录页面@RequestMapping("/login")public StringtoLogin(){return"views/loginbak";}//返回到其它页面@GetMapping("/level1/{id}")public StringtoLevle(@PathVariableint id){return"views/level1/"+id;}//返回到其它页面@GetMapping("/level2/{id}")public StringtoLevle2(@PathVariableint id){return"views/level2/"+id;}//返回到其它页面@GetMapping("/level3/{id}")public StringtoLevle3(@PathVariableint id){return"views/level3/"+id;}}

可以在这一步,把前端相关页面,导入到我们 的工程,html相关 文件稍微有点多 ,我就不贴出来了,都在工程文件夹里面。导入之后,文件目录 结构为:

Spring Boot + Spring Security通过数据库验证用户身份信息

2.6 建立包 config

在该包下,进行Spring Security的配置类SecurityConfig.java 编写,继承WebSecurityConfigurerAdapter

@EnableWebSecurity//标识为spring security 的配置类,并启动,含有@Component,注册到了spring的组件中@EnableGlobalMethodSecurity(prePostEnabled=true)//开启注解方法安全publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Autowiredprotected UserServiceImpl userService;//认证@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throws Exception{//将我们的认证实现类,注册到security中
        auth.userDetailsService(userService);}//授权@Overrideprotectedvoidconfigure(HttpSecurity http)throws Exception{//请求授权的规则
        http.authorizeRequests().antMatchers("/","/index").permitAll().antMatchers("/level1/**").hasRole("vip1").antMatchers("/level2/**").hasRole("vip2").antMatchers("/level3/**").hasRole("vip3");//没有权限会跳转到登录页面,需要开启登录页面

        http.formLogin()//允许表单登录.loginPage("/views/loginbak.html")//换成我们自定义的登录页面.loginProcessingUrl("/login")//登录请求转发到/login 要与表单中action转发的url一致//这里的usernameParameter和passwordParameter对应的是表单中input输入框的name属性.usernameParameter("username").passwordParameter("password").defaultSuccessUrl("/index").permitAll()//当认证成功后,跳转到首页.failureUrl("/login");//认证失败的处理url,暂时就重定向到登录页//开启了注销功能
        http.logout()//内置的登出页面.logoutSuccessUrl("/index");//登出操作成功执行之后,请求的路径//关闭跨站伪造请求攻击
        http.csrf().disable();//开启记住我功能,cookie 默认保存2周
        http.rememberMe();}}@EnableWebSecurity//标识为spring security 的配置类,并启动,含有@Component,注册到了spring的组件中@EnableGlobalMethodSecurity(prePostEnabled=true)//开启注解方法安全publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Autowiredprotected UserServiceImpl userService;//认证@Overrideprotectedvoidconfigure(AuthenticationManagerBuilder auth)throws Exception{//将我们的认证实现类,注册到security中
        auth.userDetailsService(userService);}//授权@Overrideprotectedvoidconfigure(HttpSecurity http)throws Exception{//请求授权的规则
        http.authorizeRequests().antMatchers("/","/index").permitAll().antMatchers("/level1/**").hasRole("vip1").antMatchers("/level2/**").hasRole("vip2").antMatchers("/level3/**").hasRole("vip3");//没有权限会跳转到登录页面,需要开启登录页面

        http.formLogin()//允许表单登录.loginPage("/views/loginbak.html")//换成我们自定义的登录页面.loginProcessingUrl("/login")//登录请求转发到/login 要与表单中action转发的url一致//这里的usernameParameter和passwordParameter对应的是表单中input输入框的name属性.usernameParameter("username").passwordParameter("password").defaultSuccessUrl("/index").permitAll()//当认证成功后,跳转到首页.failureUrl("/login");//认证失败的处理url,暂时就重定向到登录页//开启了注销功能
        http.logout()//内置的登出页面.logoutSuccessUrl("/index");//登出操作成功执行之后,请求的路径//关闭跨站伪造请求攻击
        http.csrf().disable();//开启记住我功能,cookie 默认保存2周
        http.rememberMe();}}

3 小结

至此,demo已完成,该demo基于Spring Boot +Spring Data JPA +Spring Security + Mysql,实现了从数据库中进行用户的身份验证。

总结一下,期间遇到的问题

  1. 存储在mysql数据库的密码不是密文形式,导致验证失败。

    解决办法: 建立了一个密码加盐形式存储在数据库中的工具类,实现PasswordEncoder接口,返回一个 spring security 推荐的加密方式对象BCryptPasswordEncoder()。具体实现,参考工程目录下的/utils/GeneratePassword.java

  2. 登录页面login.html中登录表单的文本输入框input缺少name属性,导致文本输入框提交的数据无法被后台所接收,SecurityConfig.java里面覆盖重写的授权方法里usernameParameterpasswordParameter因此也无法找到对应的参数,所以导致认证失败,顺便一提,这两方法的默认传递参数为usernamepassword 若 表单中的inputname 属性值分别为usernamepassword则可以省略不写 ,若为自定义name属性,需要在usernameParameterpasswordParameter 传递对应的参数。

  3. 解决了上述2个问题,最后依然认证失败,查找原因,发现是 实体类User.java在实现UserDetails的时候,实现方法中有一个isEnable()方法未设置为true,最后将该实现方法的返回值设置为true,解决,该方法的意思是 用户是否可用。

  4. 最后,希望这个demo以及这篇文章对大家有所帮助。


转载请说明原文地址,谢谢!

  • 作者:星空日落
  • 原文链接:https://blog.csdn.net/qq_43647821/article/details/111628821
    更新时间:2022年10月21日08:17:44 ,共 7863 字。