ProjectDay02

续 Spring 安全框架

密码加密的实现

上次课讲解了密码加密的概念

介绍了bcrypt密码加密的算法

下面我们通过一个测试,演示这个算法

// 声明密码加密操作对象
PasswordEncoder encoder=new BCryptPasswordEncoder();
// 加密测试
@Test
public void pwd(){
    String str="123456";
    // 执行加密,使用encode方法参数是要加密的字符串,返回值是加密结果
    String pwd= encoder.encode(str);
    System.out.println(pwd);
    //$2a$10$kVuKchYUN92TGcQb./H.iOHCT8LOIho12bI1OTb5xPTassynjdsES
}

运行测试方法,能够得到加密结果

我们发现每次加密结果是不同的

因为如果每次加密结果时固定的,反而容易被人破解,并不安全

bcrypt加密算法采用了"随机加盐"技术,每次加密结果不同,提高了安全等级

下面我们来测试,加密得到的字符串,能不能正确的验证成功

"验证"指判断当前加密字符串是不是指定字符串生成的

// 验证操作
@Test
public void match(){
    // 验证一个字符串是否能够加密为指定的加密结果
    // 方法matches([原字符串],[加密字符串])返回值是boolean类型
    // 返回值为真表示验证通过,返回值为假表示验证失败
    boolean b=encoder.matches("123456",
            "$2a$10$kVuKchYUN92TGcQb./H.iOHCT8LOIho12bI1OTb5xPTassynjdsE");
    System.out.println("验证结果为:"+b);

}

上面的测试如果运行输出验证通过(true),表示字符串和加密结果匹配

否则会输出false

下面将我们的application.properties配置文件中的用户密码设置为加密的

# Spring-Security自定义登录用户名和密码的配置
spring.security.user.name=admin
spring.security.user.password={bcrypt}$2a$10$kVuKchYUN92TGcQb./H.iOHCT8LOIho12bI1OTb5xPTassynjdsES

{bcrypt}是"算法id"的声明定义

表示后面的字符串是由bcrypt算法加密得来的

登录时会按照相同的算法来验证

Spring-Security配置登录与权限管理

上面章节中我们将登录用户信息保存在application配置文件中

实际登录时,一定是从数据库来获得用户信息的,所以登录一定会编写在java代码中

我们学习如何在java配置类中配置一个用户的登录,顺便演示用户的权限管理功能

portal项目中创建一个security包

这个包中创建SecurityConfig类,进行配置代码如下

// SpringBoot启动时扫描到@Configuration标记的类,会自动加载其中的配置
// 所有SpringBoot框架环境下@Configuration必须写才能配置生效
@Configuration
// 启动Spring-Security配置的注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // WebSecurityConfigurerAdapter是我们需要基础的父类
    // 这个父类提供了配置Spring-Security运行的基本方法
    // 我们要想修改配置的话,重写它的方法即可
    // 下面我们就配置一个可以登录的用户
    // 一旦重写这个方法,配置文件中的admin就失效了
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 删除原有的super调用
        // 在代码中配置一个等于用户
        // inMemory表示在内存中保存一个用户
        auth.inMemoryAuthentication()
                .withUser("tom")
                .password("{bcrypt}$2a$10$ELGiEhKyLlO9r3.WVOkHDe16JTCKCErcABhElD5CF7ZwQ.Hm6sVRW")
                .authorities("answer");
        // 我们配置了一个可以登录的用户
        // 用户名tom 密码 888888
        // 在代码最后一行,我们授予了当前tom用户answer的授权\资格
        // 这个授权或资格可以访问需要对应授权的控制器方法

    }
}

重启服务

登录tom

登录成功后可以访问项目的大部分资源

但是如果有控制器方法需要特殊授权才能执行

运行前Spring-Security会验证当前用户是否具有这个资格

我们在UserController中编写两个需要不同授权才能访问的方法代码如下

@GetMapping("/answer")
// 下面的注解规定当前用户必须包含answer授权
// 才能访问下面的控制方法
@PreAuthorize("hasAuthority('answer')")
public String answer(){
    return "允许回答问题!";
}
@GetMapping("/delete")
@PreAuthorize("hasAuthority('delete')")
public String delete(){
    return "允许删除回答!";
}

重启服务

tom用户具有answer授权

所以可以访问/answer方法

但是访问/delete方法时返回403错误,表示没有权限访问

这样就能显示某些用户访问某些资源

如果想让一个用户具有多个授权,可以改写登录配置如下

String[] as={"answer","delete"};
// inMemory表示在内存中保存一个用户
auth.inMemoryAuthentication()
        .withUser("tom")
        .password("{bcrypt}$2a$10$ELGiEhKyLlO9r3.WVOkHDe16JTCKCErcABhElD5CF7ZwQ.Hm6sVRW")
        .authorities(as);

再重启服务,登录tom

就可以访问/answer和/delete两个方法了!

用户与权限

上面章节,我们学习了定义一个用户的登录,并且设置一个用户具有的授权

