General documentation: Article directory Github: github.com/black-ant

The protocol is almost finished, the rest are mainly some highly targeted protocols, and the individual has not completely passed, so we will not enter them for the time being. The authentication series begins to analyze different authentication frameworks and the implementation of relevant protocols.

A. The preface

SpringSecurity is probably the most common authentication framework, and being in the Spring architecture allows him to get started quickly. This article begins as an introductory article to briefly explain the overall structure of SpringSecurity.

The official documentation on the use of Security is too detailed, and it is recommended to review it directlyThe official documentation, as the beginning, do not go into the source code, only to introduce the system ~~

The following will be one after another in the notes of the source code comb out, notes are very messy, I hope to sort out the system!

2. Knowledge system of Spring Security

The members of Security that we need to operate on can be roughly divided into the following categories, but there are many more classes involved

  • Filter: Intercepts and processes requests
  • Provider: authenticates users
  • Token: indicates the user authentication subject

2.1 Main package structure of Spring Security

  • Spring-security-remoting: Provides integration with Spring Remoting
  • Spring-security-web: Contains filters and associated network security infrastructure code.
  • Spring-security-config: Contains security namespace resolution code and Java configuration code? – Java Configuration support for Configuration using the Spring Security XML namespace or Srping Security
  • Spring-security-ldap: This module provides LDAP authentication and configuration code
  • Spring-security-oauth2-core: Contains core classes and interfaces that support the OAuth 2.0 authorization framework and OpenID Connect Core 1.0
  • Spring-security-oauth2-client: Includes Spring Security client support for the OAuth 2.0 authorization framework and OpenID Connect Core 1.0
  • spring-security-oauth2-jose :Includes Spring Security support for the JOSE (Javascript object signing and encryption) framework
    • JSON Web Token (JWT)
    • JSON Web Signature (JWS)
    • JSON Web Encryption (JWE)
    • JSON Web Key (JWK)
  • Spring-security-oauth2-resource-server: includes Spring Security support for OAuth 2.0 resource servers. It protects the API by hosting tokens through OAuth 2.0
  • Spring-security-acl: This module contains a specialized implementation of domain object ACL. It is used to apply security to specific domain object instances in the application
  • Spring-security-cas: This module contains the CAS client integration for Spring Security
  • Spring-security-openid: This module includes OpenID Web authentication support. It is used to authenticate users against an external OpenID server.
  • spring-security-test
  • spring-secuity-taglibs

2.2 Spring Security 核心体系 Filter

There are a lot of filters in SpringSecurity. Aside from the underlying ones, filters in general business are mainly for controlling how authentication is performed. In common architectures, the process will be looped. For example, in CAS, it is looped through HandlerManager for each, and in SpirngSecurity, it is looped through SecurityFilterChain.

SecurityFilterChain will be introduced in detail in the later source code comb, here is a picture:

FilterChainProxy uses SecurityFilterChain to determine which Spring security filter should be called for this request,

