spring security 扩展自定义登录方式

背景

spring security 默认给我们提供了一套用户名密码登录的的逻辑实现,在很多时候根本不满足我们实际开发的要求,所以我们经常需要对它认证模块进行扩展,那么如何扩展我们的认证方式呢,这篇文章将会给你一个较为完整的解答。

认证原理

Spring Security使用了一种基于过滤器链的认证原理来保护应用程序。它提供了一组过滤器,这些过滤器按照特定的顺序对每个请求进行处理。

认证过程中的主要步骤如下:

  1. 用户发送认证请求到应用程序。
  2. 应用程序将请求发送给Spring Security的过滤器链。
  3. 过滤器链依次处理请求,直到找到可以处理该请求的过滤器。
  4. 找到处理请求的过滤器后,该过滤器会验证用户的凭证(如用户名和密码)。
  5. 如果凭证有效,则生成一个已认证的安全上下文,并将其保存在Spring Security的上下文存储中。
  6. 如果凭证无效,则返回认证失败的信息给用户。
  7. 认证成功后,用户可以继续访问受保护的资源。

在认证过程中,Spring Security还提供了灵活的配置选项,可以根据特定的需求进行自定义配置,例如使用不同的认证提供程序、自定义身份验证逻辑等。

总结起来,Spring Security的认证原理是通过一组过滤器链来处理认证请求,验证用户凭证并生成认证的安全上下文,以保护应用程序的资源。

具体实现原理

Spring Security的认证实现原理主要涉及以下几个核心概念和组件:

  1. 用户提供的凭证:用户在进行认证时,需要提供其身份凭证,如用户名和密码。

  2. 认证提供程序(Authentication Provider):认证提供程序是Spring Security中的重要组件,负责验证用户提供的凭证。它使用用户提供的凭证与系统中存储的凭证进行比对,并决定是否通过认证。Spring
    Security提供了多种内置的认证提供程序,如基于数据库的JDBC认证提供程序、LDAP认证提供程序、InMemory认证提供程序等。同时也支持自定义认证提供程序。

  3. 用户详情服务(UserDetailsService):用户详情服务是Spring Security中的接口,负责获取用户的详细信息,如用户的权限、角色等。认证提供程序在验证用户凭证后,会使用用户详情服务获取用户的详细信息,并构建一个认证对象(Authentication)。

  4. 认证对象(Authentication):认证对象是Spring Security中表示已认证用户的对象。它包含了用户的身份信息、凭证信息、权限信息等。认证对象由认证提供程序生成,并将其保存在SecurityContextHolder的上下文存储中。

  5. 认证过滤器链(Authentication Filter Chain):认证过滤器链是Spring Security中的过滤器组成的链条,负责处理认证请求。每个过滤器都会对请求进行处理,并根据需要进行认证、授权和其他安全操作。在认证过程中,认证过滤器链会根据请求的URL匹配合适的过滤器进行处理。

  6. 安全上下文(Security Context):安全上下文是Spring Security中用于保存已认证用户信息的容器。它以ThreadLocal的方式存储,可以通过SecurityContextHolder来访问和操作。安全上下文中保存了当前的认证对象,可以在应用程序的任何地方获取已认证用户的信息。

总结起来,Spring Security的认证实现原理是通过认证提供程序验证用户提供的凭证,使用用户详情服务获取用户的详细信息,并构建一个认证对象,然后将该认证对象保存在安全上下文中。认证过滤器链负责处理认证请求,并根据需要进行认证、授权和其他安全操作。通过这种方式,Spring Security实现了对应用程序进行认证和授权的功能。

如何实现自定义认证的功能

要自定义Spring Security的认证模块,你可以按照以下步骤进行:

  1. 创建一个自定义的认证提供程序(Authentication Provider)类,该类实现了org.springframework.security.authentication.AuthenticationProvider接口。在该类中,你可以编写你自己的认证逻辑,比如验证用户名和密码是否匹配。

  2. 实现UserDetailsService接口来获取用户的详细信息。你可以根据自己的需求,从数据库、LDAP等数据源中获取用户信息,并返回一个UserDetails对象。

  3. 在配置类(通常是继承自WebSecurityConfigurerAdapter的类)中,覆盖configure(AuthenticationManagerBuilder auth)方法。在该方法中,你可以指定使用你自定义的认证提供程序和用户详情服务来进行认证。

  4. 在上述配置类中,覆盖configure(HttpSecurity http)方法来配置认证过滤器链。你可以根据需要添加和配置各种过滤器,例如表单登录过滤器、基于Token的认证过滤器等。

