一、基础

官网地址: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 "";
    }

}
复制代码

如果觉得本文对你有帮助,可以点赞关注支持一下