基于数据库实现认证
我们刚才的实现都是基于内存来构建用户的,在实际开发中,用户肯定会保存到数据库中,在Spring Security框架中提供了一个UserDetailsService 接口
它的主要作用是提供用户详细信息。具体来说,当用户尝试进行身份验证时,UserDetailsService 会被调用,以获取与用户相关的详细信息。这些详细信息包括用户的用户名、密码、角色等
1. 执行流程
暂时无法在飞书文档外展示此内容
新创建一个UserDetailsServiceImpl,让它实现UserDetailsService ,代码如下
@Component public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询数据库中的用户,并且返回框架要求的UserDetails return null; } }
- 当前对象需要让spring容器管理,所以在类上添加注解@Component
- 大家注意一下loadUserByUsername方法的返回值,叫做UserDetails,这也是框架给提供了保存用户的类,并且也是一个接口,如果我们有自定义的用户信息存储,可以实现这个接口,我们后边会详细讲解
既然以上能使用这个类来查询用户信息,那么我们之前在SecurityConfig中定义的用户信息,可以注释掉了,如下:
/* @Bean public UserDetailsService users() { UserDetails user = User.builder() .username("user") .password("$2a$10$2VCyByZ5oeiXCEN73wvBB.xpmJgPBbZVS/Aallmdyij2G7hmAKQTG") .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password("$2a$10$cRH8iMMh6XO0T.ssZ/8qVOo8ThWs/qfntIH3a7yfpbPd05h9ZGx8y") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); }*/
- 数据库查询用户
我们下面就来实现数据库去查询用户,我们可以直接使用我们项目中的用户表,实现的步骤如下:
- 导入相关依赖(数据库、mybaits、lombok等)
- 添加配置:连接数据库、mybatis配置等(application.yml)
- 编写实体类和mapper
- 改造UserDetailsServiceImpl(用户从数据库中获取)
1. pom文件添加依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--MySQL支持--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> </dependency>
2. application.yml添加数据库相关配置
#服务配置 server: #端口 port: 8080 spring: application: name: springsecurity-demo #数据源配置 datasource: druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.200.146:3306/security_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root # MyBatis配置 mybatis: #mapper配置文件 mapper-locations: classpath*:mapper*/*Mapper.xml type-aliases-package: com.itheima.project.entity configuration: # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 驼峰下划线转换 map-underscore-to-camel-case: true use-generated-keys: true default-statement-timeout: 60 default-fetch-size: 100
3. 表结构及实体类和mapper
新创建一个数据库,名字为:security_db
导入当天资料的sql脚本
用户实体类
package com.itheima.project.entity; import lombok.Data; import java.time.LocalDateTime; @Data public class User { public Long id; /** * 用户账号 */ private String username; /** * 密码 */ private String password; /** * 用户昵称 */ private String nickName; }
用户mapper,我们只需要定义一个根据用户名查询的方法即可
package com.itheima.project.mapper; import com.itheima.project.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; /** * @author sjqn */ @Mapper public interface UserMapper { @Select("select * from sys_user where username = #{username}") public User findByUsername(String username); }
4. 改造UserDetailsServiceImpl
package com.zzyl.security.service; import com.zzyl.security.entity.User; import com.zzyl.security.entity.UserAuth; import com.zzyl.security.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.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.SimpleTimeZone; /** * @author sjqn */ @Component public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询用户 User user = userMapper.findByUsername(username); if(user == null){ throw new RuntimeException("用户不存在或已被禁用"); } SimpleGrantedAuthority user_role = new SimpleGrantedAuthority("user"); SimpleGrantedAuthority admin_role = new SimpleGrantedAuthority("admin"); List<GrantedAuthority> list = new ArrayList<GrantedAuthority>(); list.add(user_role); list.add(admin_role); return new org.springframework.security.core.userdetails.User(user.getUsername() ,user.getPassword() , list); } }
测试
重启项目之后,可以根据数据库中有的用户进行登录,如果登录成功则表示整合成功
2、自定义UserDetails
上述代码中,返回的UserDetails或者是User都是框架提供的类,我们在项目开发的过程中,很多需求都是我们自定义的属性,我们需要扩展该怎么办?
其实,我们可以自定义一个类,来实现UserDetails,在自己定义的类中,就可以扩展自己想要的内容,如下代码:
package com.zzyl.security.entity; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; /** * @author sjqn * @date 2023/9/1 */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserAuth implements UserDetails { private String username; //固定不可更改 private String password;//固定不可更改 private String nickName; //扩展属性 昵称 private List<String> roles; //角色列表 @Override public Collection<? extends GrantedAuthority> getAuthorities() { if(roles==null) return null; //把角色类型转换并放入对应的集合 return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_"+role)).collect(Collectors.toList()); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
然后,我们可以继续改造UserDetailsServiceImpl中检验用户的逻辑,代码如下:
package com.itheima.project.service; import com.itheima.project.entity.User; import com.itheima.project.mapper.UserMapper; import com.itheima.project.vo.UserAuth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @author sjqn */ @Component public class UserDetailServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //查询用户 User user = userMapper.findByUsername(username); if(user == null){ throw new RuntimeException("用户不存在或已被禁用"); } UserAuth userAuth = new UserAuth(); userAuth.setUsername(user.getUsername()); userAuth.setPassword(user.getPassword()); userAuth.setNickName(user.getNickName()); //添加角色 List<String> roles=new ArrayList<>(); //加入判断,方便后面授权的时候使用 if("user@qq.com".equals(username)){ roles.add("USER"); userAuth.setRoles(roles); } if("admin@qq.com".equals(username)){ roles.add("USER"); roles.add("ADMIN"); userAuth.setRoles(roles); } return userAuth; } }
修改HelloController,使用getPrincipal()方法读取认证主体对象。
/** * @ClassName * @Description */ @RestController public class HelloController { @RequestMapping("/hello") public String hello(){ //认证成功,可以得到用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String name = authentication.getName(); //获取自定义属性 UserAuth principal = (UserAuth) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return "hello"+name+" 昵称:"+principal.getNickName(); } }
测试
重启项目之后,可以根据数据库中有的用户进行登录,如果登录成功则表示整合成功