SpringSecurityFoundation

SpringSecurityFoundation

一. 关于Spring Security

  • Spring Security框架主要解决了认证与授权相关的问题。
  • 认证信息(Authentication):表示用户的身份信息
  • 认证(Authenticate):识别用户的身份信息的行为,例如:登录
  • 授权(Authorize):授予用户权限,使之可以进行某些访问,反之,如果用户没有得到必要的授权,将无法进行访问

1.1 添加依赖

  • 依赖
            <!-- Spring Boot支持Spring Security的依赖项,用于处理认证与授权 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>
    

1.2 Spring Security框架的典型特征

当添加了spring-boot-starter-security依赖后,在启动项目时执行一些自动配置,具体表现有

1.2.1 所有请求必须登录

  • 所有请求(包括根本不存在的)都是必须要登录才允许访问的,如果未登录,会自动跳转到框架自带的登录页面

1.2.2 当尝试登录时

  • 当尝试登录时,如果在打开登录页面后重启过服务器端,则第1次的输入是无效的
  • 具体原因参见后续的CSRF相关内容

1.2.3 默认的用户名是user

  • 默认的用户名是user,密码是在启动项目是控制台提示的一段UUID值,每次启动项目时都不同
  • UUID是通过128位算法(运算结果是128个bit)运算得到的,是一个随机数,在同一时空是唯一的,通常使用32个十六进制数来表示,每种平台生成UUID的API和表现可能不同,UUID值的种类有2的128次方个,即:3.4028237e+38,也就是340282366920938463463374607431768211456

1.2.4 当登录成功后,会自动跳转到此前尝试访问的URL

1.2.5 当登录成功后,可以通过 /logout 退出登录

1.2.6 默认不接受普通POST请求

  • 默认不接受普通POST请求 如果提交POST请求,将响应403(Forbidden)
  • 具体原因参见后续的CSRF相关内容

二. 关于Spring Security的配置

2.1 关闭所有配置

  • 关闭所有配置
    @Slf4j
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         
        @Override
        protected void configure(HttpSecurity http) throws Exception {
         
            // super.configure(http); // 不保留配置
        }
    }
    

2.2 默认登录页控制 formLogin()

  • 若有此配置则需要权限时弹出默认登录页面而不是403
    @Slf4j
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         
        @Override
        protected void configure(HttpSecurity http) throws Exception {
         
            // 如果调用以下方法,当Security认为需要通过认证,但实际未通过认证时,就会跳转到登录页面
            // 如果未调用以下方法,将会响应403错误
            http.formLogin();
        }
    }
    

2.3 请求的授权访问(访问控制)

  • 举例
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         
        // 白名单
        // 使用1个星号,可以通配此层级的任何资源,例如:/admin/*,可以匹配:/admin/add-new、/admin/list,但不可以匹配:/admin/password/change
        // 使用2个连续的星可以,可以通配若干层级的资源,例如:/admin/**,可以匹配:/admin/add-new、/admin/password/change
        String[] urls = {
         
                "/doc.html",
                "/**/*.css",
                "/**/*.js",
                "/swagger-resources",
                "/v2/api-docs",
        };
        // 配置授权访问
        // 注意:以下授权访问的配置,是遵循“第一匹配原则”的,即“以最先匹配到的规则为准”
        // 例如:anyRequest()是匹配任何请求,通常,应该配置在最后,表示“除了以上配置过的以外的所有请求”
        // 所以,在开发实践中,应该将更具体的请求配置在靠前的位置,将更笼统的请求配置在靠后的位置
        http.authorizeRequests() // 开始对请求进行授权
                .mvcMatchers(urls) // 匹配某些请求
                .permitAll() // 许可,即不需要通过认证就可以访问
                .anyRequest() // 任何请求
                .authenticated() // 要求已经完成认证的
        ;
    }
    

2.4 使用自定义账号登录

