【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面

【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面

【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面

Day01 项目:【稻草人问答项目—Day01】环境搭建、数据库的连接、LOMBOK框架、MYbatis Plus Generator 代码生成器

接着Day 01项目继续往下进行

一、Spring安全框架的概述以及使用

1. Spring安全框架概述

  • 不是随随便便的一个人就可以访问我们的网站页面,获取数据,那样我们的网站会很不安全,容易被黑客攻击;为此我们要给我们的网站做一些安全措施
  • Spring安全框架: Spring-Security,是Spring提供的安全管理框架,所谓安全管理指登录控制,权限管理,访问规则等一个网站的安全设置,Spring-Security可以实现初级程序员编写少量代码实现较高安全级别的程序,现在主流的安全框架除了Spring-Security还有Shiro

2.Spring安全框架初步使用

  • 1、在我们portal项目中添加依赖
<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security Test -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
  • 依赖一旦添加成功,当前网站的所有资源都会被Spring-Security保护起来了,必须输入用户名和密码来访问,默认的用户名是user,密码会在启动服务时随机生成,输出到控制台上,
  • 如果不想使用默认的用户名和密码,可以在application.properties文件中设置:
# 配置Spring-Security的用户名和密码
spring.security.user.name=admin
spring.security.user.password=123123
  • 但是采用明文密码有安全隐患,别人就可以直接在配置文件中看到密码:123123,所以我们要进行密码加密

二、Bcrypt算法加密

  • 现在主流的加密算法:MD5,BCrypt,这里我们使用Bcrypt
  • Spring-Security框架直接包含了BCrypt的加密对象,供我们直接使用

BCrpty加密

  • 1、首先在测试类中编写代码运行Bcrtpy的加密代码
@Test
public void bcrypt(){
    //这个类就是能够对字符串进行加密的类
    BCryptPasswordEncoder encoder=
            new BCryptPasswordEncoder();
    //将字符串加密的方法encode
    String pwd=encoder.encode("123456");
    //输出加密之后的字符串
    System.out.println(pwd);
    //$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS
    //每次运行程序加密的结果是不同的,这是加密算法的“加盐”
    //虽然每次生产的结果不同,但是一定能表示我们要加密的内容
}

结果:
【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面

  • 补充:加密算法“加盐” 就是每次运行程序加密的结果是不同的,虽然每次生产的结果不同,但是一定能表示我们要加密的内容
  • 2、验证一个字符串是否匹配一个加密结果
@Test
public void bcrypt(){
    //这个类就是能够对字符串进行加密的类
    BCryptPasswordEncoder encoder=
            new BCryptPasswordEncoder();
   
    //下面验证匹配结果
    //要匹配的字符串
    String str="123123";
    //要验证的加密结果
    String pwd="$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS";
    //开始验证
    boolean b=encoder.matches(str,pwd);
    System.out.println("是否匹配:"+b);
}
  • 3、回到我们使用的Spring-Security框架,配置文件中修改密码如下:
# 配置Spring-Security的用户名和密码
spring.security.user.name=admin
# {bcrypt}不是密码内容,而是约定好的算法id
# Spring-Security会根据指定的算法id对密码进行验证
spring.security.user.password={bcrypt}$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS

三、Spring-Security框架验证用户登录

  • 实际开发中数据库保存密码,我们就必须想办法在java代码实现对用户名和密码的设置
    也就是说: 用户登录信息后,Spring-Security会根据用户的登录信息在对应的数据库表查找,如果信息匹配则登陆成功,显示页面!

3.1 使用用户详情类认证数据

  • Spring-Security框架认证数据库中用户的流程图
    【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面
    流程分析: Spring-Security框架提供了一个UserDetails的类,这个类中能够保存用户的用户名,密码,权限等重要信息;把用户输入的 username用户名传入到我们的业务层:UserServiceImpl,业务层调用持久层UserMapper 根据传入的username在数据库查找,最终返回的是封装好的 UserDetails对象(包含 用户名,密码,权限等)

下面我们根据上述流程开始实现

  • 1、创建一个包security,在包中创建Spring-Security的配置类
//当前类是一个配置类
@Configuration
//启动Spring-Security 的权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
//Spring-Security要求当前配置类继承一个父类来配置相关信息
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsServiceImpl userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
}
  • 2、创建一个UserDetailsServiceImpl类来返回用户详情
//@Component 表示这个类必须要注入到spring容器中
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private IUserService userService;

    //Spring-Security 框架是按照程序员实现的接口中方法
    //loadUserByUsername方法是spring-security框架用户验证用户信息的方法
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        //调用连接数据库并且获得用户详情的业务逻辑层方法直接返回
        return userService.getUserDetails(username);
    }
}
  • 3、业务层接口