我们在登录当前数据库中用户时,又如何确定这个用户的授权呢?

现在登录需要的信息有

1.用户名

2.密码(加密的)

3.用户的所有权限

上面的信息都保存在数据库中,用户名和密码保存在user表里

但是用户的授权(权限)是需要相对复杂的查询才能查询出来的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5WH6V5b-1646625752230)(image-20220307115752528.png)]

上的5张表表示的是用户\角色\权限的关系

我们需要根据用户的id查询出这个用户具有哪些角色

在根据用户的角色查询出它的所有权限

他们之前的关系表有user_role和role_permission

所有这个查询是一个需要连接5张表的sql查询语句

我们需要编写这个sql语句如下

-- 根据用户id进行5表连查获得权限信息
SELECT p.id , p.name
FROM user u
LEFT JOIN user_role ur ON u.id=ur.user_id
LEFT JOIN role r       ON r.id=ur.role_id
LEFT JOIN role_permission rp
							  ON r.id=rp.role_id
LEFT JOIN permission p ON p.id=rp.permission_id
WHERE u.id=11

上面的sql语句运行能够成功的话

要编写到我们的UserMapper中来执行

@Repository
public interface UserMapper extends BaseMapper<User> {

    // 根据用户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\n" +
            "ON r.id=rp.role_id\n" +
            "LEFT JOIN permission p ON p.id=rp.permission_id\n" +
            "WHERE u.id=#{id}")
    List<Permission> findUserPermissionsById(Integer id);

    // 根据用户名查询用户信息
    @Select("select * from user where username=#{username}")
    User findUserByUsername(String username);

}

UserMapper编写完毕

推荐大家进行测试

测试类中编写测试代码如下

@Autowired
UserMapper userMapper;
@Test
public void userTest(){
    // 先运行根据用户名获得用户对象的方法
    //  id:11 学生用户名为st2     id:3 讲师用户名为tc2
    User user=userMapper.findUserByUsername("tc2");
    // 根据用户对象中的id属性查询这个用户的所有权限
    List<Permission> list=userMapper
                        .findUserPermissionsById(user.getId());
    System.out.println(user);
    for(Permission p: list){
        System.out.println(p);
    }
}

Spring-Security数据库用户登录

上面章节,我们已经创建了必要的登录配置类,以及响应的连接数据库的Mapper方法

下面我要就来完成数据库中用户的登录功能

Spring-Security框架已经设置要的登录流程中的关键步骤:

  • 控制器的编写
  • 用户名密码的获取
  • 用户输入的密码和数据库中密码的对比验证

都由Spring-Security框架封装好了,不需要咱们编写

我们只需要完成Spring-Security框架提供的一个接口

实现这个接口的方法,具体来说就是根据用户输入的用户名,获得这个用户的相关信息,这些信息包括

  • 用户名
  • 密码
  • 所有权限
  • 其它信息…

我们现在需要做的就是编写一个类,实现这个接口,按要求编写接口中的方法并返回正确的返回值,最后通过一个配置实现和Spring-Security框架的关联,其它都由Spring-Security框架完成

我们打开当前项目的service包中的impl包

这个包里创建一个UserDetailsServiceImpl类

代码如下

// 当前类要保存到Spring容器中以便Spring-Security使用
// 这个类实现的接口UserDetailsService,是Spring-Security框架
// 提供的一个接口,用于我们实现数据库登录
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    // loadUserByUsername方法是UserDetailsService接口中声明的
    // 方法的功能是根据用户输入的用户名返回这个用户的详情信息(详情\Details)
    // 返回值UserDetails也是Spring-Security提供的,
    // 使用Spring-Security框架识别的用户信息
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 方法的参数就是用户登录时输入的用户名
        // 1. 根据用户名获得用户对象
        User user=userMapper.findUserByUsername(username);
        // 2. 验证用户对象是不是null
        if(user==null){
            // 如果是null,表示用户名不存在,返回null表示登录失败
            return null;
        }
        // 3. 根据用户id查询所有权限
        List<Permission> permissions=userMapper
                             .findUserPermissionsById(user.getId());
        // 4. 将权限集合转换为String数组
        String[] auth=new String[permissions.size()];
        int i=0;
        for(Permission p:permissions){
            auth[i]=p.getName();
            i++;
        }
        // 5. 创建UserDetails对象,保存信息获得用户详情
        UserDetails details= org.springframework.security.core.
                userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(auth)
                // 设置当前账号是否锁定,false表示锁定
                .accountLocked(user.getLocked()==1)
                // 设置当前账号是否可用,false表示可用
                .disabled(user.getEnabled()==0)
                .build();
        // 6. 返回用户详情UserDetails对象
        // 千万别忘了返回details
        return details;
    }
}

到此为止我们只是编写了一个类而已

没有和Spring-Security框架进行关联,我们编写的代码就不能真正生效

要想生效,需要经过SecurityConfig类中进行登录配置

我们将SecurityConfig类修改代码如下