2.4.1 指定明文密码登录

  • 举例
    @Slf4j
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
         
        /***
         * 指定密码不需要经过加密处理
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
         
            return NoOpPasswordEncoder.getInstance();
        }
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
         
            log.debug("用户名:{}", s);
            // 假设正确的用户名是root,匹配的密码是1234
            if (!"root".equals(s)) {
         
                log.debug("此用户名没有匹配的用户数据,将返回null");
                return null;
            }
            log.debug("用户名匹配成功!准备返回此用户名匹配的UserDetails类型的对象");
            UserDetails userDetails = User.builder()
                    .username(s)
                    .password("1234")
                    .disabled(false) // 账号状态是否禁用
                    .accountLocked(false) // 账号状态是否锁定
                    .accountExpired(false) // 账号状态是否过期
                    .credentialsExpired(false) // 账号的凭证是否过期
                    .authorities("这是一个临时使用的山寨的权限!!!") // 权限
                    .build();
            log.debug("即将向Spring Security返回UserDetails类型的对象:{}", userDetails);
            return userDetails;
        }
    }
    

2.4.2 使用数据库中的账号登录

2.4.2.1 配置数据库账号的密码加密方式为BCryptPasswordEncoder()
  • 举例
    root $2a$10$VgJZ/AsasCll6SE7WGXsWONCGdpBT8Z2dBSXiNVGOHiJS7GETW6nm
    
2.4.2.2 编写sql 推算出是要的VO类型
  • 举例
    select username, password, enable from ams_admin where username=?
    
2.4.2.3 编写VO类
  • pojo.vo.AdminLoginInfoVO
    @Data
    @Accessors(chain = true)
    public class AdminLoginInfoVO implements Serializable {
         
        private String username;
        private String password;
        private Integer enable;
    }
    
2.4.2.4 编写mapper层
  • 举例
    @Mapper
    public interface AdminLoginInfoMapper {
         
        //@Select(" select username, password, enable from ams_admin where username = #{username}")
        AdminLoginInfoVO getLoginInfoByUsername(String username);
    }
    
2.4.2.5 编写mapper映射文件
  • 举例
    <!-- AdminLoginInfoVO getLoginInfoByUsername(String username); -->
    <select id="getLoginInfoByUsername" resultType="cn.tedu.csmall.passport.pojo.vo.AdminLoginInfoVO">
        SELECT 
            username, password, enable 
        FROM 
             ams_admin 
        WHERE 
            username=#{
         username}
    </select>
    
2.4.2.6 使用BCryptPasswordEncoder()密码编码器
  • 举例
    /***
     * 指定BCryptPasswordEncoder密码编码器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
         
        return new BCryptPasswordEncoder();
    }
    
2.4.2.7 登录逻辑完整
  • 举例
    @Slf4j
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
         
        @Resource(name = "adminLoginInfoMapper")
        private AdminLoginInfoMapper mapper;
    
        /***
         * 指定密码不需要经过加密处理
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
         
            return new BCryptPasswordEncoder();
        }
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
         
            AdminLoginInfoVO adminLoginInfoVO = mapper.getLoginInfoByUsername(s);
            log.debug("用户名:{}", adminLoginInfoVO.getUsername());
            // 假设正确的用户名是root,匹配的密码是1234
            if (adminLoginInfoVO==null) {
         
                log.debug("此用户名没有匹配的用户数据,将返回null");
                return null;
            }
            log.debug("用户名匹配成功!准备返回此用户名匹配的UserDetails类型的对象");
            UserDetails userDetails = User.builder()
                    .username(adminLoginInfoVO.getUsername())
                    .password(adminLoginInfoVO.getPassword())
                    .disabled(false) // 账号状态是否禁用
                    .accountLocked(false) // 账号状态是否锁定
                    .accountExpired(false) // 账号状态是否过期
                    .credentialsExpired(false) // 账号的凭证是否过期
                    .authorities("这是一个临时使用的山寨的权限!!!") // 权限
                    .build();
            log.debug("即将向Spring Security返回UserDetails类型的对象:{}", userDetails);
            return userDetails;
        }
    }
    

2.5 配置post可用

Spring Security框架设计了“防止伪造的跨域攻击”的防御机制,所以,默认情况下,自定义的POST请求是不可用的,简单的解决方案就是在Spring Security的配置类中禁用这个防御机制即可,例:

  • 举例
    @Slf4j
    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         
        @Override
        protected void configure(HttpSecurity http) throws Exception {
         
        //关闭防止伪造的跨域攻击
            http.cors().disable();
        }
    }
    

2.6 使用前后端分离的登录

2.6.1 关闭自带的登录页面

  • 配置类不再调用
    http.formLogin()
    

2.6.2 使用控制器接收客户端登录请求

  • 举例
    @PostMapping("/login")
    public AdminLoginInfoVO login(AdminLoginInfoVO adminLoginInfoVO) {
         
        
        return adminLoginInfoVO;
    }
    

2.6.3 给登录控制器添加白名单

  • 举例
        String[] urls = {
         
                "/doc.html",
                "/**/*.css",
                "/**/*.js",
                "/swagger-resources",
                "/v2/api-docs",
                "/login" //添加白名单
        };
    
        // 配置授权访问
        // 注意:以下授权访问的配置,是遵循“第一匹配原则”的,即“以最先匹配到的规则为准”
        // 例如:anyRequest()是匹配任何请求,通常,应该配置在最后,表示“除了以上配置过的以外的所有请求”
        // 所以,在开发实践中,应该将更具体的请求配置在靠前的位置,将更笼统的请求配置在靠后的位置
        http.authorizeRequests() // 开始对请求进行授权
                .mvcMatchers(urls) // 匹配某些请求
                .permitAll() // 许可,即不需要通过认证就可以访问
                .anyRequest
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值