详解Spring Security(2)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
访问控制,不限时长
简介: 1.概述Spring Security是一个基于Spring框架的安全性框架,它提供了一系列的API和扩展点,可以帮助开发人员在应用程序中轻松地实现安全认证和授权控制。我们可以理解为Spring Security维护了一组我们可以自定义的访问规则,每次访问都会去进行规则比对,满足规则的才放行。这些规则可以有很多维度,本文会以最基础的基于角色的控制入手逐步扩展详细介绍Spring Security。基于角色的控制,我们可以理解为Spring Security为我们维护了一张“白名单”,而登录就是去白名单

3.登出

讲完登陆后再看登出就很好明白:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // 其他配置
        .logout()
            .logoutUrl("/logout") // 配置登出的URL
            .logoutSuccessUrl("/login") // 配置登出成功后的跳转URL
            .deleteCookies("remember-me") // 删除Cookie
            .permitAll(); // 允许所有用户访问登出URL
}

4.使用数据库

上文中我们都是用auth.inMemoryAuthentication()将定义的用户和角色信息放在内存中,在实际业务场景中有时候我们是需要将这些信息放在数据库中进行持久化的。spring security中有两种常用方式来将信息放在数据库中:

  • auth.jdbcAuthentication()
  • auth.userDetailsService()

4.1.jdbcAuthentication

使用这种方式时,严格规定了表结构,具体的建表语句在这里:

386a3561f4cb47cb9e4353fc81cd1eed.png

625ecc1f0b7443488ef19edb876e5230.png


这里要注意几点:

  • 权限前缀要加ROLE_,不然不会生效,enabled属性是用户是否生效1是0否
  • 密码一定要使用加密,不加密识别不了


f8de5f202d8a413f9aaa804e1c0f87fd.png


接下来就是使用:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username, password, enabled from users where username=?")
            .authoritiesByUsernameQuery("select username, authority from authorities where username=?")
            .passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // ...
    }
}

4.2.userDetailsService

jdbcAuthentication这种方式中对表结构有严格要求,如果觉得太死,需要灵活一点,security还提供了userDetailsService这种方式灵活的来让我们灵活的使用。


我们可以自己实现UserDetailsService,重新loadUserByUsername方法,在方法中灵活的去将系统用户转为security的用户。

@Service
public class UserServiceImpl implements UserDetailsService {
    @Autowired
    UserDao userDao;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //从存储中获取系统用户
        SystemUser systemUser = userDao.getUser();
        //将系统用户的用户名密码来创建security的用户
        User.UserBuilder builder = User.builder();
        UserDetails user = builder.username(systemUser.getUserName()).password(systemUser.getPassword()).roles("admin").build();
        return user;
    }
}

5.自定义处理器

在Spring Security中,默认认证成功了就能访问到接口,但是如果我想认证成功后不直接访问接口而是做其他事情喃?spring security也提供了我们自定义操作完成后的处理过程的能力,我们可以使用自定义的处理器(Handler)来处理认证、授权、注销等操作。自定义处理器可以通过实现相应的接口或继承相应的类来完成。


以下是几个常用的自定义处理器的接口/类及其作用:


AuthenticationSuccessHandler:用于认证成功后的处理,比如记录日志、跳转到指定页面等。


AuthenticationFailureHandler:用于认证失败后的处理,比如记录日志、显示错误信息等。


AccessDeniedHandler:用于处理访问被拒绝的情况,比如跳转到错误页面、记录日志等。


LogoutSuccessHandler:用于注销成功后的处理,比如跳转到登录页面、删除Cookie等。


InvalidSessionStrategy:用于处理无效的Session,比如跳转到登录页面、记录日志等。

在Spring Security中,我们可以通过配置来使用自定义的处理器。例如:

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 处理认证成功的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/index");
    }
}


public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // 处理认证失败的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/login?error=true");
    }
}
public class MyLogoutHandler implements LogoutHandler {
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 处理注销的逻辑,比如记录日志、清除缓存等
    }
}
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 处理注销成功的逻辑,比如记录日志、跳转页面等
        response.sendRedirect("/logoutSuccess");
    }
}

