(三)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 ,只是在其运行期间,参与了一个环节,给它一个我们自定义的检验器,让它使用我们的检验器;

posted @ 2019-03-20 09:23  Yiaz  阅读(1241)  评论(0编辑  收藏  举报