盘点认证框架 : SpringSecurity 基础篇

简介: SpringSecurity 应该是最常见的认证框架了 , 处于Spring体系中使他能快速地上手 , 这一篇开始作为入门级开篇作 , 来浅浅地讲一下SpringSecurity 的整体结构.

一 . 前言

SpringSecurity 应该是最常见的认证框架了 , 处于Spring体系中使他能快速地上手 , 这一篇开始作为入门级开篇作 , 来浅浅地讲一下SpringSecurity 的整体结构.

关于Security 的使用 , 官方文档已经太详细了, 建议直接查看 官方文档 , 作为开篇 , 不深入源码 , 只做体系的介绍~~

后续会陆陆续续将笔记中的源码梳理出来 ,笔记很乱, 但愿等整理出体系!

二 . Spring Security 知识体系

Security中需要我们操作的成员大概可以分为以下几种 , 但是涉及的类远远不止他们

  • Filter : 对请求拦截处理
  • Provider : 对用户进行认证
  • Token : 用户认证主体

2.1 Spring Security 主要包结构

  • spring-security-remoting : 提供与 Spring Remoting 的集成
  • spring-security-web : 包含过滤器和相关的网络安全基础设施代码。
  • spring-security-config : 包含安全命名空间解析代码和 Java 配置代码 ?- 使用 Spring Security xml 命名空间进行配置或 Srping Security 的 Java Configuration 支持
  • spring-security-ldap : 该模块提供 LDAP 身份验证和配置代码
  • spring-security-oauth2-core : 包含支持 OAuth 2.0授权框架和 OpenID Connect Core 1.0的核心类和接口
  • spring-security-oauth2-client : 包含 Spring Security 对 OAuth 2.0授权框架和 OpenID Connect Core 1.0的客户端支持
  • spring-security-oauth2-jose : 包含 Spring Security 对 JOSE (Javascript 对象签名和加密)框架的支持JSON Web Token (JWT)JSON Web Signature (JWS)JSON Web Encryption (JWE)JSON Web Key (JWK)
  • spring-security-oauth2-resource-server : 包含 Spring Security 对 OAuth 2.0资源服务器的支持。它通过 OAuth 2.0承载令牌来保护 api
  • spring-security-acl : 此模块包含专门的域对象 ACL 实现。它用于对应用程序中的特定域对象实例应用安全性
  • spring-security-cas : 该模块包含 Spring Security 的 CAS 客户端集成
  • spring-security-openid : 此模块包含 OpenID web 身份验证支持。它用于根据外部 OpenID 服务器对用户进行身份验证。
  • spring-security-test
  • spring-secuity-taglibs

2.2 Spring Security 核心体系 Filter

SpringSecurity 中存在很多Filter , 抛开一些底层的 , 一般业务中的Filter主要是为了控制以何种方式进行认证 . 一般的体系结构里面 , 都会循环处理 , 例如 CAS 中 , 就是通过 HandlerManager 进行 for each 循环 , 而 SpirngSecurity 中 , 同样通过 SecurityFilterChain 进行循环.

SecurityFilterChain 在后期源码梳理的时候再详细介绍 , 这里先看张图 :

FilterChainProxy 使用 SecurityFilterChain 来确定应该为此请求调用哪个 Spring 安全过滤器 ,

