• SpringSecurity - 自定义用户认证


    1. 基于内存的用户定义认证

    测试接口:启动项目访问 localhost:8080/info

    @RestController
    public class UserController {
        @RequestMapping("/info")
        public void info(HttpServletRequest request){
            // 当前登录用户的用户名
            String remoteUser = request.getRemoteUser();
            Principal userPrincipal = request.getUserPrincipal();
            Authentication authentication = (Authentication) userPrincipal;
            // 判断当前用户是否具备某一个指定的角色
            boolean admin = request.isUserInRole("admin");
            // 登录用户的用户名
            System.out.println("remoteUser = " + remoteUser);
            System.out.println("authentication.getName():"+authentication.getName());
            System.out.println("admin = " + admin);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1. 配置文件方式

    默认的用户定义在SecurityProperties里边,是一个静态内部类,如果要定义自己的用户名密码,必然是要去覆盖默认配置,在配置文件中配置:

    spring.security.user.name=admin
    spring.security.user.password=admin
    spring.security.user.roles=ADMIN
    
    • 1
    • 2
    • 3

    2. 配置类中重写configure方式

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("zhangsan").password("123").roles()
                    .and()
                    .withUser("lisi").password("123").roles();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginProcessingUrl("/login").permitAll()
                    .and()
                    .csrf().disable();
        }
    
        @Bean
        PasswordEncoder passwordEncoder() {
            //暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例
            return NoOpPasswordEncoder.getInstance();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    启动项目,此时就可以使用这里配置的用户登录了。

    3. 自定义 InMemoryUserDetailsManager 方式

    SpringSecurity 支持多种用户定义方式,自定义用户其实就是使用UserDetailService的不同实现类来提供用户数据,同时将配置好的UserDetailsService配置给AuthenticationManagerBuilder,系统再将UserDetailsService提供给AuthenticationProvider使用。

    通过自定义 InMemoryUserDetailsManager 来看一下基于内存的用户是如何自定义的。

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            // 创建对象设置用户名,密码,角色 (密码加上{noop}代表密码不加密,明文存储)
            manager.createUser(User.withUsername("zhangsan").password("123").roles("admin").build());
            manager.createUser(User.withUsername("lisi").password("123").roles("user").build());
            auth.userDetailsService(manager);
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginProcessingUrl("/login").permitAll()
                    .and()
                    .csrf().disable();
        }
    
        @Bean
        PasswordEncoder passwordEncoder() {
            //暂时先不给密码进行加密,所以返回 NoOpPasswordEncoder 的实例
            return NoOpPasswordEncoder.getInstance();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2. 提供用户数据源的接口 UserDetailsService

    1. UserDetails 接口

    SpringSecurity 中定义了UserDetails 接口来规范开发者自定义的用户对象,接口定义如下:

    public interface UserDetails extends Serializable {
        // 返回当前账户锁具备的权限
        Collection<? extends GrantedAuthority> getAuthorities();
        // 返回当前账户的密码
        String getPassword();
        // 返回当前账户的用户名
        String getUsername();
        // 返回当前账户是否未过期
        boolean isAccountNonExpired();
        // 返回当前账户是否未锁定
        boolean isAccountNonLocked();
        // 返回当前账号凭证是否未过期
        boolean isCredentialsNonExpired();
        // 返回当前账号是否可用
        boolean isEnabled();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2. UserDetailsService 接口

    这是用户对象的定义,而负责提供用户数据源的接口是UserDetailsService,UserDetailService中只有一个查询用户的方法:

    public interface UserDetailsService {
        // username是用户在认证时传入的用户名,最常见的是用户在登录表单中输入的用户名
        // 拿到用户名之后,再去数据库中查询用户,最终返回一个UserDetails实例
        UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在实际项目中,一般需要自定义UserDetailsService的实现。如果没有自定义UserDetailsService的实现,Spring Security 也为UserDetailsService 提供了默认实现:

    (1) UserDetailsManager 在UserDetailsService 的基础上,定义了5种方法:

    public interface UserDetailsManager extends UserDetailsService {
        // 创建用户
        void createUser(UserDetails var1);
    	// 更新用户
        void updateUser(UserDetails var1);
    	// 删除用户
        void deleteUser(String var1);
    	// 修改密码
        void changePassword(String var1, String var2);
    	// 判断用户是否存在
        boolean userExists(String var1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (2) JdbcDaoImpl 在UserDetailsService 的基础上,通过spring-jdbc实现了从数据库中查询用户的方法;

    (3) InMemoryUserDetailsManager 实现了UserDetailsService 中关于用户的增删改查方法,不过都是基于内存的操作,数据没有持久化;

    (4) JdbcUserDetailsManager 继承自 JdbcDaoImpl 同时又实现了 UserDetailsManager 接口,因此可以通过JdbcUserDetailsManager 实现对用户的在呢过删改查操作,这些操作都会持久化到数据化中,但是操作数据库中用户的SQL都是提前写好的,不够灵活,因此在是开发中 JdbcUserDetailsManager 用的并不多;

    (5) CachingUserDetailsService 的特点是会将 UserDetailsService 缓存起来;

    (6) UserDetailsServiceDelegator 提供了 UserDetailsService 的懒加载功能;

    当我们使用Spring Security 时,如果仅仅引入了一个Spring Security依赖,则默认使用的用户就是 InMemoryUserDetailsManager 提供的;

    SpringBoot 之所以能做到零配置使用SpringSecurity,就是因为它提供了众多的自动化配置类。其中UserDetailsService 的自动化配置类时UserDetailsServiceAutoConfiguration;

    3. 基于MyBatis数据源的用户定义认证

    使用MyBatis做数据持久化是目前大多数企业应用采取的方案,SpringSecurity中结合MyBatis可以灵活的定制用户表和角色表。

    首先设计三张表,分别为用户表,角色表,以及用户角色关联表,三张表的关系如图所示:

    在这里插入图片描述

    create table role(
    	id int(11) not null auto_increment,
        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 auto_increment,
        username varchar(32) default null,
        password varchar(255) default null,
        enable tinyint(1) default null,
        accountNonExpired tinyint(1) default null,
        accountNonLocked tinyint(1) default null,
        credentialsNonExpired tinyint(1) default null,
        primary key(id)
    )engine=innodb default charset=utf8
    
    create table user_role(
    	id int(11) not null auto_increment,
        uid int(11) default null,
        rid int(11) default null,
        primary key(id),
        key uid(uid),
        key rid(rid)
    )engine=innodb default charset=utf8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    向数据库中添加几条模拟数据:

    INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (1, 'ROLE_dba', '数据库管理员');
    INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (2, 'ROLE_admin', '系统管理员');
    INSERT INTO `test`.`role` (`id`, `name`, `nameZh`) VALUES (3, 'ROLE_user', '用户');
    
    INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (1, 'root', '123', 1, 1, 1, 1);
    INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (2, 'admin', '123', 1, 1, 1, 1);
    INSERT INTO `test`.`user` (`id`, `username`, `password`, `enable`, `accountNonExpired`, `accountNonLocked`, `credentialsNonExpired`) VALUES (3, 'zhangsan', '123', 1, 1, 1, 1);
    
    INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (1, 1, 1);
    INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (2, 1, 2);
    INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (3, 2, 2);
    INSERT INTO `test`.`user_role` (`id`, `uid`, `rid`) VALUES (4, 3, 3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在SpringSecurity项目中引入MyBatis和MySQL的依赖:

    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.1.3version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在resources/application.yml中配置数据库基本连接信息:

    spring:
      application:
        name: uua
      datasource:
        username: root
        password: root
        url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
    
    mybatis:
      mapper-locations: classpath:mapper/*.xml
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    自定义用户类需要实现UserDetails接口,并实现接口中的方法:

    @Data
    public class Role {
        private Integer id;
        private String name;
        private String nameZh;
    }
    
    @Data
    public class UserRole {
        private Integer id;
        private Integer uid;
        private Integer rid;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    @Data
    public class User implements UserDetails {
        private Integer id;
        private String username;
        private String password;
        private Boolean enabled;
        private Boolean accountNonExpired;
        private Boolean accountNonLocked;
        private Boolean credentialsNonExpired;
        // 保存用户所具备的角色信息
        private List<Role> roles = new ArrayList<>();
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for(Role role:roles){
                authorities.add(new SimpleGrantedAuthority(role.getName()));
            }
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }
    
        @Override
        public boolean isEnabled() {
            return enabled;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    实现UserDetailsService接口自定义MyUserDetailsService以及对应的数据库查询方法:

    @Service
    public class MyUserDetailsService implements UserDetailsService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapper.loadUserByUsername(username);
            if(Objects.isNull(user)){
                throw new UsernameNotFoundException("用户不存在");
            }
            user.setRoles(userMapper.getRolesByUid(user.getId()));
            return user;
        }
    }
    
    @Mapper
    public interface UserMapper {
        User loadUserByUsername(String username);
        List<Role> getRolesByUid(Integer uid);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.imooc.uua.dao.UserMapper">
        <select id="loadUserByUsername" resultType="com.imooc.uua.entity.User">
            select * from user wherr username=#{username};
        select>
        <select id="getRolesByUid" resultType="com.imooc.uua.entity.Role">
            select r.*
            from  user_role ur
            join role r
            on ur.rid=r.id and ur.uid=#{uid}
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    SecurityConfig中注入UserDetailsService :

    @EnableWebSecurity(debug = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        MyUserDetailsService myUserDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginProcessingUrl("/login").permitAll()
                    .and()
                    .csrf().disable();
        }
        
        @Bean
        PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    启动项目利用数据库中添加的模拟用户进行登录测试,就可以登录成功了。

  • 相关阅读:
    程序员的数学课13 复杂度:如何利用数学推导对程序进行优化?
    认识数据库
    4931: 二叉树遍历(先序中序还原树+后续遍历)
    DTD和XSD的区别
    关于js_节流的介绍和简单的使用
    c文件如何编译为ko的MAKEFILE文件编写
    GEE引擎架设好之后进游戏时白屏的解决方法——gee引擎白屏修复
    Spring Boot基于KLock实现分布式锁的使用详解(二)
    敦煌考古,招不到人了?看数字化技术如何破圈
    【photoshop学习】用 Photoshop 做的 15 件创意事
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/126476928