// 下面的配置是让我们编写的UserDetailsServiceImpl类生效的
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 让Spring-Security框架进行登录操作时
    // 调用我们编写的userDetailsService中的方法
    auth.userDetailsService(userDetailsService);

}

重启服务

就能使用数据库中的用户来登录了

建议测试st2和tc2用户,密码都是888888

设置页面权限

一个网站,不应该所有页面都需要登录之后才能访问

我们如果想将学生首页设置为不需要登录就能访问的状态

可以通过修改配置文件来实现

SecurityConfig类添加新的configure方法实现代码如下

// 配置页面权限的方法
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()  // 设置禁用防跨域攻击
        .authorizeRequests()  // 开始设置页面访问权限
        .antMatchers(// 指定路径
                "/index_student.html",
                "/css/*",
                "/js/*",
                "/img/**",
                "/bower_components/**"
        ).permitAll()  // 上述路径全部放行(不登录就能访问)
        .anyRequest()  // 除此之外的其它请求
        .authenticated() // 需要登录才能访问
        .and()         // 这是个分割,上面配置已经完毕下面编写新配置
        .formLogin();  // 支持表单登录
}

重启服务

测试是否访问学生首页不需要登录

方法其它页面还需要登录

自定义登录页

Spring-Security框架提供的登录页功能单一

没有中文,没有个性化设置

我们希望能够使用自己的登录页面来替换掉

也是在上面的配置中继续编写即可

// 配置页面权限的方法
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()  // 设置禁用防跨域攻击
        .authorizeRequests()  // 开始设置页面访问权限
        .antMatchers(// 指定路径
                "/index_student.html",
                "/css/*",
                "/js/*",
                "/img/**",
                "/bower_components/**",
                "/login.html"
        //      ↑↑↑↑↑↑↑↑↑↑↑↑↑
        ).permitAll()  // 上述路径全部放行(不登录就能访问)
        .anyRequest()  // 除此之外的其它请求
        .authenticated() // 需要登录才能访问
        .and()         // 这是个分割,上面配置已经完毕下面编写新配置
        .formLogin()  // 支持表单登录
        .loginPage("/login.html")  // 配置登录页为login.html
        .loginProcessingUrl("/login") // 配置表单提交登录信息的路径
        .failureUrl("/login.html?error") // 配置登录失败跳转的路径
        .defaultSuccessUrl("/index_student.html") //登录成功后跳转的页面*
        .and() //登录设置完成,开始设置登出
        .logout()  // 开始设置登出
        .logoutUrl("/logout")  // 设置登出路径
        .logoutSuccessUrl("/login.html?logout");//设置登出成功后跳转的页面
    /*
        defaultSuccessUrl设置的是登录成功时默认跳转的页面
        特指用户没有指定要访问的页面时,登录成功时跳转的页面
        如果用户指定的要跳转的页面,登录成功时优先访问用户指定的页面
     */

}

经过上面的配置

我们的项目支持了我们自己编写的登录页面

并且支持登出功能

实现注册功能

分析注册业务流程

在这里插入图片描述

注册业务流程描述

1.学生在注册页面填写表单信息

2.点击"注册"按钮将表单提交给Controller控制器

3.Controller控制器接收到表单信息之后,调用业务逻辑层(Service)方法

4.Service方法中判断邀请码是否正确,手机号是否已经被注册,密码加密等操作

5.Service中调用Mapper方法新增用户

6.最后由控制器返回新增结果到页面显示

在这里插入图片描述

英文

Enable:启用\启动

Global:全局\全球

Adapter:适配器

Configurer:配置

Authentication:授权

今后几乎所有auth开头的单词都是授权的意思,只是不同的词性

Memory:记忆\计算机中就是内存

role:角色

permission:权限

Details:详情

default:默认

学校中的用户和权限

用户 中间表 角色 中间表 权限

杨过 学生 签到

令狐冲 任课老师 成绩管理

欧阳锋 班主任 学生管理

乔峰 年级组长 课程管理

段誉 教导主任 教职工管理

洪七公 校长 学校倒闭

表与表之间的关系

1.一对一

2.一对多(多对一):

班级和学生

部门和员工

特征:在多(学生表)的一方,保存一个一的一方(班级表)的id列

一对多是所有关系中最常见的关系

3.多对多

传统义务教育阶段的老师和班级

一个老师给多个班级讲课

一个班级被多个老师教

特征:在两张表之间,创建一个关系表

关系表中保存班级和老师的关系

Spring-Security登录流程

登录流程本身并不是重点内容,重点内容时了解我们实现登录需要编写的代码

1.用户输入用户名密码点击提交

2.Spring-Security提供的控制器会接收登录请求获得用户名和密码

3.Spring-Security会调用我们编写的loadUserByUsername方法获得用户详情

4.Spring-Security获得用户详情之后会验证用户输入的密码和数据库的密码是否匹配,如果匹配进行下一步,如果不匹配显示登录失败

5.匹配成功会将当前登录用户的相关信息(包括权限)保存到session会话中,以实现登录状态的保持

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值