写点什么

Spring Security 怎么从数据库加载我们的用户?

  • 2023-02-15
    湖南
  • 本文字数:3359 字

    阅读完需:约 11 分钟

如何从数据库中读取用户对象?

前面我们分析认证的时候就会发现他在 DaoAuthenticationProvider 中就已经有从数据库中获取用户名和密码的。


public interface UserDetailsService {   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}
复制代码

意思是说我们只要重写这个类,就可以从我们所想要的地方获取用户信息。


那我们重写吧

public class MybatisUserDetailsService implements UserDetailsService, UserDetailsPasswordService {		@Resource	private UsersMapper usersMapper;		@Override	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {		Users users = usersMapper.loadUserByUsername(username);		if (null == users) {			throw new UsernameNotFoundException(username);		}		return users;	}		@Override	public UserDetails updatePassword(UserDetails user, String newPassword) {		if (user instanceof Users users) {			users.setPassword(newPassword);			usersMapper.updateByPrimaryKeySelective(users);		}		return user;	}}
复制代码

发现这里需要把 spring security 的 users 对象转换成我们所需要的 users 对象。比较麻烦。


小白: "为什么不直接使用 spring security 那里面的 users 对象呢?"

小黑: "我们自己实现 users 对象的话就可以在自定义。特别是权限这边,我们肯定是需要自定义 users 对象的,不能直接使用其内部的对象。"


然后我们直接继承 UserDetails 接口,然后自定义一些我们所需要的属性。说白了就是从内置 users 对象中拷贝一些代码出来。


小黑: "这里我故意留下了一个坑,如果你真的从内置的 users 对象里复制代码的话,你会发现一个问题。


@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class Users implements Serializable, UserDetails {    private Long id;
private String username;
private String password;
private Boolean enabled;
private Boolean accountnonexpired;
private Boolean accountnonlocked;
private Boolean credentialsnonexpired;
private List<Roles> rolesList;
private static final long serialVersionUID = 1L;
@Override public Collection<? extends GrantedAuthority> getAuthorities() { ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Roles roles : getRolesList()) { authorities.add(new SimpleGrantedAuthority(roles.getRole())); } return authorities; }
@Override public boolean isAccountNonExpired() { return accountnonexpired; }
@Override public boolean isAccountNonLocked() { return accountnonlocked; }
@Override public boolean isCredentialsNonExpired() { return credentialsnonexpired; }
@Override public boolean isEnabled() { return enabled; }}
复制代码

小白: "代码定义好了,但现在你要怎么加入到 spring security 中呢?让 spring security 主动调用我们自定义的类呢?"

小黑: "我们要使用自定义的类的话,无非是在 DaoAuthenticationProvider 里面设置。所以我们只要找到 setUserDetailsService 这个函数就行了。看一下有哪些方法调用了这个函数?"


看了一下发现这边调用了 setUserDetailsService 方法的。前面的 UserDetailsService 是对象是通过 spring bean 上下文面拿出来的。


那我们也可以效仿他的方式,在 spring bean 上添加我们的 UserDetailsService Bean。


@Beanpublic UserDetailsService userDetailsService() {   return new MybatisUserDetailsService();}
复制代码


@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {   return httpSecurity.authorizeHttpRequests()         .anyRequest().authenticated()         .and()         .formLogin()         .defaultSuccessUrl("/hello", true) // 认证成功后访问         .permitAll() // 白名单         .and()         .logout()         .logoutUrl("/logout")         .logoutSuccessUrl("/login") // 注销成功后访问         .clearAuthentication(true)         .invalidateHttpSession(true)         .and()         .build();}
复制代码


@GetMapping("hello")public HashMap<String, Object> hello(Authentication authentication) {   HashMap<String, Object> map = new HashMap<>();   Object principal = authentication.getPrincipal();   String name = authentication.getName();   map.put("principal", principal);   map.put("name", name);   return map;}
复制代码

小白: "停一下, 你数据库表结构呢? "

小黑: "我忘了, 我找找在哪可以抄"


在分析 UserDetailsService 的时候, 我们一般都要看看他的类族是怎样的, 结果发现可以偷懒的地方, 也就是表结构的位置


create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));create unique index ix_auth_username on authorities (username,authority);
复制代码

这段 sql 要改改, 否则 mysql 无法执行


结果发现, 就这...


我还不如自己设计呢

SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;
-- ------------------------------ Table structure for authorities-- ----------------------------DROP TABLE IF EXISTS `authorities`;CREATE TABLE `authorities` ( `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, UNIQUE INDEX `ix_auth_username`(`username` ASC, `authority` ASC) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ------------------------------ Table structure for users-- ----------------------------DROP TABLE IF EXISTS `users`;CREATE TABLE `users` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `enabled` bit(1) NULL DEFAULT NULL, `accountNonExpired` bit(1) NULL DEFAULT b'1', `accountNonLocked` bit(1) NULL DEFAULT b'1', `credentialsNonExpired` bit(1) NULL DEFAULT b'1', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
复制代码

注意, 这里仅仅只是为了玩耍, 而非企业, 在企业中, 肯定不是这么设计的, 一般根据 RBAC 设计


小白: "这样就完成了?"

小黑: "完成了, 其他代码都是 mybatis 生成的, 简单"


启动, 访问,崩了


等下, spring security 脱敏呢? 为什么这里可以输出密码?


分析源码看看

脱敏为什么没有生效?

通过源码分析,脱敏应该是认证成功之后的事情


大概看了下源码, ProviderManager 有脱敏, 但是为什么不生效呢?



看这代码的意思, 是要我们在 Users 类上多添加一个接口 CredentialsContainer


public class Users implements Serializable, UserDetails, CredentialsContainer {	@Override	public void eraseCredentials() {        // 设置 password 为 null		this.password = null;	}}
复制代码

行, 前面的坑补上了。再试试


完美~~~

总结

记住 UserDetailsService 怎么自定义, 注意自定义的 User 需要实现哪些接口, 还有脱敏问题


作者:bangiao

链接:https://juejin.cn/post/7182826437617909817

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
Spring Security怎么从数据库加载我们的用户?_Java_做梦都在改BUG_InfoQ写作社区