FilterChainProxy 决定应该使用哪个 SecurityFilterChain。会调用第一个被匹配的SecurityFilterChain ,即匹配是有序的

  • 如果请求/api/messages/ 的URL,它将首先匹配 SecurityFilterChain0的 /api/** 模式,因此只会调用 SecurityFilterChain0,即使它也匹配 SecurityFilterChainn。
  • 如果请求/messages/ 的URL,它将不匹配 SecurityFilterChain0的 /api/** 模式,因此 FilterChainProxy 将继续尝试每个 SecurityFilterChain。假设没有其他的 SecurityFilterChai n实例匹配 SecurityFilterChainn 将被调用。 (即无匹配调用最后一个)

已知的 Filter 类

ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth2LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
OpenIDAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
SwitchUserFilter

2.3 Authentication 体系结构

认证体系是核心的处理体系 , 包含以下主要类 :

SecurityContextHolder : Spring Security 存储被验证者的详细信息的地方。

SecurityContext : 从 SecurityContextHolder 获得,并包含当前经过身份验证的用户的身份验证。。

Authentication : 可以作为 AuthenticationManager 的输入,以提供用户为身份验证或来自 SecurityContext 的当前用户提供的凭据。。

GrantedAuthority : 在身份验证上授予主体的权限(即角色、范围等)。

AuthenticationManager : 定义 Spring Security 的过滤器如何执行身份验证的 API。。

ProviderManager : AuthenticationManager 最常用的实现。。

Providationprovider : 由 ProviderManager 用于执行特定类型的身份验证。。

AuthenticationEntryPoint : 用于从客户机请求凭证(即重定向到登录页面,发送 www 认证响应等)。


AbstractAuthenticationProcessingFilter :
用作验证用户凭据的基本筛选器

AccessDecisionManager :
AbstractSecurityInterceptor 调用,负责做出最终的访问控制决策

后续我们会围绕以上类进行源码梳理:

三 . SpringSeurity 案例

以下是一个很简单的 Security 案例 : >>>>项目源码<<<<

3.1 SpringSecurity 依赖和配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler myAuthenctiationFailureHandler;
    @Bean
    public UserService CustomerUserService() {
        System.out.print("step1============");
        return new UserService();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //BCryptPasswordEncoder().encode("123456")).roles("ADMIN");
        auth.userDetailsService(CustomerUserService()).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //此方法中进行了请求授权,用来规定对哪些请求进行拦截
        //其中:antMatchers--使用ant风格的路径匹配
        //regexMatchers--使用正则表达式匹配
        http.authorizeRequests()
                .antMatchers("/test/**").permitAll()
                .antMatchers("/before/**").permitAll()
                .antMatchers("/index").permitAll()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()                      //其它请求都需要校验才能访问
                .and()
                .formLogin()
                .loginPage("/login")                             //定义登录的页面"/login",允许访问
                .defaultSuccessUrl("/home")  //登录成功后默认跳转到"list"
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
                .logout()                                           //默认的"/logout", 允许访问
                .logoutSuccessUrl("/index")
                .permitAll();
        http.addFilterBefore(new BeforeFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/**/*.js", "/lang/*.json", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html", "/**/*.png");
    }
}

其中有几个主要的地方 :

@EnableWebSecurity 干了什么 ?

  • 该配置将创建一个 servlet Filter,作为名为 springSecurityFilterChain 的 bean
  • 创建一个具有用户用户名和随机生成地登录到控制台的密码的 UserDetailsService bean
  • 为每个请求向 Servlet 容器注册名为 springSecurityFilterChain 的 bean 的 Filter

TODO

3.2 准备一个 UserDetailsService

public class UserService implements UserDetailsService {
    //......
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users user = userRepository.findByUsername(username);
        // ....
        return user;
    }

一个基本的 Demo 就完成了 , 案例能怎么简单 , 其实主要是因为我们复用了以下的类 :

  • UsernamePasswordAuthenticationFilter
  • DaoAuthenticationProvider
  • UsernamePasswordAuthenticationToken

四 . 定制案例

我们把整个结构再定制一下 , 满足我们本身的功能 :

4.1 Step 1 : 定制一个 Filter

// 我们复用 UsernamePasswordAuthenticationFilter , 将其进行部分定制
public class DatabaseAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    // 修改用户名为 account
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "account";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;
    public DatabaseAuthenticationFilter() {
        super(new AntPathRequestMatcher("/database/login", "POST"));
    }
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        // username 从下方方法获取
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        username = username.trim();
        // 核心 : 这里替换了 DatabaseUserToken
        DatabaseUserToken authRequest = new DatabaseUserToken(username, password);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
    }
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
    }
    protected void setDetails(HttpServletRequest request,
                              DatabaseUserToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }
    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
    public final String getUsernameParameter() {
        return usernameParameter;
    }
    public final String getPasswordParameter() {
        return passwordParameter;
    }
}

4.2 Step 2 : 准备一个 Token

Token 是在Authentication 中传递的核心 , 它用于后续进行票据的认证

public class DatabaseUserToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    private final Object principal;
    private String credentials;
    private String type;
    private Collection<? extends GrantedAuthority> authorities;
    public DatabaseUserToken(Object principal, String credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        this.type = "common";
        setAuthenticated(false);
    }
    public DatabaseUserToken(Object principal, String credentials, String type) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        this.type = StringUtils.isEmpty(type) ? "common" : type;
        setAuthenticated(false);
    }
    public DatabaseUserToken(Object principal, String credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }
    public DatabaseUserToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = null;
        super.setAuthenticated(true); // must use super, as we override
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    /**
     * @param isAuthenticated
     * @throws IllegalArgumentException
     */
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        super.setAuthenticated(true);
    }
    @Override
    public Object getCredentials() {
        return credentials;
    }
    @Override
    public Object getPrincipal() {
        return principal;
    }
}

4.3 Step 3 : 准备一个 Provider

这里通过 supports 方法判断 token 是否符合 ,从而发起认证过程 (PS : 和 CAS 简直一个思路)

