Spring Security是什么,以及如何在Spring Boot项目中整合Spring Security并且使用它,下面我们通过一个登录案例简单介绍一下Spring Security。
1.什么是Spring Security?
在了解Spring Security之前,我们是不是应该先思考一个问题,我们自己写的web案例一般都需要先登录,之后登录之后才能访问其他页面,或者说我们不同的用户登录之后能进行的操作不同,比如系统管理员和普通用户所能操作的功能是有所区别的。那我们如何解决这个问题?
不错,这时候我们就需要用到一些安全框架,比较Apache 旗下的Shiro、Spring家族中的Spring Security,Shiro相比Spring Security而言,比较简单易上手,不过我们这篇文章主要讲解Spring Security的简单应用。
接着上面的话题,Spring Security提供了一组可用在Spring应用上下文中配置的Bean,充分的利用了Spring IOC、DI(依赖注入)、AOP(面向切面编程)的功能,提供了生命式的安全访问控制功能,提高了开发效率。
Spring Security的本质就是一组过滤器链(16个),通过创建大量的filter和interceptor来进行请求的验证和拦截,以此来达到安全校验和鉴权的效果。
2.如何使用Spring Security?
要使用Spring Security很简单,我们只需要引入Spring Security 的相关依赖,就可以得到一个Spring Security的简单示例。
创建Spring Boot项目 ,引入相关的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<!--security 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--JWT 依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
配置yaml文件,主要配置数据源:
server:
#配置服务端口号
port: 8081
spring:
datasource:
#配置数据库驱动
driver-class-name: com.mysql.cj.jdbc.Driver
#配置数据库地址,务必加上时区
url: jdbc:mysql://localhost:3306/yeb?useUnicode=true%&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#数据库账户名称
username: root
#数据库密码
password: XXXXX
然后启动Spring Boot项目,你会发现,咦,我明明没有编写登录页面,为什么它需要我登录?
没错,我们的Spring Security已经生效了,尽管我们什么也没做,只是引入了一个依赖而已,这是Spring Security的默认行为,只要我们添加了Spring Security的依赖,后续我们的所有操作都必须要通过它的验证才能继续。
它的账户是user,密码在我们后端控制台,每次生成的密码都是随机的,我们需要到后端控制台复制密码。
输入用户名,密码,就可以跳转到我们原来的页面。
通过这个简单的使用, 你现在是不是对Spring Security有了大概的认识了。
3.Spring Security 的进阶使用,UserDetailsService的详解。
我们当然不是只能使用它提供给我们的登录页面,我们可以通过相关的配置,将它的登录页面,替换为我们本来的的登录页面。
当我们需要自定义它的验证逻辑时,我们就需要来实现UserDetailsService这个接口
点开它我们发现它里面只有一个方法,并且这个方法的作用是通过用户名加载用户,用户名怎么来的,看见后面的参数了么,它就是前端传递过来的用户名称,而且返回一个UserDetails的一个类,那这个类又是什么呢?,我们继续点开看看。
点开这个类之后,我们发它仍然是一个接口,并且提供了很多方法,接口意味着我们要想使用它是不是就得现实现它,说明UserDetails在Spring Security中已经有了一个实现类了,接下来我们继续寻找UserDetails的实现类,并且观察它的实现类。
我们ctrl+alt 点击UserDetails,就可以看到它的实现类,我们发现它确实存在一个实现类,并且类名为User,我们要注意把这个User区分开,这是Spring Security提供的User,同样的我们点开观察它。
这个类里面的东西很多,我就不全部截图啦,大家可以自己点开看一下。总之,这个实现类User的意思大概就是根据客户端传递过来的用户名,它会去查询数据库,并且返回查到的相应信息,包括密码、用户名、权限信息、是否可用,账户是否失效等等。
我们在回到UserDetailsService这个类,我们发现它抛出了一个异常,这个异常就是没有查询到数据时,它就会抛出这个异常。
4.PasswordEncoder详解。
要实现Spring Security的自定义验证流程,我们除了实现USerDetailsService这个接口,我们还需要实现PasswordEncoder。当我们没有进行任何配置的时候,Spring Security它已经自带了一个实现了PasswordEncoder的实例,这就是我们刚刚运行时,控制台打印密码是那么一长串字符的原因。
PasswordEncoder接口包含三个方法,如下:
PasswordEncoder的实现类很多,每一个实现类都代表一种加密算法,官方推荐我们使用BCryptPasswordEncoder这种算法。
大家也可以ctrl+alt点开看一看里面的内容,
这里我们可以通过一个测试类来测试它的加密效果:
//创建BCryptPasswordEncoder对象
PasswordEncoder pe=new BCryptPasswordEncoder();
String encode = pe.encode("123456");//调用encode方法加密,得到加密后的字符串
System.out.println("加密后的密码 = " + encode);
boolean matches = pe.matches("123456", encode);//调用matches方法匹配原密码和加密之后的密码
System.out.println("原密码与加密后的密码是否相等 = " + matches);
运行结果如上,通过这个小例子是不是对PasswordEncoder接口的使用有了更加深入的了解。
5.自定义登录登录逻辑
了解了UserDetailsService和PasswordEncode之后,我们是不是就能自定义登录逻辑了,
现在我们回顾一下,给大家三分钟回想一下UserDetailsService是干啥的,有哪些方法,通过它我们可以干些啥,PasswordEncoder的方法及作用,可以用在什么场景,大家想一想,思考一下。
在实现我们的自定义登录逻辑之前,我们的Spring Securit要求我们进行自定义登录逻辑时,我们的容器中必须有一个PasswordEncoder的实例存在,所以我们不能想上面那个例子一样直接用new的方式,我们需要写一个配置类,将它注入到容器当中。
代码如下:
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getpw(){
return new BCryptPasswordEncoder();
}
}
接下来创建一个类去实现UserDetailsService接口,重写UserDetails方法,我们在serve层创建一个类UserDetailsServiceImpl去实现UserDetailsService接口,并且重写UserDetails方法。如图。
OK,现在想一想我们应该做什么,它只有这个方法,这个方法有什么用。
具体如下:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pe;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库判断用户名是否存在,如果不存在则抛出异常UsernameNotFoundException
//怎么查数据库?这应该都会吧,我这里就不弄查数据库的部分了,节省时间,写几个小时了,这里我们就自己造一个数据吧,
//假设我们从数据中查到的用户名为YangJunDong,我们直接判断就是
if(!"YangJunDong".equals(username)) { //注意这个username是传递过来的参数,也就是前端输入框中的名字
throw new UsernameNotFoundException("用户名不存在!");
}
//2.将查询出来的密码(数据库中已加密的密码)进行解析,或者直接放到实现类的构造函数中?
/*
这里咱也不查数据库了,自己造一个密码吧,但是这个密码是数据库中的,我们得造一个加密的密码
之前写的配置类管用了吧,我们直接Resource注入进来就好了
*/
String password = pe.encode("123");
//通过数据库查到密码之后怎么做?我们这里采用直接放到到实现类(UserDetailsService的最终实现类是啥来着)的构造函数中,代码如下
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList( "admin" ));
//这里的三个参数,前两个用户名密码,不必多说,但是第三个是权限信息,同样的我们没查数据库,也只能造一个,使用AuthorityUtils工具类中
//的commaSeparatedStringToAuthorityList方法,给其设置权限,我设置的权限是admin的权限,当然也可以短号隔开设置多个权限,一个人也存有多种权限的情况的
}
}
OK,到这里我们就成功自定义了登录逻辑,现在在次运行项目,然后我们会发现,控制台居然没有打印密码了,说明我们已经更改掉了它的默认方式,输入我们从数据库(自定义)的账号和密码。
登录成功!!!
那如果我们输出错误的密码,或者错误的用户名呢?
OK,可以看到输入错误的信息是无法通过验证的。
以上就是这篇文章的内容了,这篇文章主要说了Spring Security的作用,以及UserDetailsService和PasswordEncoder接口的作用和使用方法,并且通过这个两个接口自定义了登录逻辑,也就是让它去查数据库,实打实的来,而不是用它默认的方法生成账户和密码,如果有错误的地方请大家指正。
后续会陆续更新自定义登录页面(将登录页面换成我们自己的登录页面)和Spring Security的其他功能。