一、基础
官网地址:spring.io/projects/spring-security
Spring Security是一个功能强大,可高度定制的认证与授权的框架;重点处理认证和授权2个功能
1.1)认证
就是判断一个用户身份是否合法的过程,用户访问系统资源时系统要求验证用户的身份信息,根据认证的结果来进行后一步的操作;认证是为了保证系统的隐私数据与资源;
1.2)授权
授权是用户通过认证后,根据用户的权益来控制用户可以访问资源的过程;授权是为了更细粒度地对隐私数据进行划分,授权是发生在认证通过之后;
1.3)会话
系统为了保持当前用户的登录状态所提供的机制;常见的方式有session、token等方式;
二、入门示例
2.1)加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
复制代码
2.2)启动类配置
//@EnableWebSecurity这个注解是SpringSecurity框架提供的注解,加入从注解后即集成了Security
@EnableWebSecurity
@SpringBootApplication
public class SecurityDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityDemoApplication.class, args);
}
}
复制代码
2.3)构建访问交互
@RestController
@RequestMapping("/testDemo")
public class TestDemoController {
/**
* 功能描述:测试访问1
*
* @author : XXSD
* @date : 2021/1/7 0007 上午 10:56
*/
@RequestMapping("/demo1")
public String demo1() {
return "demo1";
}
}
复制代码
2.4)启动项目并访问
在浏览器输入: http://localhost:8081/testDemo/demo1
得到如下结果;注意:这里我们没有配置任何的页面,但是项目启动后输入上面的地址,会打开一个界面,这个界面是SpringSecurity为我们提供的一个默认界面;
在代码中并没有添加任何的登录页面,但是这里却出现了,显然是Security框架提供过的;
回顾控制台日志信息:
Connected to the target VM, address: '127.0.0.1:49458', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2021-01-07 11:04:25.322 INFO 52984 --- [ main] xinyan.SecurityDemoApplication : Starting SecurityDemoApplication using Java 1.8.0_202 on JKY8986KLO9O81T with PID 52984 (F:\Tu_Ling\xuexi-demo\security-demo\target\classes started by Administrator in F:\Tu_Ling\xuexi-demo)
2021-01-07 11:04:25.327 INFO 52984 --- [ main] xinyan.SecurityDemoApplication : No active profile set, falling back to default profiles: default
2021-01-07 11:04:26.501 INFO 52984 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2021-01-07 11:04:26.511 INFO 52984 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-01-07 11:04:26.511 INFO 52984 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
2021-01-07 11:04:26.574 INFO 52984 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-01-07 11:04:26.574 INFO 52984 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1188 ms
2021-01-07 11:04:26.751 INFO 52984 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
#在没有任何配置的情况下,Security会自动创建一个user用户,下面就自动生成的登录密码
Using generated security password: f82577c5-4c0f-420b-842c-34f837bf8c15
复制代码
2.5)Security提供的默认配置
登录地址:【域名或IP】【:端口号】/login
登出地址:【域名或IP】【:端口号】/logout
启动类上添加:@EnableWebSecurity注解后自动集成Security;在什么都不进行配置的情况下会创建默认用户“user”;默认的密码在控制台打印;
三、认证授权逻辑实现
结合第二节的示例继续
3.1)授权配置
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;
/**
* 类描述: Security授权配置
* <br />
* WebSecurityConfigurerAdapter是Sprng提供的一个适配器,通过这个适配器可以帮助开发对授权的行为进行配置
* 注意:需要加入@EnableWebSecurity注解,也可以使用@Configuration进行标注
*
* @author XXSD
* @version 1.0.0
* @date 2021/1/7 0007 上午 11:49
*/
@EnableWebSecurity
public class SecurityAuthorizationConfig extends WebSecurityConfigurerAdapter {
/**
* 在开启“记住我”功能的时候需要指定的一个UserDetailsService对象
*/
private final UserDetailsService userDetailsService;
public SecurityAuthorizationConfig(UserDetailsService userDetailsService){
this.userDetailsService=userDetailsService;
}
/**
* 功能描述:超类中提供了3个confgure配置方法,这里使用Http请求访问规则权限类型的配置方法进行配置
*
* @author : XXSD
* @date : 2021/1/7 0007 下午 6:50
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭跨域检查
.csrf().disable()
.authorizeRequests()
//配置路径及对应的资源权限
.antMatchers("/mobile/**").hasAuthority("mobile")
.antMatchers("/salary/**").hasAuthority("salary")
.antMatchers("/testDemo/**").hasAuthority("testDemo")
//设置放行的请求
.antMatchers("/message/**").permitAll()
//其他请求需要登录,注意在anyRequest()后就不能再配置规则了,因为既然已经全部放行了,后面再配置规则就没有意义了,同时也证明了这个配置是有先后顺序的;
.anyRequest().authenticated()
//设置为并行条件
.and()
//开启“记住我”功能,开启后需要指定一个userDetailsService对象,同时也可以使用.rememberMeParameter("【自定义参数名称】")来指定参数的名称
.rememberMe().userDetailsService(userDetailsService)
.and()
//可以设置登录成功后跳转的页面
.formLogin()
//配置自己的登录页,如果只配置了loginPage部分,没有配置loginProcessingUrl,那么会出现无法获取到用户的问题,因为提交的地址并没有真正的提交到SpringSecurity
.loginPage("/myLogin.html").loginProcessingUrl("/login")
.defaultSuccessUrl("/testDemo/demo1").failureUrl("/login");
}
}
复制代码
3.2)认证配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 类描述: Security认证配置
*
* @author XXSD
* @version 1.0.0
* @date 2021/1/7 0007 上午 11:49
*/
@Configuration
public class SecurityAuthenticationConfig implements WebMvcConfigurer {
/**
* 功能描述:注入自己的密码编码器
*
* @author : XXSD
* @date : 2021/1/7 0007 下午 12:02
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
/**
* 功能描述:注册用户管理服务
* <br />
* 如果没有注入这个用户管理服务,那么在启动的时候Security框架会在UserDetailsServiceAutoConfiguration中帮你注入一个
* 用户名为“user”用户的InMemoryUserDetailsManager对象;
* 另外也可以采用修改configure(AuthenticationManagerBuilder auth)方法注入AuthenticationManagerBean
* <br />
* UserDetailsService是一个接口
* Spring不仅提供了InMemoryUserDetailsManager接口同时还提供了一个JdbcUserDetailsManager的实现类,专用于数据库的处理
* @author : XXSD
* @date : 2021/1/7 0007 下午 5:14
*/
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
/*
* 这里直接创建了2个用户并赋予权限资源
* 在创建用户的时候要去必须指定一个资源
* passwordEncoder().encode("admin")是使用当前的加密器对密文进行加密
* */
User.withUsername("Admin").password(passwordEncoder().encode("admin")).authorities("testDemo","mobile", "salary").build(),
//authorities配置中可以加入“ROLE_”前缀的资源名称
User.withUsername("manager").password(passwordEncoder().encode("manager")).authorities("mobile").build()
);
}
/**
* 配置默认的跳转路径,这里可以定制自己的跳转url
* Configure simple automated controllers pre-configured with the response
* status code and/or a view to render the response body. This is useful in
* cases where there is no need for custom controller logic -- e.g. render a
* home page, perform simple site URL redirects, return a 404 status with
* HTML content, a 204 with no content, and more.
*
* @param registry
* @see ViewControllerRegistry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login");
}
}
复制代码
四、Security拓展点
4.1)主体数据来源拓展点
SpringSecurity是通过引用Spring容器中的UserDetailsService对象来管理主体数据的,默认情况下,会注入一个包含用户信息的默认主体来管理服务;在实际的项目中用户信息都是来源于外部数据库,在SpringSecurity中提供了一个JdbcUserDetailsManager来实现对数据库的用户信息进行管理,当然这个自带的实现是基于大众化的,如果有个性化处理,可以自己实现一个UserDetailsService对象并注入到Spring容器中即可;
/**
自定义的UserDetailsService注入
具体实现方式参考JdbcUserDetailsManager
*/
@Bean
public UserDetailsService userDetailsService() {
return new 自定义的UserDetailsService 接口实现();
}
复制代码
UserDetails对象:
SpringSecurity中提供了一个UserDetails对象,此对象中有如下几个总要的属性
//密码
private String password;
//用户名
private final String username;
//权限
private final Set authorities;
//账户是否超时
private final boolean accountNonExpired;
//账户是否锁定
private final boolean accountNonLocked;
//密码是否超时
private final boolean credentialsNonExpired;
//是否失效
private final boolean enabled;
复制代码
以上accountNonExpired、accountNonLocked、credentialsNonExpired、enabled四个状态用来维护用户的可用性,当这4个状态全部为False的时候用户才可以正常登录;
4.2)密码解析器
SpringSecurity提供了多个密码解析器,包括CryptPassEncoder、Argon2PasswordEncoder、Pbkdf2PasswordEncoder等,这些密码解析器都实现了PassEnger接口;目前最常用的是BCryptPasswordEncoder;值得注意的是,我们在选择不同的密码解析器后,后台存储用户密码的介质媒体内容也要发生更改;
BCryptPasswordEncoder加密器加密后的密文是不一样的,但是不管结果如何,都是可以被识别的(使用方式:new BCryptPasswordEncoder().matche(【密文原数据】, 【密文】));
BCryptPasswordEncoder是使用加严的方式进行的,即加密后的结果中“ ”是分隔符, ” 是 分 <typo id="typo-9536" data-origin="隔" ignoretag="true">隔</typo> 符 , 之间是版本标识;最后其实就是密文+严内容,前22位是严,22位之后是密文加密后的数据;
4.3)自定义授权及安全拦截策略
这个拓展点是整个SpringSecurity的核心部分;
在实际开发过程中,最常规的方式是通过覆盖WebSecurityConfigurerAdapter中的protected void configure(HttpSecurity http)方法;通过Http来配置自定义的拦截规则,包含访问控制、界面及逻辑、退出页面及逻辑等;
- 自定义登录
http.loginPage()方法配置登录页,http.loginProcesingUrl()方法定制登录逻辑;这里需要特别注意的是:SpringSecurity的登录页和逻辑hi同一个“/login”地址,如果使用自定义的页面,需要将登录逻辑地址进行分离;
http.loginPage("/index.html").loginProcessingUrl("/login");
复制代码
具体的登录页面实现的逻辑,请参考系统提供的默认登录页,登录页面的源码可以在DefaultLoginPageGeneratingFilter中找到,需要注意的是登录页面的相关访问权限;
SpringSecurity本质上是前后端不分离的;
- 记住我功能
登录页面提供了记住我功能,此功能只需往登录接口提交一个remeber-me参数即可,其值可选范围:no、yes、1、true;就会记住当前登录用户的Token到Cookie中;
http.rememberMe().rememberMeParameter("remeber-me")
复制代码
这个配置参数在登出时,会清除记住我功能的cookie;
- 拦截谋略
antMachers()方法设置路径的匹配逻辑,可以使用通配符进行配置,两个星号来标识多层路径,一个星号代表一个或多个字符,问号代表一个字符;
permitAll():所有人都可以访问;
denyAll():所有人都不能访问;
anonymous():只有未登录的人可以访问,已经登录的人无法访问;
hasAuthority、hasRole这些是配置需要有对应的权限或者角色才能访问的控制逻辑方法;其中角色就是对应一个“ROLE_【角色名】”这样的资源;
AuthenticationManagerBuilde:配置认证谋略;
WebSecurity:配置补充的Web请求策略;
- CSRF
是Cross-Site Request Forgery的缩写,意义为:跨站点请求伪造,这是一种攻击手段;
SpringSecurity针对CSRF是由一整套专门的检查机制;在后台session中加入一个csif的Token值,然后向后端发起请求,对于Get、Head、Trace、Options以外的请求(例如:POST、PUT、DELETE等),会要求带上这个Token值进行对比;
在SpringSecurity有一个CsrfFilter专门负责对Csrf参数进行检查,会调用HttpSessionCsrfTokenRepository生成一个CsrfToken,并将其保存到Session中;
- 权限注解的支持
在启动类上添加@EnableGlobalMethodsecurity注解即可开启;
prePostEnabled:是否开启对@PreAuthorize注解的支持;
securedEnabled:是否开启对@Secured注解的支持,角色级别的控制;
jsr250Enabled:是否开启对@RolesAllowed注解的支持,等价于@Secured;
- 异常处理
前后端分离的情况下可以使用@ControllerAdvice注入一个异常处理类,以@ExceptionHandler注解声明方法,往前端推送异常信息;
@ControllerAdvice
public class SecurityExceptionManager {
/**
* 功能描述:处理没有权限的情况
* @author : XXSD
* @date : 2021/1/8 0008 下午 4:17
*/
@ResponseBody
@ExceptionHandler(AccessDeniedException.class)
public String exceptionManager(){
return "";
}
}
复制代码
如果觉得本文对你有帮助,可以点赞关注支持一下