public class DatabaseAuthenticationProvider implements AuthenticationProvider {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private AntSSOConfiguration antSSOConfiguration;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        logger.info("------> auth database <-------");
        String username = (authentication.getPrincipal() == null)
                ? "NONE_PROVIDED" : String.valueOf(authentication.getPrincipal());
        String password = (String) authentication.getCredentials();
        if (StringUtils.isEmpty(password)) {
            throw new BadCredentialsException("密码不能为空");
        }
        UserInfo user = userInfoService.searchUserInfo(new UserInfoSearchTO<String>(username));
        logger.info("------> this is [{}] user  :{}<-------", username, String.valueOf(user));
        if (null == user) {
            logger.error("E----> error :{} --user not fount ", username);
            throw new BadCredentialsException("用户不存在");
        }
        String encodePwd = "";
        if (password.length() != 32) {
            encodePwd = PwdUtils.AESencode(password, AlgorithmConfig.getAlgorithmKey());
            logger.info("------> {} encode password is :{} <-------", password, encodePwd);
        }
        if (!encodePwd.equals(user.getPassword())) {
            logger.error("E----> user check error");
            throw new BadCredentialsException("用户名或密码不正确");
        } else {
            logger.info("user check success");
        }
        DatabaseUserToken result = new DatabaseUserToken(
                username,
                new BCryptPasswordEncoder().encode(password),
                listUserGrantedAuthorities(user.getUserid()));
        result.setDetails(authentication.getDetails());
        logger.info("------> auth database result :{} <-------", JSONObject.toJSONString(result));
        return result;
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return (DatabaseUserToken.class.isAssignableFrom(authentication));
    }
    private Set<GrantedAuthority> listUserGrantedAuthorities(String uid) {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        if (StringUtils.isEmpty(uid)) {
            return authorities;
        }
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return authorities;
    }
}

4.4 隐藏环节

// 将 Provider 注入体系
auth.authenticationProvider(reflectionUtils.springClassLoad(item.getProvider()));
// 将 Filter 注入体系
AbstractAuthenticationProcessingFilter filter = reflectionUtils.classLoadReflect(item.getFilter());
filter.setAuthenticationManager(authenticationManager);
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);

以上的流程可以看到已经不需要 继承 UserService类了 . 重写这些足够我们去实现大部分业务逻辑 , 使用时在Provider 完成对应的认证方式即可

五 . 总结

Security 很好用, 在我的个人实践中 , 正在尝试将常见的协议进行整合 , 做成一个开源脚手架 , 个人感觉SpringSecuity 体系应该可以轻松地完成 .

开篇比较简单 , 正在构思怎样才能从实践的角度将他讲清楚 , 笔记也在陆陆续续整理 , 争取下个月将整套文章发出来 !

附录

HttpSecurity 常用方法

作者|AntBlack|掘金

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
监控 算法
探秘Guava的RateLimiter:单机流量控制的黄金法宝
探秘Guava的RateLimiter:单机流量控制的黄金法宝
522 0
|
安全 前端开发 PHP
[PiKaChu靶场通关]文件包含file include
[PiKaChu靶场通关]文件包含file include
997 0
[PiKaChu靶场通关]文件包含file include
|
安全 Java 数据库
SpringSecurity 入门
Spring Security是Spring采用 `AOP`思想,基于 `servlet过滤器`实现的安全框架。它提供了完善的**认证机制**和**方法级的授权功能**。是一款非常优秀的权限管理框架。
261 0
|
存储 数据采集 安全
各种系统架构图与详细说明
原文:各种系统架构图与详细说明 共享平台逻辑架构设计 如上图所示为本次共享资源平台逻辑架构图,上图整体展现说明包括以下几个方面: 1 应用系统建设 本次项目的一项重点就是实现原有应用系统的全面升级以及新的应用系统的开发,从而建立行业的全面的应用系统架构群。
27059 1
|
5月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
197 0
|
druid Java 关系型数据库
Druid连接池的基本配置与使用
Druid连接池的基本配置与使用
4196 0
Druid连接池的基本配置与使用
|
druid Java 数据库
spring boot 整合 druid(深入浅出)
spring boot 整合 druid(深入浅出)
688 0
|
算法 调度 Python
Python高级算法——贪心算法(Greedy Algorithm)
Python高级算法——贪心算法(Greedy Algorithm)
1060 3
|
SQL 监控 druid
深入了解Druid连接池:高性能数据库连接管理工具
在现代的应用开发中,数据库连接池是优化数据库访问性能的关键。Druid连接池作为一款高性能的数据库连接管理工具,为我们提供了强大的连接池功能和监控能力。本文将深入探讨Druid连接池的基本概念、特点,以及如何在实际应用中使用它进行高效的数据库连接管理。
1686 0
|
Java
错误:has been compiled by a more recent version of the Java Runtime (class file version 56.0)
错误:has been compiled by a more recent version of the Java Runtime (class file version 56.0)
6837 0