定义完后直接使用即可:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //省略其他配置...
    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    @Autowired
    private CustomInvalidSessionStrategy customInvalidSessionStrategy;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .successHandler(customAuthenticationSuccessHandler)
            .failureHandler(customAuthenticationFailureHandler)
            .and()
            .exceptionHandling()
            .accessDeniedHandler(customAccessDeniedHandler)
            .and()
            .logout()
            .logoutSuccessHandler(customLogoutSuccessHandler)
            .and()
            .sessionManagement()
            .invalidSessionStrategy(customInvalidSessionStrategy);
    }
}

6.更多细粒度的控制

前文我们都是基于角色来进行访问控制的,稍加思考就会想到,光是靠角色来控制实际应用中可能不够,有时候还要基于HTTP METHOD、IP、权限等等,spring security当然给我们提供了这些更加细粒度的访问控制方法:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 配置安全规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 基于角色控制访问
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            // 基于权限控制访问
            .antMatchers("/article/**").hasAuthority("ARTICLE_VIEW")
            // 基于IP地址控制访问
            .antMatchers("/profile/**").hasIpAddress("192.168.1.0/24")
            // 基于方法表达式控制访问
            .antMatchers(HttpMethod.DELETE, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_DELETE')")
            .antMatchers(HttpMethod.PUT, "/article/**").access("hasRole('ADMIN') or hasAuthority('ARTICLE_EDIT')")
            // 其他请求需要进行身份认证
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .httpBasic();
    }
    // 配置用户信息
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            // 配置用户角色和权限
            .withUser("user").password("{noop}password").roles("USER").authorities("ARTICLE_VIEW")
            .and()
            .withUser("admin").password("{noop}password").roles("ADMIN").authorities("ARTICLE_VIEW", "ARTICLE_EDIT", "ARTICLE_DELETE");
    }
}

当然除了代码里可以配置,也提供了注解版本:

// 基于角色的访问控制注解
    @Secured("ROLE_ADMIN")
    public void adminOnlyMethod() {
        // 只有 ADMIN 角色的用户可以调用这个方法
    }
    // 基于角色的访问控制注解
    @RolesAllowed("ROLE_USER")
    public void userOnlyMethod() {
        // 只有 USER 角色的用户可以调用这个方法
    }
    // 基于方法调用之后的返回值进行访问控制
    @PostAuthorize("returnObject.owner == authentication.name")
    public Resource getResourceById(String id) {
        // 获取资源
        return resource;
    }
    // 在方法调用前进行集合过滤
    @PreFilter("filterObject.owner == authentication.name")
    public List<Resource> getResources(List<String> ids) {
        // 获取指定 ID 的资源列表
        return resources;
    }
    // 在方法调用后进行集合过滤
    @PostFilter("filterObject.owner == authentication.name")
    public List<Resource> getAllResources() {
        // 获取所有资源列表
        return resources;
    }
    // 基于SpEL表达式的访问控制注解,控制HTTP方法
    @PreAuthorize("hasRole('ADMIN') and #httpMethod == 'GET'")
    @GetMapping("/adminOnly")
    public ResponseEntity<String> adminOnlyEndpoint(HttpServletRequest request) {
        return ResponseEntity.ok("Only accessible to admins");
    }

7.原理简述

939b28b1493340adb40c1f93d737a0ec.jpg

看上图,Spring Security的实现原理一目了然,其实它就是通过一个Servlet过滤器来挂载到应用程序的请求流程中。当一个请求到达应用程序时,它首先会被Spring Security的过滤器拦截,进行安全控制处理,然后将请求继续传递给应用程序的其他组件进行处理。


这里面的投票器不用深究,它的作用其实就是用于决定一个用户是否有访问特定资源的权限。投票器通过对安全上下文(SecurityContext)中的身份验证信息、访问控制列表(ACL)和其他因素进行评估,决定是否允许用户访问资源。

相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
目录
相关文章
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
4月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
5月前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
196 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
5月前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
223 2
Spring Security 6.x OAuth2登录认证源码分析
|
5月前
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
241 5
Spring Security 6.x 一文快速搞懂配置原理
|
5月前
|
安全 Java API
Spring Security 6.x 图解身份认证的架构设计
【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现
81 1
Spring Security 6.x 图解身份认证的架构设计
|
4月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
4月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权
|
4月前
|
存储 安全 Java
Spring Security在企业级应用中的应用
Spring Security在企业级应用中的应用