• 【SpringSecurity】SpringSecurity2.7.x 的使用(03)


    3. 登录流程源码分析

    在这里插入图片描述
    通过源码我们发现,InmermoryUserDetailManger中的loadUserByUsername方法最核心的。而且该类实现UserDetailService接口。 那么我们就可以自定义一个类并实现该接口UserDetailService。而且我们自定义的类也要交于容器来管理。

    4. 模仿从数据库中获取用户

    1. 编写配置类
    package com.ykq.springsecurity04.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;
    
    /**
     * @program: springsecurity04
     * @description:
     * @author: 闫克起2
     * @create: 2022-11-01 15:11
     **/
    @Configuration
    public class MySpringSecurityConfig {
    
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        //定义表单规则以及过滤规则
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
              //表单的规则
             http.formLogin()
                     .loginPage("/login.html")
                      //该路径没有对应的控制层 交于springsecurity完成登录功能
                     .loginProcessingUrl("/login")
                     .successForwardUrl("/success")
                     .permitAll();
             //禁用跨域伪造
            http.csrf().disable();
    
            //其他请求必须认证后才能访问
            http.authorizeRequests().anyRequest().authenticated();
    
            return http.build();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    注意要将BCryptPasswordEncoder设置为bean,否者不能进行密码验证

    1. 自定义一个实现类实现UserDetailsService 接口,
    package com.ykq.springsecurity04.service;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    /**
     * @program: springsecurity04
     * @description:
     * @author: 闫克起2
     * @create: 2022-11-01 15:18
     **/
    @Service //该类对象的生命周期交于spring容器管理
    public class MyUserService implements UserDetailsService {
        //注入 dao对象---根据账户查询用户信息
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            /*
                String username, String password,
                boolean enabled 是否可以, boolean accountNonExpired,账户是否没有过期
                    boolean credentialsNonExpired, 密码是否没有过期
                    boolean accountNonLocked,
                    Collection authorities: 当前账户具有的权限
             */
            if("admin".equals(username)){
                Collection<GrantedAuthority> authorities =new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("user:query"));
                authorities.add(new SimpleGrantedAuthority("user:delete"));
                authorities.add(new SimpleGrantedAuthority("user:update"));
                authorities.add(new SimpleGrantedAuthority("user:insert"));
                User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
                        true,true,true,authorities
                        );
                return user;
            }else if("ykq".equals(username)){
                Collection<GrantedAuthority> authorities =new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("user:query"));
                authorities.add(new SimpleGrantedAuthority("user:export"));
                User user=new User(username,"$2a$10$k8XcVVIyNaXOfYR/q5v1veda/koy21JJ6FfGYQeRcubjuv1qax/v6",true,
                        true,true,true,authorities
                );
                return user;
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    1. 从数据库中来获得用户十分简单,只需要实现UserDetailsService 接口,重写loadUserByUsername方法就行了,返回一个UserDetails 类型的数据。
    2. UserDetails 是一个接口,他有一个常用的实现类User,注意User是SpringSecurity包中的。
    3. 用户名和密码十分容易获取,而对于权限信息authorities需要我们想办法从数据库中封装了。

    5. 常见授权认证数据库表的设计

    在这里插入图片描述

    综合案例

    使用springboot + thymeleaf+ mybatis+ druid+springsecurity模拟一下从数据库中查询用户,完成授权认证

    前后端不分离

    1. 引入jar包

        <dependencies>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.30version>
            dependency>
            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
                <version>1.2.12version>
            dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>2.2.2version>
            dependency>
            
            <dependency>
                <groupId>org.thymeleaf.extrasgroupId>
                <artifactId>thymeleaf-extras-springsecurity5artifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-thymeleafartifactId>
            dependency>
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-securityartifactId>
            dependency>
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
                <version>5.8.4version>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>org.springframework.securitygroupId>
                <artifactId>spring-security-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    2. 配置application.yml文件

    mybatis:
      configuration:
        # 开启mybatis日志输出(可以打印出sql执行的日志)
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      # mybatis的mapper扫描
      mapper-locations: classpath:/mapper/*.xml
    spring:
      # 数据源
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          initial-size: 5
          max-active: 20
          max-wait: 3000
          min-idle: 5
          password: 1234
          url: jdbc:mysql://localhost:3306/shiro?serverTimezone=Asia/Shanghai
          username: root
    server:
      port: 80
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3. 数据库层

    1. 首先创建用户实体类
    package com.aaa.springsecuritydemo02.entity;
    
    import lombok.Data;
    
    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/11/1 22:40
     * @description :
     */
    @Data
    public class Users {
        private Integer userid;
        private String username;
        private String userpwd;
        private String sex;
        private String address;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为了和security自带的User区分,这里将我们自己的实体类名字叫Users

    1. 创建UserDao及UserDao.xml
    @Mapper
    public interface UsersDao {
    
        Users getUserByUsername(String username);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.aaa.springsecuritydemo02.dao.UsersDao">
    
        <select id="getUserByUsername" resultType="com.aaa.springsecuritydemo02.entity.Users">
            select *
            from user
            where username = #{username}
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. PermissonDao和PermissonDao.xml

    返回UserDetails 的时候需要权限信息,写这个接口是为了根据用户Id查询权限消息

    Permisson实体类

    @Data
    public class Permission {
        private Integer id;
        private String pername;
        private String percode;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    PermissionDao 接口

    @Mapper
    public interface PermissionDao {
    
        /**
         * 根据用户id查询对应的权限
         */
        List<Permission> findPermissionByUserid(Integer userid);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    PermissionDao.xml文件

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.aaa.springsecuritydemo02.dao.PermissionDao">
    
        <select id="findPermissionByUserid" resultType="com.aaa.springsecuritydemo02.entity.Permission">
            select distinct p.*
            from user_role ur
                     join role_permission rp
                          on ur.roleid = rp.roleid
                     join permission p on rp.perid = p.perid
            where ur.userid = #{userid}
        select>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意sql语句

    4. service层

    package com.aaa.springsecuritydemo02.service;
    
    import com.aaa.springsecuritydemo02.dao.PermissionDao;
    import com.aaa.springsecuritydemo02.dao.UsersDao;
    import com.aaa.springsecuritydemo02.entity.Permission;
    import com.aaa.springsecuritydemo02.entity.Users;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/11/1 15:41
     * @description :
     */
    @Service
    public class MyUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UsersDao usersDao;
        @Autowired
        private PermissionDao permissionDao;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            Users user = usersDao.getUserByUsername(username);
    
            if (user != null) {
                Collection<GrantedAuthority> authorities = new ArrayList<>();
                List<Permission> permissions = permissionDao.findPermissionByUserid(user.getUserid());
                //把上面的权限封装authorities中----jdk1.8 Stream流--
    			//authorities=permissions.stream()
    			//        .map(item->new SimpleGrantedAuthority(item.getPercode()))
    			//        .collect(Collectors.toList());
    
                for (Permission item : permissions) {
                    SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(item.getPercode());
                    authorities.add(simpleGrantedAuthority);
                }
                return new User(username, user.getUserpwd(), authorities);
            }
    
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    注意要交给Spring管理 @Service

    5. SecurityConfig配置类

    package com.aaa.springsecuritydemo02.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.web.SecurityFilterChain;
    
    
    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/10/29 15:49
     * @description :
     */
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig {
    
    	@Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            //认证
            http
                    .formLogin()
                    //默认登录页面
                    .loginPage("/login.html")
                    //登录的处理路径,无需自己创建该路径的业务处理功能。
                    .loginProcessingUrl("/login")
                    //通过这两个可以修改表单中name的值
                    //登录成功跳转路径
                    .successForwardUrl("/user/success")
    
                    //放行
                    .permitAll();
            //授权
            http
                    //禁用跨域请求伪造 csrf
                    .csrf().disable();
            http
                    .authorizeRequests()
                    //其他的请求都需要认证
                    .anyRequest().authenticated();
    
    
            return http.build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    配置文件要配置密码加密器,否者程序不知道要用那种方式来匹配密码

    6. 控制层

    package com.aaa.springsecuritydemo02.controller;
    
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.context.SecurityContextImpl;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.security.Principal;
    
    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/10/31 14:48
     * @description :
     */
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        /**
         * 认证成功后,会把用户的信息封装到Authentication该类中,
         * 并且该类存放在SecurityContext对象中---等价于session
         * @return Authentication
         */
        @GetMapping("/info")
        public Authentication authentication() {
            SecurityContext securityContext = SecurityContextHolder.getContext();
            Authentication authentication = securityContext.getAuthentication();
            //通过Authentication通常可以拿到Principal,里面存放着用户信息
            //Object principal = authentication.getPrincipal();
            //System.out.println(principal);
            return authentication;
        }
    
        @PostMapping("/success")
        public ModelAndView success(ModelAndView modelAndView){
            modelAndView.setViewName("success");
            return modelAndView;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    7. 页面

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <head>
        <meta charset="UTF-8">
        <title>登录成功页面title>
    head>
    <body>
    <a href="#" sec:authorize="hasAuthority('/user/query')">查询用户a><br>
    <a href="#" sec:authorize="hasAuthority('/user/delete')">删除用户a><br>
    <a href="#" sec:authorize="hasAuthority('/user/update')">修改用户a><br>
    <a href="#" sec:authorize="hasAuthority('/user/insert')">添加用户a><br>
    <a href="#" sec:authorize="hasAuthority('/user/export')">导出用户a><br>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 注意thymeleaf中要想使用sec语法需要引入thymeleaf-extras-springsecurity5的jar包,页面头部写的
      只是来规定语法,不让报错并能写代码时给出提示,并没有实际作用。真正起作用的是thymeleaf-extras-springsecurity5包。
    • 其实不写头也可以,只是没有语法提示,可能页面会报错,但实际运行不影响
  • 相关阅读:
    粒子群算法优化双向长短期记忆神经网络的多输入单输出回归分析,粒子群算法优化gru神经网络的多输入回归分析
    Kafka Shell命令交互
    听说现在流行卷应用?开发者们都开始调用文心API开发了?!
    AI | 第6章 深度学习 TensorFlow2 使用 keras 构建神经网络
    Windows Server 2016 ServU-v6.30
    ChatGPT规模化服务的经验与教训
    Mysql服务器主从同步搭建
    衍三的硬件笔记之如何选择MOS管
    二维码解析易语言代码
    设计模式之【门面模式(外观模式)】
  • 原文地址:https://blog.csdn.net/qq_60969145/article/details/127647599