FilterChainProxy determines which SecurityFilterChain should be used. The first matched SecurityFilterChain is called, i.e. the match is ordered

  • If the URL for/API /messages/ is requested, it will first match the/API /** pattern of SecurityFilterChain0, so only SecurityFilterChain0 will be called, even though it also matches SecurityFilterChainn.

  • If the URL of /messages/ is requested, it will not match the/API /** pattern of SecurityFilterChain0, so FilterChainProxy will continue to try every SecurityFilterChain. Assuming no other instance of SecurityFilterChai matches SecurityFilterChainn will be called. (that is, no match calls the last one)

The Filter class is known

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
Copy the code

2.3 Authentication Architecture

Certification system is the core processing system, including the following main categories:

SecurityContextHolder: Where Spring Security stores the details of the verifier. SecurityContext: obtained from SecurityContextHolder and containing the authentication of the currently authenticated user. Authentication: can be used as input to the AuthenticationManager to provide credentials that the user provides for Authentication or the current user from the SecurityContext. GrantedAuthority: The authority granted to the principal on authentication (that is, role, scope, and so on). AuthenticationManager: API that defines how Spring Security’s filters perform authentication. ProviderManager: The most common implementation of AuthenticationManager. Providationprovider: Used by the ProviderManager to perform specific types of authentication. AuthenticationEntryPoint: Used to request credentials from the client (that is, redirect to the login page, send a WWW authentication response, and so on). AbstractAuthenticationProcessingFilter: basic filter used to validate user credentials of the AccessDecisionManager: By the AbstractSecurityInterceptor calls, is responsible for making the final access control decisions

In the future, we will conduct source code combing around the above classes:

3. Case of Springsesecurity

Here is a very simple Security case:>>>> project source <<<<

3.1 SpringSecurity Dependency and Configuration

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

Copy the code
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler myAuthenctiationFailureHandler;

    @Bean
    public UserService CustomerUserService(a) {
        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 {
        // Request authorization is used in this method to specify which requests to intercept
        // Where: antMatchers-- Use Ant style path matching
        //regexMatchers-- use regular expressions to match
        http.authorizeRequests()
                .antMatchers("/test/**").permitAll()
                .antMatchers("/before/**").permitAll()
                .antMatchers("/index").permitAll()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated()                      // All other requests must be verified before access
                .and()
                .formLogin()
                .loginPage("/login")                             // Define the login page "/login" to allow access
                .defaultSuccessUrl("/home")  // Default to "list" after successful login
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
                .logout()                                           // The default "/logout" is allowed
                .logoutSuccessUrl("/index")
                .permitAll();
        http.addFilterBefore(new BeforeFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Resolve the issue of static resources being intercepted
        web.ignoring().antMatchers("/**/*.js"."/lang/*.json"."/**/*.css"."/**/*.js"."/**/*.map"."/**/*.html"."/**/*.png"); }}Copy the code

There are a few main places:

What did @enableWebSecurity do?

  • This configuration will create a servlet Filter, as called springSecurityFilterChain bean
  • Create a UserDetailsService bean with the user user name and a randomly generated password to log in to the console
  • For each request to the Servlet container registered bean called springSecurityFilterChain Filter

TODO

3.2 Preparing a UserDetailsService

public class UserService implements UserDetailsService {

    / /...

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users user = userRepository.findByUsername(username);
        / /...
        return user;
    }
Copy the code

A basic Demo is done. The case is as simple as it gets, mainly because we reuse the following classes:

  • UsernamePasswordAuthenticationFilter
  • DaoAuthenticationProvider
  • UsernamePasswordAuthenticationToken

4. Customized cases

We customized the entire structure to suit our own function:

4.1 Step 1: Customize a Filter

/ / we reuse UsernamePasswordAuthenticationFilter, its part of the custom
public class DatabaseAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    // Change the user name to 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(a) {
        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 is obtained from the following method
        String username = obtainUsername(request);
        String password = obtainPassword(request);

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

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

        username = username.trim();
        // Core: DatabaseUserToken is replaced here
        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(a) {
        return usernameParameter;
    }

    public final String getPasswordParameter(a) {
        returnpasswordParameter; }}Copy the code

4.2 Step 2: Prepare a Token

Token is the core of transmission in Authentication. It is used for the subsequent Authentication of the ticket

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(a) {
        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(a) {
        return credentials;
    }

    @Override
    public Object getPrincipal(a) {
        returnprincipal; }}Copy the code

4.3 Step 3: Prepare a Provider

The supports method is used to determine whether the token meets the requirements, so as to initiate the authentication process (PS: just like 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("Password cannot be empty.");
        }

        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("User does not exist");
        }
        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("Incorrect user name or password");
        } 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"));
        returnauthorities; }}Copy the code

4.4 Hidden Links

// Inject Provider into the system
auth.authenticationProvider(reflectionUtils.springClassLoad(item.getProvider()));

// Inject the Filter into the system
AbstractAuthenticationProcessingFilter filter = reflectionUtils.classLoadReflect(item.getFilter());
filter.setAuthenticationManager(authenticationManager);
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
Copy the code

The above process can be seen that there is no need to inherit the UserService class. Rewriting this is enough to implement most of the business logic, using the Provider to complete the corresponding authentication mode

In live.

Security is easy to use. In my personal practice, I am trying to integrate common protocols into an open source scaffolding, and I feel that SpringSecuity should be easy to accomplish.

The beginning is relatively simple, and I am thinking about how to explain it clearly from a practical point of view. My notes are also being organized one after another, and I will try to send out the whole article next month!

The appendix

Common HttpSecurity methods