- Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
- 记住两个类和一个注解:
- WebSecurityConfigurerAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
- Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)
- “认证”(Authentication)
- 身份验证就是验证您的身份凭据,如用户名/用户ID和密码,以验证您的身份是否合法
- 身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用
- “授权” (Authorization)
- 授权发生在系统成功验证您的身份后(即你需要先通过认证证明你的身份合法性,spring security才会去检查你的身份对应的角色,spring security才会对你进行授权验证,对比你请求的资源是不是你对应的角色的权限范围内的,是就返回资源,不是就403)
- 这两个概念是通用的,而不是只在Spring Security 中存在
目录
- 1.导入依赖
- 2.简单上手
- 1.授权(Authorization/or)
- 2.认证(Authentication/en)
- 3.小结
- 4.补充:使用JDBC实现认证
1.导入依赖
- 导入依赖,只需要导入启动器即可,我们可以在原来有的启动器依赖上面修改即可导入,这是因为springBoot的启动器依赖格式都是spring-boot-starter-XXX
<!--Spring Security的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 我们可以观察一下这个依赖为我们导入的其他依赖
- 可以发现它导入了aop依赖和安全配置和安全web依赖
2.简单上手
@EnableWebSecurity //使用注解@EnableWebSecurity
public class Config extends WebSecurityConfigurerAdapter { //继承WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception { //覆写configure()即可
http
.apply(customDsl())
.flag(true)
.and()
...;
}
}
- 按照官方模板创建一个config
package com.thhh.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
1.授权(Authorization/or)
- 需求:首页大家都可以访问,但是其他功能也只有有权限的人才能进行访问
- 注意:按照模板写的方法是对父类的方法的覆写,所以方法具体怎么实现我们可以去参照父类的实现
- 照猫画虎实现覆写的方法
package com.thhh.config;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//链式编程
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求授权规则制定,即对于请求服务器上的资源的授权,antMatchers()用于指定哪些资源请求需要进行授权,
//permitAll()表示这个资源的请求开放给所有人;hasRole()用于在()中指定将antMatchers()中指定的资源的请求权限开放给指定的人
http.authorizeRequests().antMatchers("/").permitAll()
//添加请求验证,对于"/"的访问,允许所有人访问
.antMatchers("/level1/**").hasRole("vip1")
//添加请求验证,对于"/level1/**"的访问,只有角色为vip1的角色可以访问
.antMatchers("/level2/**").hasRole("vip2") //同理
.antMatchers("/level3/**").hasRole("vip3"); //同理
}
}
- 当用户在没有登陆的状态下去请求页面时,不应该被跳转4xx页面,而是应该跳转登录页,示意登陆用户账号之后再去访问,登陆之后如果请求了权限范围之外的页面,直接跳转403,提示权限不够,当然我们也可以定制403页面向用户具体显示提示信息;
- 在springSecurity中,让没有登陆的用户在请求资源的时候跳转登陆页面的方法实现其实很简单,只需要一行代码
http.formLogin();
- 这相当于一个开关,只要我们调用了这个方法,就可以开启spring security中实现让没有登陆的用户在请求资源的时候跳转登陆页面的功能
- 我们可以阅读一下http.formLogin()的源码
/**
* The most basic configuration defaults to automatically generating a login page at
* the URL "/login", redirecting to "/login?error" for authentication failure. The
* details of the login page can be found on
* {@link FormLoginConfigurer#loginPage(String)}
* =====Override=====
* 可见源码的注释中为我们列举了覆写configure()方法的格式/我们可以使用http对象的哪些方法
*
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* authorizeRequests 授权
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
*/
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
- 可以发现,源码指出:本来http.formLogin()也是http对象的一个方法,所以注释中就将一个比较完整的http链式编程的例子写了出来,而http.formLogin()只是作为了链式编程中使用and()拼接的一个功能开启
2.认证(Authentication/en)
- 上面我们对于用户的授权做出了spring security的限制,只要你没有登陆,点击请求任何资源都会被重定向到spring security自带的登陆页面,但是由于我们还没有编写认证,所以跳转登陆页面之后我们也登陆不上,所以需要配置spring security的认证功能
- spring security的认证可以有两种形式:①内存存储认证信息(优点:认证速度快;缺点:占用内存,可用空间有限) ②数据库存储认证信息(优点:存储空间大;缺单:认证速度慢)
- 这里我们先使用内存存储认证信息的方式来实现认证
- 实现认证我们需要覆写WebSecurityConfigurerAdapter的又一个方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
- 同理,去看父类怎么实现的这个方法,照猫画虎实现覆写方法
* For example, the following configuration could be used to register in memory
* authentication that exposes an in memory {@link UserDetailsService}:
* </p>
*
* <pre>
* @Override
* protected void configure(AuthenticationManagerBuilder auth) {
* auth
* // enable in memory based authentication with a user named
* // "user" and "admin"
* .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
- 覆写实现
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //AuthenticationManagerBuilder授权管理建造者
auth.inMemoryAuthentication() //inMemoryAuthentication()在内存中存储授权信息
.withUser("admin").password("12345").roles("vip1,vip2,vip3").and() //授权用户1信息配置
.withUser("zhangsan").password("12345").roles("vip1").and() //授权用户2信息配置
.withUser("lisi").password("12345").roles("vip2,vip3"); //授权用户3信息配置
}
- 正常情况下,上面的登陆用户信息应该在数据库中去读取,但是这里直接在内存中虚拟了3个用户信息
- 测试
- 报错信息:There was an unexpected error (type=Internal Server Error, status=500),即我们使用的密码没有编码/加密
- 这是由于spring security5.0+的版本中,强制要求密码需要使用加密算法进行加密,原因:明文密码不安全,反编译可以看到明文密码,所以需要进行加密
- 加密方式:在auth.inMemoryAuthentication()之后直接"."方法passwordEncoder(),查看这个方法需要的参数
public C passwordEncoder(PasswordEncoder passwordEncoder);
- 显然这个方法需要一个PasswordEncoder接口的实现类,通过IDEA可以清楚的找打这个接口有哪些实现类
- 官方推荐使用的加密方式为BCryptPasswordEncoder
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { //AuthenticationManagerBuilder授权管理建造者
auth.inMemoryAuthentication() //inMemoryAuthentication()在内存中存储授权信息
.withUser("admin").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1","vip2","vip3").and() //授权用户1信息配置
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("12345")).roles("vip1").and() //授权用户2信息配置
.withUser("lisi").password(new BCryptPasswordEncoder().encode("12345")).roles("vip2","vip3").and() //授权用户3信息配置
.passwordEncoder(new BCryptPasswordEncoder());
}
- 注意:roles()中的角色要与antMatchers().hasRole()中设置的角色对应;并且roles()中有多个角色时,每个角色一个"“引起来,不同角色之间使用”,“隔开,不要只使用一个”",将所有用户角色都写在里面,下图中就出现了这个错误,在功能测试的时候才发现
- 重启项目进行测试
- 换一个权限低的用户登陆
- 通过上面的测试我们就实现了对于用户的认证+授权访问,用户lisi就不截图了
3.小结
- 可见通过spring security实现认证+授权其实很简单,我们只是自己定义了一个confiig,使用注解@EnableWebSecurity标注了这个类,并且该类继承了WebSecurityConfigurerAdapter类,并在这个类中覆写了实现认证(protected void configure(AuthenticationManagerBuilder auth))和授权(protected void configure(HttpSecurity http))的两个config方法
- 我们并没有修改原来程序实现的任何代码,利用了spring security是通过AOP方式实现的特点将认证+授权加在了已经完成了的项目上
- 注意:spring security中方法的覆写都是通过链式编程实现的
- 认证就需要设置用户名+密码+用户角色
- 授权就需要设置需要授权的资源对应的请求url+可以请求到该资源的用户角色
4.补充:使用JDBC实现认证
- You can find the updates to support JDBC based authentication. The example below assumes that you have already defined a DataSource within your application. The jdbc-javaconfig sample provides a complete example of using JDBC based authentication.
- 您可以找到支持基于JDBC的身份验证的更新,下面的示例假设您已经在应用程序中定义了一个数据源,jdbcjavaconfig示例提供了使用基于jdbc的身份验证的完整示例
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}