盘点认证框架 : 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给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
6月前
|
安全 Java Spring
教程:在Spring Boot应用中集成OAuth 2.0认证
教程:在Spring Boot应用中集成OAuth 2.0认证
|
安全 Java 数据安全/隐私保护
SpringSecurity 认证流程
通过了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。
116 0
|
7月前
|
安全 Java 数据库
后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)
后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)
|
安全 Java 程序员
阿里开源SpringSecurity:用户+案例+认证+框架
SpringSecurity 相信Spring大家一定不陌生,那么SpringSecurity你又了解多少呢?市面上有关Spring的介绍有很多,那么对于SpringSecurity只有一些简单的有关概念的介绍,如果想深入了解并使用SpringSecurity还是需要下很大的功夫的! 可想而知,SpringSecurity有着强大的功能,但是它同时也有很高的学习成本;毕竟囊括了身份认证的各种场景以及Web安全的大量知识,在官方参考的手册中就数十万字的介绍,且还不包括当中诸多实现细节。问题来了,很多开发人员在面对这样的“庞然大物”的时候也是无从下手的,
73 0
|
安全 Cloud Native Java
Spring与OAuth2:实现第三方认证和授权的最佳实践
Spring与OAuth2:实现第三方认证和授权的最佳实践
215 0
|
存储 Java 数据库
SpringSecurity基础-认证原理
SpringSecurity是基于Filter实现认证和授权,底层通过FilterChainProxy代理去调用各种Filter(Filter链),Filter通过调用AuthenticationManager完成认证 ,通过调用AccessDecisionManager完成授权,SpringSecurity中核心的过滤器链详细如下:
104 0
|
存储 Java 数据库
三.SpringSecurity基础-认证原理
SpringSecurity基础-认证原理
|
存储 安全 NoSQL
前后端分离认证实践指南:Spring Security和JWT详解(上)
前后端分离认证实践指南:Spring Security和JWT详解
870 0
|
SQL 存储 NoSQL
前后端分离认证实践指南:Spring Security和JWT详解(下)
前后端分离认证实践指南:Spring Security和JWT详解
218 0
|
存储 前端开发 数据安全/隐私保护
十五.SpringCloud+Security+Oauth2实现微服务授权 -前端登录实战
SpringCloud+Security+Oauth2实现微服务授权 -前端登录实战