public interface IUserService extends IService<User> {
    //spring-security在验证登录时
    //连接数据库返回用户详情业务的方法
    public UserDetails getUserDetails(String username);
}
  • 4、业务层实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails getUserDetails(String username) {
        //根据用户名查询用户信息
        User user = userMapper.findUserByUsername(username);
        if (user==null){
            return null;
        }
        //根据用户id查询用户所有权限
        List<Permission> ps = userMapper.findPermissionsByUserId(user.getId());
        //把得到的权限转换为字符串格式 String[]
        String[]auths = new String [ps.size()];
        int i = 0;
        for (Permission p:ps){
            auths[i++] = p.getName();
        }
        //声明用户详情并且赋值
        UserDetails u = org.springframework.security.core.userdetails.User
                .builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(auths)
                //1 :锁  0:不锁
                .accountLocked(user.getLocked()==1)
                //1:可用  0:不可用
                .disabled(user.getEnabled()==0)
                .build();
        return u;
    }
}
  • 现在缺的就是我们的持久层: UserMapper,根据用户输入的用户名来查询密码和权限,这就牵扯到了多表查询,下面画图看看各表的关系,才可以写sql语句
    【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面
  • 持久层接口
@Repository
public interface UserMapper extends BaseMapper<User> {
    //用户登录时,根据用户名查询用户信息的方法
    @Select("select * from user where username=#{username}")
    User findUserByUsername(String username);

    //根据用户id查询用户权限的方法
    @Select("SELECT p.id,p.name\n" +
            "FROM user u\n" +
            "LEFT JOIN user_role ur ON u.id=ur.user_id\n" +
            "LEFT JOIN role r ON r.id=ur.role_id\n" +
            "LEFT JOIN role_permission rp ON r.id=rp.role_id\n" +
            "LEFT JOIN permission p ON p.id=rp.permission_id\n" +
            "WHERE u.id=#{id}")
    List<Permission> findPermissionsByUserId(Integer id);
}

四、Spring-Security授权范围设置

  • 现在我们的项目中所有的资源都需要登录之后才能访问,但是网站中并不是所有页面和资源都需要先登录,根据网站设计,我们可以允许某些页面不登录也能访问,这就需要在Spring-Security中设置访问限制规则SecurityConfig类中添加一个方法来实现
@Override
protected void configure(HttpSecurity http) throws Exception {
    //这个方法是用于设置当前Spring-Security框架访问控制范围的
    http.csrf().disable() //关闭防跨域攻击的功能
        .authorizeRequests()
            //设置放行url
            .antMatchers(
                    "/index.html",
                    "/img/**",
                    "/js/*",
                    "/css/*",
                    "/bower_components/**"
            ).permitAll()
            //设置除放行url之外所有请求都需要登录
            .anyRequest().authenticated()
            //设置登录方式为表单输入
            .and().formLogin();
}
  • 这个方法中设置了放行的页面,和所有静态资源,还可以设置放行的控制器方法路径等

五、设置自定义登录页面

  • 自定义登录页面是我们自己创建的视图:login.html

步骤

  • 1、它需要thymeleaf的支持,所以要添加thymeleaf的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
  • 2、static文件夹中的login.html就是我们登录页面的代码,将它移动到templates文件夹下称为视图模板
  • 3、我们需要编写Controller代码才能显示视图模板内容给浏览器,所有我们创建一个SystemController控制器类来编写显示视图的代码
@RestController
public class SystemController {

    @GetMapping("/login.html")
    public ModelAndView loginForm(){
        return new ModelAndView("login");
    }
}
  • 4、我们需要在配置Spring-Security的配置信息中,明确要使用我们的login.html替换默认的登录页面
  //放行规则:就是并不是访问所有的页面都需要登录,我们对这些页面进行设置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()//关闭防跨域攻击
        .authorizeRequests()
                //设置放行url
                .antMatchers(
                        "/index.html",
                        "/login.html",
                        "/img/**",
                        "/js/*",
                        "/css/*",
                        "/bower_components/**"
                ).permitAll()
                //设置除了url放行之外的所有请求都需要登录
               .anyRequest().authenticated()
                //设置登陆方式为表单输入
               .and().formLogin()
               .loginPage("/login.html")//访问控制器
               .loginProcessingUrl("/login")// /login是spring-security框架要求固定写法
               .failureUrl("/login.html?error")
                .defaultSuccessUrl("/index.html")
                .and().logout()//登出
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login.html?logout");
    }
上一篇:Day02-05变量常量作用域


下一篇:day02