深入浅出SpringSecurity

SpringSecurity学习

SpringSecurity简介

安全框架的概述

什么是安全框架?是为了解决安全问题的框架。如果没有安全框架,我们需要手动处理每个资源的访问控制,非常麻烦。使用安全框架,我们可以通过的配置方式实现对资源的访问控制。

常用的安全框架概述

  1. SpringSecurity:Spring家族的一员,是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文配置的Bean,充分利用Spring IOC、DI(控制反转 Inversion of Control)、DI(依赖注入 Dependency Injection)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量的代码的重复工作。
  2. Apache Shiro:一个功能强大且易于使用的java安全框架,提供了认证和授权、加密、和会话管理。

概述

Spring Security是一个高度自定义的安全框架。利用 Spring 1oC/Dl和AOP功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。

使用Spring Secruity 的原因有很多,但大部分都是发现了javaEE的 Servlet 规范或 EJB规范中的安全功能缺乏典型企业应用场景。同时认识到他们在 WAR 或 EAR 级别无法移植。因此如果你更换服务品环境,还有大量工作去重新配置你的应用程序。使用 Spring Security解决了这些问题,也为你提供许多其他有用的、可定制的安全功能。

正如你可能知道的两个应用程序的两个主要区域是认证"和"授权”(或者访问控制)。

这两点也是 Spring Security 重要核心功能。

