Spring security 体验

39 阅读3分钟

两个依赖的区别


在整个spring中有两个有关security的依赖可以选择: spring-cloud-starter-security和spring-boot-starter-security,很多时候不知道应该选择哪一个,这里做一个介绍:

⛱️先说结论:spring-cloud-starter-security中包含很多依赖包,其中就有spring-boot-starter-security,所以是包含的关系。如果只是基于spring-boot的单体应用,直接使用spring-boot-starter-security即可,如果是基于spring-cloud开发微服务应用,可以选择另一个

spring-boot-starter-security


<dependency>   
    <groupId>org.springframework.cloud</groupId>   
    <artifactId>spring-cloud-starter-security</artifactId> 
</dependency>

这个依赖中包含很多组件,其中有一个:

<dependency>   
    <groupId>org.springframework.cloud</groupId>   
    <artifactId>spring-cloud-security</artifactId>   
    <version>2.2.4.RELEASE</version>   
    <scope>compile</scope> 
</dependency>

继续深挖,该依赖中包含的组件更多,比如gateway、oauth-client和security等:

<dependency>   
    <groupId>org.springframework.boot</groupId>   
    <artifactId>spring-boot-starter-security</artifactId>   
    <version>2.3.2.RELEASE</version>  
    <scope>compile</scope> 
</dependency>

到这里就能明白了,spring-cloud-starter-security最终也是依赖spring-boot-starter-security,同时还包括很多其他的cloud组件。

添加依赖


为了纯粹的体验和分析security源码,这里选择spring-boot-starter-security依赖:

<dependency>     
    <groupId>org.springframework.boot</groupId>     
    <artifactId>spring-boot-starter-security</artifactId> 
</dependency>

登录Config和Filter


Security实际上是由大量Filter Chain构成,各种各样的Filter负责不同的逻辑,和登录相关的配置默认使用FormLoginConfig类,拦截处理登录的逻辑默认使用UsernamePasswordAuthenticationFilter:

FormLoginConfig

负责配置UsernamePasswordAuthenticationFilter到Chain中,同时设置默认登录相关的信息,源码:

package org.springframework.security.config.annotation.web.configurers;
// 定义比较复杂,各种泛型,后面单独介绍,暂时不用管
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
      AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
   //眼熟吧,这就是默认的用户名和密码参数设置的地方,如果不配置默认就在这里设置的
   public FormLoginConfigurer() {
      super(new UsernamePasswordAuthenticationFilter(), null);
      usernameParameter("username");
      passwordParameter("password");
   }
   //设置登录页面
   @Override
   public FormLoginConfigurer<H> loginPage(String loginPage) {
      return super.loginPage(loginPage);
   }
   //修改默认的用户名参数
   public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
      getAuthenticationFilter().setUsernameParameter(usernameParameter);
      return this;
   }
   //修改默认的密码参数
   public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
      getAuthenticationFilter().setPasswordParameter(passwordParameter);
      return this;
   }
   //登录失败后跳转的地址
   public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
      failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
      return this;
   }
   //登录成功后跳转的地址
   public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) {
      successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
      return this;
   }

   @Override
   public void init(H http) throws Exception {
      super.init(http);
      initDefaultLoginFilter(http);
   }
 
   private String getUsernameParameter() {
      return getAuthenticationFilter().getUsernameParameter();
   }
   private String getPasswordParameter() {
      return getAuthenticationFilter().getPasswordParameter();
   }
 
   private void initDefaultLoginFilter(H http) {
      DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
            .getSharedObject(DefaultLoginPageGeneratingFilter.class);
      if (loginPageGeneratingFilter != null && !isCustomLoginPage()) {
         loginPageGeneratingFilter.setFormLoginEnabled(true);
         loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
         loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
         loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
         loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
         loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
      }
   }
}

整个类比较简单,部分介绍直接写在注释了。

主要介绍一下方法initDefaultLoginFilter,方法中实例化一个Filter:DefaultLoginPageGeneratingFilter,当采用默认的登录配置时,登录请求的服务器地址、登录页面等都是这个类生成的,简单看下属性:

image.png

第2行(选中行)默认定义的登录地址/login就在这里,在该Filter的init方法中,默认设置请求的登录服务和登录页面地址都是/login

image.png

UsernamePasswordAuthenticationFilter

该拦截器主要负责拦击请求,获取登录用户名和密码,然后构建token,最后去认证。整个类也很简单,主要看下核心方法:

public Authentication attemptAuthentication(HttpServletRequest request,
      HttpServletResponse response) throws AuthenticationException {
   if (postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException(
            "Authentication method not supported: " + request.getMethod());
   }

   String username = obtainUsername(request);
   String password = obtainPassword(request);

   if (username == null) {
      username = "";
   }

   if (password == null) {
      password = "";
   }

   username = username.trim();

   // 构建Token,为认证做数据准备
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
         username, password);

   // Allow subclasses to set the "details" property
   setDetails(request, authRequest);

   return this.getAuthenticationManager().authenticate(authRequest);
}
逻辑比较简单,不再介绍。

修改默认配置

接着上文的例子,继续完善一下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .usernameParameter("yy_user")
                .passwordParameter("yy_pass")
                .loginPage("/yylogin.html")
                .loginProcessingUrl("/sys/login");
    }
}

解释

  • usernameParameter:登录时用户名的参数,默认username,通过该配置可以修改自定义登录界面传值时的用户参数
  • passwordParameter:登录时密码的参数,默认password,通过该配置可以修改自定义登录界面传值时的密码参数
  • loginPage:请求被拦截后,跳转的登录页,默认/login.html [get],配置自定义的登录界面地址,GET访问
  • loginProcessingUrl:登录请求的服务地址,默认/login.html [post],配置登录请求服务,POST访问

通过上述方式,即可修改默认的配置了,简单吧。