Spring Boot - security 實戰與源碼分析

1、實現步驟

1.在application.yml中添加起步依賴java

2.自定義安全類web

package com.example.demo.readinglist;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private ReaderRepository readerRepository;
  
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .antMatchers("/").access("hasRole('READER')")
        .antMatchers("/**").permitAll()
      .and()
      .formLogin()
        .loginPage("/login")
        .failureUrl("/login?error=true");
  }
  
  @Override
  protected void configure(
              AuthenticationManagerBuilder auth) throws Exception {
    auth
      .userDetailsService(new UserDetailsService() {
        @Override
        public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
          UserDetails userDetails = readerRepository.findOne(username);
          if (userDetails != null) {
            return userDetails;
          }
          throw new UsernameNotFoundException("User '" + username + "' not found.");
        }
      });
  }

}

3.定義實體類spring

package com.example.demo.readinglist;

import java.util.Arrays;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.Id;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
public class Reader implements UserDetails {

  private static final long serialVersionUID = 1L;

  @Id
  private String username;
  
  private String fullname;
  private String password;
  
  public String getUsername() {
    return username;
  }
  
  public void setUsername(String username) {
    this.username = username;
  }
  
  public String getFullname() {
    return fullname;
  }
  
  public void setFullname(String fullname) {
    this.fullname = fullname;
  }
  
  public String getPassword() {
    return password;
  }
  
  public void setPassword(String password) {
    this.password = password;
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return Arrays.asList(new SimpleGrantedAuthority("ROLE_READER"));
  }

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return true;
  }

}

4.定義實體對應的倉庫數據庫

package com.example.demo.readinglist;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ReaderRepository extends JpaRepository<Reader, String> {
}

5.自定義參數解析器安全

package com.example.demo.readinglist;

import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class ReaderHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return Reader.class.isAssignableFrom(parameter.getParameterType());
  }

  @Override
  public Object resolveArgument(MethodParameter parameter,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
      WebDataBinderFactory binderFactory) throws Exception {

    Authentication auth = (Authentication) webRequest.getUserPrincipal();
    return auth != null && auth.getPrincipal() instanceof Reader ? auth.getPrincipal() : null;
    
  }

}

從安全認證返回的結果中得到參數實體類,其中爲何能從安全認證的結果中獲得實體類,後面會詳細說明app

6.在主應用程序中添加視圖控制器和參數解析器ide

package com.example.demo;

import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.example.demo.readinglist.ReaderHandlerMethodArgumentResolver;

@SpringBootApplication
public class ReadingListApplication extends WebMvcConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(ReadingListApplication.class, args);
    }
    
    //添加視圖控制器
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/login").setViewName("login");
    }
    
    //添加自定義參數解析器
    @Override
    public void addArgumentResolvers(
        List<HandlerMethodArgumentResolver> argumentResolvers) {
      argumentResolvers.add(new ReaderHandlerMethodArgumentResolver());
    }
    
}

2、源碼分析

大致流程:源碼分析

Spring Boot在啓動的時候會先掃描到自定義的安全配置類SecurityConfig,登錄時會根據輸入的用戶與密碼從嵌入式數據庫中查找對應的記錄,若是找到了則表示認證成功ui

詳情以下:this

1.Spring  Boot啓動時掃描安全配置類SecurityConfig,並設置權限控制和認證策略

權限控制

認證策略,該方法會將匿名內部類注入到DaoAuthenticationProvider的userDetailsService

2.輸入用戶名和密碼,點擊登陸,Spring Boot 根據參數使用DaoAuthenticationProvider的retrieveUser方法獲得登陸用戶詳情

圈出來的就是關鍵的部分,這裏就是調用了匿名內部類中重寫發loadUserByUsername方法

最終調用的就是根據用戶名查找用戶詳情的代碼(若是對Spring 的 repository高級特性不懂的再去百度一下)

而後使用DaoAuthenticationProvider類的additionalAuthenticationChecks進行密碼的比較

認證經過建立一個UsernamePasswordAuthenticationToken,而且屬性principal爲一個UserDetails類型的Reader(屬性已經所有賦值),則密碼不對則拋出異常

 

若是應用的控制器須要使用上面獲得的Reader,那麼使用以下代碼便可

Spring 判斷是否是目標方法的參數是否是支持的參數類型(Reader.class),若是是,則從request中取登陸認證結果對象做爲參數傳給目標方法,Spring實際上就是在綁定參數時調用了ReaderHandlerMethodArgumentResolver.resolveArgument方法作到的

相關文章
相關標籤/搜索