下面根据微信小程序登录来实现一个自定义模块的接口:
1.定义token信息类

public class JsCodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 5615450055779559101L;
    private String code;

    /**
     * Creates a token with the supplied array of authorities.
     *
     * @param authorities the collection of <tt>GrantedAuthority</tt>s for the principal
     *                    represented by this authentication object.
     */
    public JsCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, String code) {
        super(authorities);
        this.code = code;
    }

    public JsCodeAuthenticationToken(String code) {
        this(Collections.emptyList(), code);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return code;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

从代码中我们可以看出我们需要创建一个相关的token类来继承 AbstractAuthenticationToken 类,并实现相关的方法

AbstractAuthenticationToken 的定义

Spring Security框架中的一个抽象类,用于表示身份验证请求令牌。它用于封装用户凭据或其他必要的信息来进行用户身份验证。

AbstractAuthenticationToken的子类负责提供特定的身份验证细节,例如用户名和密码、基于令牌的身份验证令牌等。这些子类通常包含获取器和设置器,用于访问和修改身份验证细节。

AbstractAuthenticationToken还提供了一组方法来管理身份验证状态,例如设置已认证标志、检索与已认证用户关联的权限以及管理与身份验证过程相关的其他详细信息。

AbstractAuthenticationToken 的工作原理

当用户请求进行身份验证时,Spring Security将创建一个实现AbstractAuthenticationToken的具体子类对象,并将其中的身份验证信息进行填充。然后,该身份验证令牌将被传递给身份验证管理器进行进一步的身份验证处理。

身份验证管理器将检查身份验证令牌,并根据所配置的身份验证策略和安全规则,对用户进行身份验证。在身份验证过程中,身份验证管理器可能会调用相关的身份验证提供者,比如用户名密码身份验证提供者或令牌身份验证提供者,来验证用户的凭据信息。

一旦用户通过身份验证,身份验证管理器将更新AbstractAuthenticationToken中的已认证标志和权限信息,并将其返回给应用程序。在后续的请求中,应用程序可以根据需要访问身份验证令牌中的身份验证信息和权限信息,以实现不同的业务逻辑和访问控制。

2.定义一个接收参数的filter

@Slf4j
public class JsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static String jsCode = "code";

    private static boolean postOnly = true;

    protected JsCodeAuthenticationFilter(WechatProperties properties) {
        super(new AntPathRequestMatcher(properties.getAuthUrl(), "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equalsIgnoreCase("post")) {
            throw new AuthenticationServiceException("该接口不支持:" + request.getMethod());
        }

        String code = obtainCode(request);
        code = code.trim();
        log.info("得到的code:{}", code);
        JsCodeAuthenticationToken authenticationTokenRequest = new JsCodeAuthenticationToken(code);
        setDetails(request, authenticationTokenRequest);
        return this.getAuthenticationManager().authenticate(authenticationTokenRequest);
    }

    public String obtainCode(HttpServletRequest request) {
        if (request.getHeader("Content-Type").contains("application/json")) {
            return obtainCodeJson(request, jsCode);
        }

        return request.getParameter(jsCode);
    }

    public String obtainCodeJson(HttpServletRequest request, String param) {
        StringBuilder builder = new StringBuilder();
        String line = null;
        String code = null;
        try{
            BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }

            Map<String, String> map = JsonUtils.fromString(builder.toString(), Map.class, String.class, String.class);
            code = map.get(param);
        }catch (RuntimeException | IOException e) {
            throw new AuthenticationServiceException("获取参数失败");
        }

        return code;
    }

    /**
     * Provided so that subclasses may configure what is put into the
     * authentication request's details property.
     *
     * @param request     that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     *                    set
     */
    protected void setDetails(HttpServletRequest request, JsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

从上面的代码可以看出,我们需要定义一个filter 类继承 AbstractAuthenticationProcessingFilter 来获取前端传输的参数

AbstractAuthenticationProcessingFilter 的定义

AbstractAuthenticationProcessingFilter是Spring Security框架中的一个过滤器,用于处理用户认证的相关操作。

AbstractAuthenticationProcessingFilter 的功能有哪些

  1. 拦截用户发起的认证请求,并交给AuthenticationManager来进行身份验证。
    2.通过调用AuthenticationManager完成身份验证后,将认证结果封装成Authentication对象。
  2. 将认证结果传递给AbstractAuthenticationProcessingFilter的成功或失败处理器进行处理。
  3. 处理认证成功或失败的逻辑,例如生成并返回认证成功的Jwt token、跳转到特定页面、返回错误信息等。

定义相关的Provider 类

public class JsCodeAuthenticationProvider implements AuthenticationProvider {

    private WxOAuth2Service weChatService;

    private UserDetailsService userDetailsService;

    private WxMpProperties wxMpProperties;

    public JsCodeAuthenticationProvider(WxOAuth2Service weChatService, WxMpProperties wxMpProperties) {
        this.weChatService = weChatService;
        this.wxMpProperties = wxMpProperties;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        JsCodeAuthenticationToken token = (JsCodeAuthenticationToken) authentication;
        String code = (String) token.getPrincipal();
        log.info("得到的code:{}", code);
        if (StringUtils.isBlank(code)) {
            throw new AuthenticationServiceException("jscode 不能为空");
        }

        WxOAuth2AccessToken wxOAuth2AccessToken;
        try {
            wxOAuth2AccessToken = weChatService.getAccessToken(wxMpProperties.getAppId(), wxMpProperties.getSecret(), code);
        } catch (WxErrorException e) {
            throw new AuthenticationServiceException("授权登录失败");
        }



        WeChatDetailsService weChatDetailsService = (WeChatDetailsService)userDetailsService;
        UserDetails details = weChatDetailsService.loadByToken(wxOAuth2AccessToken.getOpenId(), wxOAuth2AccessToken.getAccessToken());
        if (details == null) {
            throw new AuthenticationServiceException("无法获取公众号用户");
        }

        JsCodeAuthenticationToken authenticationResult = new JsCodeAuthenticationToken(details.getUsername());
        authenticationResult.setDetails(token.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

AuthenticationProvider 的定义

AuthenticationProvider是Spring Security框架中的一个接口,用于身份验证的核心组件。

它的定义如下:

public interface AuthenticationProvider {
 Authentication authenticate(Authentication authentication) throws AuthenticationException;
 boolean supports(Class<?> authentication);
}

它包含两个方法:

  1. authenticate(Authentication authentication):该方法用于执行身份验证过程。输入参数authentication是一个封装了用户认证信息的Authentication对象,包括用户名、密码等信息。该方法返回一个认证成功的Authentication对象,或者抛出AuthenticationException异常表示认证失败。
  2. supports(Class<?> authentication):该方法用于判断是否支持给定类型的认证请求。输入参数authentication是要被验证的对象类型,通常是UsernamePasswordAuthenticationToken或JwtAuthenticationToken等。该方法返回一个boolean值,表示是否支持该类型的认证请求。

AuthenticationProvider可以自定义实现,用于实现不同的身份验证逻辑。开发者可以通过实现该接口,并覆盖其中的方法来定制化身份验证过程,例如从数据库查询用户信息并进行密码校验等。在Spring Security的配置中,可以通过AuthenticationManagerBuilder来注册和配置AuthenticationProvider的实例,以供身份验证使用。

定义相关配置类

@Component
@EnableConfigurationProperties(WxMpProperties.class)
public class JsCodeAuthenticationSecurityConfig  extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationSuccessHandler jsCodeAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler jsCodeAuthenticationFailureHandler;

    @Autowired
    private WxOAuth2Service weChatService;

    @Autowired
    private WechatProperties weChatProperties;

    @Autowired
    private WxMpProperties wxMpProperties;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        JsCodeAuthenticationFilter mobilePasswordAuthenticationFilter = new JsCodeAuthenticationFilter(weChatProperties);
        mobilePasswordAuthenticationFilter.setAuthenticationSuccessHandler(jsCodeAuthenticationSuccessHandler);
        mobilePasswordAuthenticationFilter.setAuthenticationFailureHandler(jsCodeAuthenticationFailureHandler);
        mobilePasswordAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        JsCodeAuthenticationProvider provider = new JsCodeAuthenticationProvider(weChatService, wxMpProperties);
        provider.setUserDetailsService(userDetailsService);
        // 将当前服务注册到 mobilePasswordAuthenticationFilter 连之后
        http.authenticationProvider(provider).addFilterAfter(mobilePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

SecurityConfigurerAdapter 的定义

SecurityConfigurerAdapter是Spring Security提供的一个抽象类,用于简化配置和定制化Spring Security的行为。

它是一个适配器模式,继承自SecurityConfigurer接口,并提供了一些默认实现方法,用于提供给开发者灵活地配置和扩展Spring Security。

SecurityConfigurerAdapter的定义如下:

public abstract class SecurityConfigurerAdapter<O extends SecurityFilterChain, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
 // 提供一个空实现的configure方法,供子类进行覆盖定制
  public void configure(B builder) throws Exception {}
 // 提供一个空实现的init方法,供子类进行覆盖定制 
 public void init(B builder) throws Exception {}
 // 提供一个空实现的configure方法,供子类进行覆盖定制 
 public void configure(O object) throws Exception {}
 // 提供一个空实现的postProcess方法,供子类进行覆盖定制 
 protected void postProcess(O object) throws Exception {}
}

开发者可以继承SecurityConfigurerAdapter,并重写其中的方法来实现自定义的安全配置。常见的用法是在WebSecurityConfigurerAdapter中继承SecurityConfigurerAdapter,并重写configure方法来配置Spring Security的行为,例如定义认证规则、权限控制等。通过继承SecurityConfigurerAdapter可以避免直接实现SecurityConfigurer接口时需要实现所有方法的繁琐操作。

整体配置



    public void configure(HttpSecurity http) throws Exception {
        String[] auth = StringUtils.split( securityProperties.getAuthUrl(), ",");
        http.formLogin().failureHandler(authenticationFailureHandler)
                .successHandler(authenticationSuccessHandler)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .and().apply(jsCodeAuthenticationSecurityConfig)
                .and().apply(usernameAuthenticationSecurityConfig())
                .and().authorizeRequests().antMatchers(auth).authenticated()
                .and().authorizeRequests().anyRequest().permitAll()
                .and().csrf().disable();
    }


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Security提供了多种登录方式的支持,可以使用表单登录、Basic认证、OAuth2等方式进行身份验证。如果需要自定义多种登录方式,可以按照以下步骤进行: 1. 实现自定义的AuthenticationProvider AuthenticationProvider是Spring Security的一个核心接口,用于实现身份验证逻辑。通过实现自定义的AuthenticationProvider,可以实现多种不同的身份验证方式。 例如,可以实现一个LDAPAuthenticationProvider,用于基于LDAP的身份验证,或者实现一个SmsCodeAuthenticationProvider,用于基于短信验证码的身份验证。 2. 配置多个AuthenticationProvider 在Spring Security的配置文件中,可以通过配置多个AuthenticationProvider来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationProvider和一个基于OAuth2的AuthenticationProvider。 3. 实现自定义的AuthenticationFilter AuthenticationFilter是Spring Security用于处理身份验证请求的过滤器。通过实现自定义的AuthenticationFilter,可以实现多种不同的身份验证方式。 例如,可以实现一个基于短信验证码的AuthenticationFilter,用于处理短信验证码登录请求。 4. 配置多个AuthenticationFilter 在Spring Security的配置文件中,可以通过配置多个AuthenticationFilter来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationFilter和一个基于短信验证码的AuthenticationFilter。 总的来说,实现多种登录方式的关键在于实现自定义的AuthenticationProvider和AuthenticationFilter,并在Spring Security的配置文件中进行配置。 ### 回答2: Spring Security 提供了多种自定义登录方式的选项。以下是一些常见的方法: 1. 自定义用户名密码登录:可以使用 Spring Security 的表单登录功能,通过配置用户名和密码的输入框,实现用户名密码登录功能。 例如,可以通过配置 `formLogin()` 方法来实现: ```java protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login") .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/home") .permitAll(); } ``` 2. 自定义第三方登录:可以使用 Spring Security OAuth2 来实现第三方登录,例如使用 Facebook、Google 或 Github 等社交媒体的账号进行登录Spring Security OAuth2 提供了很多集成第三方认证的实例代码,可以根据具体的需求进行自定义。 3. 自定义手机号码登录:可以通过继承 Spring Security 的 `AbstractAuthenticationProcessingFilter` 类来实现自定义手机号码登录。 可以在自定义的过滤器中验证手机号码,并进行认证逻辑。 4. 自定义单点登录(SSO):可以通过集成 Spring Security 的 `AuthenticationProvider` 接口来实现自定义的单点登录认证。 可以通过实现该接口的 `authenticate()` 方法来处理单点登录的逻辑。 这些只是一些常见的自定义登录方式的示例,根据具体的需求,可以结合 Spring Security 提供的各种功能和扩展点,灵活地进行自定义实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值