“认证”,是建立一个他声明的主体的过程(一个“主体”—般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。

“授权"指确定—个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情。

源码分析

UserDetailsService

深入浅出SpringSecurity_第1张图片

UserDetails

深入浅出SpringSecurity_第2张图片
深入浅出SpringSecurity_第3张图片

User

深入浅出SpringSecurity_第4张图片

可以看到这个user类,这个user类,可不是我们平时定义的user类。这是SpringSecurity中定义的user类。

它最主要的有两个构造函数。

深入浅出SpringSecurity_第5张图片

它这个password是我们根据userdetailsService的loadUserByUsername(username) 去数据库中查出来的数据。

深入浅出SpringSecurity_第6张图片

是不是很像我们平时开发的逻辑。

具体使用

自定义登录逻辑

在我们使用SpringSecurity做开发的时候,我们其实只有去实现两个接口。

一个是UserDetailsService、另一个就是PasswordEncoder。

自定义username

前面的根据是调用 loadUserByUsername(username) 去数据库查询用户,而要实现登陆的工能,就需要我们来自定义实现,所以就要用到PasswordEncoder接口。用来对用户数的密码加密,同时在和数据库中加过密的密码,来进行匹配。只要匹配成功,就可以实现登陆逻辑。

其实呢我们倒入依赖的时候,它就要实现。在我们的demo中,程序的启动之后控制台会打印一句话。

image-20220410204452767

他的意思就是,自动生成的密码,在demo中我们自定义了,login 方法。

当我们在浏览器中使用login 方法,来实现登录的时候我们会发现。莫名其妙的弹出来一个login界面。我还以为是我自己写的。一想我咋可能写出这么好看的登录界面。确定的是,这肯定不是我自己写的。

深入浅出SpringSecurity_第7张图片

所以呢,这就是SpringSecurity中自带的登录页面,目的就是为了,对用户进行拦截和认证。只要登录的用户才会被授权,进行后续的操作。

它规定的username,就是user,密码就是控制台打印的。

那倒我们要拿着玩意实现我们的登录逻辑嘛,显然是不可能。所以我们要再次基础上自定义我们的登录逻辑。

首先,我们肯定要重写 UserDetailsService 的 loadUserByUsername(username)的方法,为什么呢?

显然不同的用户有不同的username,所赋予的权限也不同。肯定需要重写。

接下来,我们平时的登录业务,大体上都是根据username,去数据库查询,查询到了返回整个对象实体,然后在根据前端用户输入的密码,和数据库查询带过来的对象实体中的密码,进行匹配。

可是,我们也看到,它这个密码显然是加过密了,所以呢,我们也需要对前端传过来的密码,也要进行一个加密,在和数据库中的密码进行匹配。当然了,在真实的开发,数据库当然是不会存明文密码,你懂的。

自定义登录密码

所以呢,我也需要实现这个PasswordEncoder接口。对我们用户输入的密码,进行加密。然后在登录的业务中,进行匹配。

那接下来,我们就看看这个接口。

深入浅出SpringSecurity_第8张图片

常用的也就是,加密和匹配。它这个加密是不可逆的。

深入浅出SpringSecurity_第9张图片
深入浅出SpringSecurity_第10张图片

那我们简单的把玩一下这个 BCryptPasswordEncoder

@SpringBootTest
public class PasswordEncoder_test {

    @Test
    public void test(){
        BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
        //对原始的密码进行加密
        String encode = bc.encode("123");
        System.out.println(encode);
        //原始密码和加过密的进行匹配
        boolean matches = bc.matches("123", encode);
        System.out.println(matches);
    }
}

//测试结果
$2a$10$cl18e/WgokCZsHpvdJWHbO7QOcDxZUWMVn5JtSAdIObpZ08JUs8XW
true

但是在我们的平常使用中springSecurity要求我们在spring的容器中有一个实例,所以我们平时都会一个配置类。配合@Configuration注解。@Bean注解。

在这里,我们拓展一下。使用SpringBoot做开发的同学。对下面的这个配置类,不少见吧。可是你真的知道这两个注解的作用吗?

@Configuration注解,就像当于我们在用Spring开发写的xml配置文件的作用。而@Bean就相当于我们在xml中写的标签。@Bean注解作用在方法上,声明当前方法的返回值是一个Bean。

package com.uin.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author wanglufei
 * @description: SpringSecurity配置类
 * @date 2022/4/10/7:51 PM
 */
@Configuration
public class SpringSecurityConfig{
    /**
     * BCryptPasswordEncoder实例
     *
     * @return org.springframework.security.crypto.password.PasswordEncoder
     * @author wanglufei
     * @date 2022/4/11 8:32 AM
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

自定义登录。

package com.uin.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author wanglufei
 * @description: 实现SpringSecurity中的UserDetailsService
 * @date 2022/4/11/8:43 AM
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder passwordEncoder;

    /**
     * 自定义username
     *
     * @param username
     * @return org.springframework.security.core.userdetails.UserDetails
     * @author wanglufei
     * @date 2022/4/11 8:44 AM
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.根据username去查询数据库,不存在的就会抛出异常UsernameNotFoundException
        if (!"admin".equals(username)) {
            return (UserDetails) new UsernameNotFoundException("用户名不存在");
        }
        //2.把查询的密码(注册是已经加过密)进行解析,或者直接把密码放入构造方法
        String password = passwordEncoder.encode("123");
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(
                "admin,normal"));
    }
}

我们会发现控制台并没有,打印刚才自动生成的密码,

深入浅出SpringSecurity_第11张图片

发现是可以登录的。

当然自定义登录逻辑,远远不止于此。

自定义登录页面

我们不可能,一直用它这个登录页面吧,所以我们需要自定义登录页面。在SpringSecurity中有一个这样的类,提供我们做一些自定义的配置。

深入浅出SpringSecurity_第12张图片

深入浅出SpringSecurity_第13张图片
深入浅出SpringSecurity_第14张图片

记得把super.configure(http)。这个是默认的配置。

页面太丑,反正实现效果了。

但是我们会发现一个问题,我们不登陆也可以访问main.html。这不就bbq了。所以我们要拦截请求。

深入浅出SpringSecurity_第15张图片

但是在SpringSecurity里,它就做授权,授于你权利,让你去那个页面,就很像我们的在SSM中配置的拦截器。所以我们要转换一下概念,入乡随俗嘛。

深入浅出SpringSecurity_第16张图片

这个就起到了,拦截器的作用。

深入浅出SpringSecurity_第17张图片

哇涩,重定向次数过多。想了一下,好像我们去访问login.html页面也被拦截了,有点想一个死递归,死循环。

深入浅出SpringSecurity_第18张图片
深入浅出SpringSecurity_第19张图片

自定义登录失败跳转页面

深入浅出SpringSecurity_第20张图片
深入浅出SpringSecurity_第21张图片

自定义登录参数

深入浅出SpringSecurity_第22张图片

工作流程

spring security 入门教程 详细讲解 - Caesar_the_great - 博客园 (cnblogs.com)

深入浅出SpringSecurity_第23张图片

认证流程(authentication)

深入浅出SpringSecurity_第24张图片

授权流程(authorization)

深入浅出SpringSecurity_第25张图片

优点

  1. 将用户登录,权限控制分离出来,达到和其他控制、逻辑代码完全分离。
  2. 在控制、逻辑代码里面,可以通过spring容器的到我们登录用户的信息,可插拔性的体现。
  3. 自定义的权限控制访问,不但是对某个URL可操控,同时可以对某个方法进行控制。
  4. 提供一些登录相关的操作,如记住我、登录成功跳转页面设定等等。
  5. 安全控制性好,对并发session可控性好。

实现匿名访问

spring security 实现匿名访问 - Caesar_the_great - 博客园 (cnblogs.com)

思路

  1. 标注需要匿名访问的接口
  2. 配置匿名访问

实例

自定义注解 @AnonymousAccess,写在需要匿名访问的接口上:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
/**
     * 功能描述: 登录
     * @param loginVO 用户登录时的账号和密码
     * @Description TODO
     * @return com.rman.iflash.model.R
     * @Author Caesar
     * @Date 10:57 2020/5/3
     **/
    @AnonymousAccess
    @PostMapping("/login")
    public R login(@RequestBody @Validated LoginVO loginVO){
        log.info("用户登录信息{}", loginVO);
        LoginVO authUserDTO = new LoginVO();
        authUserDTO.setUsername(loginVO.getUsername());
        authUserDTO.setPassword(loginVO.getPassword());
        authUserDTO.setCaptchaId(loginVO.getCaptchaId());
        authUserDTO.setCaptchaCode(loginVO.getCaptchaCode());
        Object data = loginService.login(authUserDTO);
        return R.success(data);
    }

在security的配置类的 configure(HttpSecurity http)方法中配置匿名访问:

//查找匿名标记URL
        Map handlerMethods =
                applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set anonymousUrls = new HashSet<>();
        for (Map.Entry infoEntry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (anonymousAccess != null) {
                anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
        anonymousUrls.forEach(s -> log.warn("可以匿名访问的url:{}", s));

http.antMatchers(anonymousUrls.toArray(new String[0])).anonymous()

你可能感兴趣的:(SpringSecurity,数据库,spring,java,spring,boot,shiro)