学科分类
目录
Spring Boot开发

UserDetailsService身份认证

对于用户流量较大的项目来说,频繁的使用JDBC进行数据库查询认证不仅麻烦,而且会降低网站登录访问速度。对于一个完善的项目来说,如果某些业务已经实现了用户信息查询的服务,就没必要使用JDBC进行身份认证了。

下面假设当前项目中已经有用户信息查询的业务方法,这里,在已有的用户信息服务的基础上选择使用UserDetailsService进行自定义用户身份认证。

1.定义查询用户及角色信息的服务接口

为了案例演示,假设项目中存在一个CustomerService业务处理类,用来通过用户名获取用户及权限信息,内容如文件1所示。

文件1 CustomerService.java

 1    import com.itheima.domain.Customer;
 2    import com.itheima.domain.Authority;
 3    import com.itheima.repository.CustomerRepository;
 4    import com.itheima.repository.AuthorityRepository;
 5    import org.springframework.beans.factory.annotation.Autowired;
 6    import org.springframework.data.redis.core.RedisTemplate;
 7    import org.springframework.stereotype.Service;
 8    import java.util.List;
 9    // 对用户数据结合Redis缓存进行业务处理
 10    @Service
 11    public class CustomerService {
 12        @Autowired
 13        private CustomerRepository customerRepository;
 14        @Autowired
 15        private AuthorityRepository authorityRepository;
 16        @Autowired
 17        private RedisTemplate redisTemplate;
 18        // 业务控制:使用唯一用户名查询用户信息
 19        public Customer getCustomer(String username){
 20            Customer customer=null;
 21            Object o = redisTemplate.opsForValue().get("customer_"+username);
 22            if(o!=null){
 23                customer=(Customer)o;
 24            }else {
 25                customer = customerRepository.findByUsername(username);
 26                if(customer!=null){
 27                    redisTemplate.opsForValue().set("customer_"+username,customer);
 28                }
 29            }
 30            return customer;
 31        }
 32        // 业务控制:使用唯一用户名查询用户权限
 33        public List<Authority> getCustomerAuthority(String username){
 34            List<Authority> authorities=null;
 35            Object o = redisTemplate.opsForValue().get("authorities_"+username);
 36            if(o!=null){
 37                authorities=(List<Authority>)o;
 38            }else {
 39                authorities=authorityRepository.findAuthoritiesByUsername(username);
 40                if(authorities.size()>0){
 41                    redisTemplate.opsForValue().set("authorities_"+username,authorities);
 42                }
 43            }
 44            return authorities;
 45        }
 46    }

文件1所示的业务代码,读者不需要细究,只要明白CustomerService是项目中存在的Customer业务处理类,该类结合Redis缓存定义了通过用户名username获取用户信息和用户权限信息的方法。关于CustomerService业务处理类及其关联的其他代码,可以查阅具体的源代码示例。

2.定义UserDetailsService用于封装认证用户信息

UserDetailsService是Security提供的进行认证用户信息封装的接口,该接口提供的loadUserByUsername(String s)方法用于通过用户名加载用户信息。使用UserDetailsService进行身份认证的时,自定义一个UserDetailsService接口的实现类,通过loadUserByUsername(String s)方法调用用户业务处理类中已有的方法进行用户详情封装,返回一个UserDetails封装类,来供Security认证使用。

下面,自定义一个UserDetailsService接口实现类进行用户认证信息封装,内容如文件2所示。

文件2 UserDetailsServiceImpl.java

 1    import com.itheima.domain.Authority;
 2    import com.itheima.domain.Customer;
 3    import org.springframework.beans.factory.annotation.Autowired;
 4    import org.springframework.security.core.GrantedAuthority;
 5    import org.springframework.security.core.authority.SimpleGrantedAuthority;
 6    import org.springframework.security.core.userdetails.*;
 7    import org.springframework.stereotype.Service;
 8    import java.util.ArrayList;
 9    import java.util.List;
 10    // 自定义一个UserDetailsService接口实现类进行用户认证信息封装
 11     @Service
 12    public class UserDetailsServiceImpl implements UserDetailsService {
 13        @Autowired
 14        private CustomerService customerService;
 15        @Override
 16        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
 17            // 通过业务方法获取用户及权限信息
 18            Customer customer = customerService.getCustomer(s);
 19            List<Authority> authorities = customerService.getCustomerAuthority(s);
 20            // 对用户权限进行封装
 21            List<SimpleGrantedAuthority> list = authorities.stream()
 22                       .map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
 23                       .collect(Collectors.toList());
 24            // 返回封装的UserDetails用户详情类
 25            if(customer!=null){
 26                UserDetails userDetails= 
 27                             new User(customer.getUsername(),customer.getPassword(),list);
 28                return userDetails;
 29            } else {
 30                // 如果查询的用户不存在(用户名不存在),必须抛出此异常
 31                throw new UsernameNotFoundException("当前用户不存在!");
 32            }
 33        }
 34    }

文件2中,重写了UserDetailsService接口的loadUserByUsername(String s)方法,在该方法中,使用CustomerService业务处理类获取用户的用户信息和权限信息,并通过UserDetails进行认证用户信息封装。

需要注意的是,使用new User()方法封装CustomerService业务处理类获取到的用户时,必须对当前用户进行非空判断,如果查询的用户为空(即用户名错误),需要使用throw关键字抛出UsernameNotFoundException异常;否则,一旦登录的用户名输入错误,程序可能出现Security无法识别的错误。

3.使用UserDetailsService进行身份认证

接下来,在configure(AuthenticationManagerBuilder auth)方法中使用UserDetailsService身份认证的方式进行自定义用户认证,内容如文件3所示。

文件3 SecurityConfig.java

 1    import com.itheima.service.UserDetailsServiceImpl;
 2    import org.springframework.beans.factory.annotation.Autowired;
 3    import org.springframework.security.config.annotation.authentication.builders.*;
 4    import org.springframework.security.config.annotation.web.configuration.*;
 5    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 6    @EnableWebSecurity  // 开启MVC security安全支持
 7    public class SecurityConfig extends WebSecurityConfigurerAdapter {
 8        @Autowired
 9        private UserDetailsServiceImpl userDetailsService;
 10        @Override
 11        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 12            //  密码需要设置编码器
 13            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
 14            // 3、使用UserDetailsService进行身份认证
 15            auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
 16        }
 17    }

在文件3中,先通过@Autowired注解引入了自定义的UserDetailsService接口实现类UserDetailsServiceImpl,然后在重写的configure(AuthenticationManagerBuilder auth)方法中使用UserDetailsService身份认证的方式自定义了认证用户信息。在使用UserDetailsService身份认证时,直接调用userDetailsService(T userDetailsService)对UserDetailsServiceImpl实现类进行认证,认证过程中需要对密码进行编码设置。

4.效果测试

重启chapter07项目进行效果测试,项目启动成功后,通过浏览器访问“http:/``/localhost:8080/”访问项目首页(务必保证项目已有的CustomerService业务处理类配置完成,且关联的Redis服务器已经启动),效果如图1所示。

图1 项目首页访问效果

从图1可以看出,执行“http://localhost:8080/”访问项目首页时,同样自动跳转到了用户登录页面“http://localhost:8080/login”。此时输入正确或者错误的用户信息。

至此,关于Spring Boot整合Spring Security中的自定义用户认证讲解完毕。内存身份认证最为简单,主要用作测试和新手体验;JDBC身份认证和UserDetailsService身份认证在实际开发中使用较多,而这两种认证方式的选择,主要根据实际开发中已有业务的支持来确定。

点击此处
隐藏目录