zoukankan      html  css  js  c++  java
  • (三)spring Security 从数据库中检索用户名和密码


    配置 Druid 数据源

    配置 Druid 数据源 ,看下这篇博客,至于后面的添加监控那些就不用看了,仅仅看如何整合的 ;


    数据库

    需要创建 userrolerole_user 表。建表语句我已经写好了:

    CREATE TABLE IF NOT EXISTS USER (
      id       INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
      name     VARCHAR(10),
      password VARCHAR(50)
    )
      CHARSET utf8;
    
    CREATE TABLE IF NOT EXISTS ROLE (
      id   INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
      name VARCHAR(10)
    )
      CHARSET utf8;
    
    CREATE TABLE IF NOT EXISTS ROLE_USER (
      user_id INT,
      role_id INT
    )
      CHARSET utf8;
    
    # 防止项目启动,出现重复插入而报错
    INSERT INTO `ROLE` (`id`, `name`) SELECT
                                        '1',
                                        'ROLE_ADMIN'
                                      FROM dual
                                      WHERE NOT exists(SELECT id
                                                       FROM `ROLE`
                                                       WHERE id = '1');
    INSERT INTO `ROLE` (`id`, `name`) SELECT
                                        '2',
                                        'ROLE_USER'
                                      FROM dual
                                      WHERE NOT exists(SELECT id
                                                       FROM `ROLE`
                                                       WHERE id = '2');
    
    INSERT INTO `USER` (`id`, `password`, `name`) SELECT
                                                    '1',
                                                    'root',
                                                    'root'
                                                  FROM dual
                                                  WHERE NOT exists(SELECT id
                                                                   FROM `USER`
                                                                   WHERE id = '1');
    INSERT INTO `USER` (`id`, `password`, `name`) SELECT
                                                    '2',
                                                    'yiaz',
                                                    'yiaz'
                                                  FROM dual
                                                  WHERE NOT exists(SELECT id
                                                                   FROM `USER`
                                                                   WHERE id = '2');
    
    
    INSERT INTO `ROLE_USER` (`user_id`, `role_id`) SELECT
                                                     '1',
                                                     '1'
                                                   FROM dual
                                                   WHERE NOT exists(SELECT user_id
                                                                    FROM `ROLE_USER`
                                                                    WHERE user_id = '1');
    INSERT INTO `ROLE_USER` (`user_id`, `role_id`) SELECT
                                                     '2',
                                                     '2'
                                                   FROM dual
                                                   WHERE NOT exists(SELECT user_id
                                                                    FROM `ROLE_USER`
                                                                    WHERE user_id = '2');
    
    
    
    

    执行下即可。或者自己写,也行;


    Mapper 文件

    
    public interface LoginMapper {
    
        @Select("select * from user where name = #{name}")
        MyUser loadUserByUsername(String name);
    
           @Select("SELECT role.`name` FROM role WHERE role.id in (SELECT role_id FROM " +
           " role_user as r_s JOIN `user` as u  ON r_s.user_id = u.id and u.id = #{id})")
        List<Role> findRoleByUserId(int id);
    }
    
    

    自定义 UserDetailsService

    UserDetailsService 的主要作用是,获取数据库里面的信息,然后封装成对象,我们既然需要从数据库中读取用户,那么我们就需要实现自己的 UserDetailsService ,按照我们的逻辑完成从数据库中获取信息;

    /**
     * 主要是封装从数据库获取的用户信息
     *
     * @author yiaz
     * @date 2019年3月19日10:50:58
     */
    @Component
    public class UserDetailServiceImpl implements UserDetailsService {
    
        // demo  不想写 service层,直接 dao 层穿透到 controller 层
        @Autowired
        private LoginMapper loginMapper;
    
        @Override
        public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
            // 根据用户名查询数据库,查到对应的用户
            MyUser myUser = loginMapper.loadUserByUsername(name);
    
            // ... 做一些异常处理,没有找到用户之类的
            if(myUser == null){
                throw new UsernameNotFoundException("用户不存在") ;
            }
    
            // 根据用户ID,查询用户的角色
            List<Role> roles = loginMapper.findRoleByUserId(myUser.getId());
            // 添加角色
            List<GrantedAuthority> authorities = new ArrayList<>();
            for (int i = 0; i < roles.size(); i++) {
                authorities.add(new SimpleGrantedAuthority(roles.get(i).getName()));
            }
            // 构建 Security 的 User 对象
            User user = new User(myUser.getName(), myUser.getPassword(), authorities);
    
            return user;
        }
    }
    
    

    自定义登陆校验器 AuthenticationProvider

    我们既然不用 security 来帮我们检验,就要实现自己的校验逻辑,实现自己的 AuthenticationProvider 类,完成校验 ;

    BCryptPasswordEncoder 是完成加盐MD5 的一个类,很棒,思路和笔者许久之前想到的差不多。不需要我们去管理盐值的问题,也不需要在数据库里面进行存储了;

    /**
     * 完成校验工作
     */
    @Component
    public class MyAuthenticationProvider implements AuthenticationProvider {
        @Autowired
        private UserDetailServiceImpl userDetailService;
    
        /**
         * 进行身份认证
         *
         * @param authentication
         * @return
         * @throws AuthenticationException
         */
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
            // 获取用户输入的用户名和密码
            String username = authentication.getName();
            String password = authentication.getCredentials().toString();
            // 获取封装用户信息的对象
            UserDetails userDetails = userDetailService.loadUserByUsername(username);
            // 进行密码的比对
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
            boolean flag = bCryptPasswordEncoder.matches(password, userDetails.getPassword());
            // 校验通过
            if (flag){
                // 将权限信息也封装进去
                return new UsernamePasswordAuthenticationToken(userDetails,password,userDetails.getAuthorities());
            }
    
            // 验证失败返回 null
            return null;
        }
    
        /**
         * 这个方法 确保返回 true 即可,
         *
         * @param aClass
         * @return
         */
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    }
    
    

    配置 security

    将之前的 WebSecurityConfig 类中的 WebSecurityConfigurerAdapter 做如下修改:

    
        /**
         * security 配置
         * @param myAuthenticationProvider
         * @return
         */
    
        @Autowired
        @Bean
        public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter(MyAuthenticationProvider myAuthenticationProvider) {
            /**
             * 配置对哪些路径进行拦截,如果方法里面什么都不写,则不拦截任何路径;
             * <p>
             * 如果,使用 super.configure(http),父类的方法:
             * ((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
             * <p>
             * 我们自定义下拦截规则,表单等一系列规则;
             */
            return new WebSecurityConfigurerAdapter() {
                @Override
                protected void configure(HttpSecurity http) throws Exception {
                    http
                            .authorizeRequests()
                            // 放行登录
                            .antMatchers("/login/**").permitAll()
                            .anyRequest().authenticated()
                            .and()
                            // 开启表单认证
                            .formLogin()
                            // 地址写的是 映射的路径
                            .loginPage("/login.html")
                            // 必须添加
                            .loginProcessingUrl("/login")
                            .permitAll()
                            // 第二个参数,如果不写成true,则默认登录成功以后,访问之前被拦截的页面,而非去我们规定的页面
                            .defaultSuccessUrl("/index.html", true)
                            .and()
    
                            .logout()
                            .logoutUrl("/logout")
                            .and()
                            .csrf()
                            .disable()
                            .httpBasic();
    
                }
    
                /**
                 * 配置自定义校验规则,密码编码,使用我们自定义的校验器
                 * @param auth
                 * @throws Exception
                 */
                @Override
                protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                    auth.authenticationProvider(myAuthenticationProvider);
                }
    
    
            };
        }
    

    其中 loginProcessingUrl("/login") 必须写上,否则会报 405 错误 ,其中后面的参数值,写成,你自定义表单的提交地址;


    后记

    这样就完成了,也不难,就是有点坑,浪费了我一天时间,之前没写上 loginProcessingUrl("/login") 大家也没提到这个问题,导致一直 405 ,如果你也遇到 405 ,兴许你花了2分钟看完,就搞定了!

    其实即使我们自定义了检验规则,其实我们也没有完全接手 security ,只是在其运行期间,参与了一个环节,给它一个我们自定义的检验器,让它使用我们的检验器;

  • 相关阅读:
    正则元字符总表
    Request中的各种地址
    JSONP实例
    【Kindeditor编辑器】 文件上传、空间管理
    【加密算法】SHA
    【加密算法】DES
    【加密算法】3DES
    【加密算法】Base64
    【加密算法】MD5
    【加密算法】AES
  • 原文地址:https://www.cnblogs.com/young-youth/p/11665574.html
Copyright © 2011-2022 走看看