Location>code7788 >text

Seven, Spring Boot integration Spring Security before and after the separation of the best implementation of authentication

Popularity:184 ℃/2024-11-06 22:09:44

Second, custom user name password authentication filter RestfulUsernamePasswordAuthenticationFilter

1、Registration filter mode

  1. Use /addFilterBefore/addFilterAfter to add filters to the filter chain, where addFilter can only add built-in filters, the order of which has been set in FilterOrderRegistration; addFilterBefore/addFilterAfter can add custom filters before/after the specified filters. addFilterAfter can add custom filters, add before/after the specified filter. The advantage of this approach is simple to use , the disadvantage is that you can not use spring security built-in components , and RestfulUsernamePasswordAuthenticationFilter need to use the AuthenticationManager component conflict , so do not use this approach .
  2. Use SecurityConfigurer to add filters to the filter chain by way of configuration classes, the official way to use. The advantage of this approach is that you can use spring security built-in components, but the disadvantage is that the implementation is more cumbersome, and you can only register filters that are set in the FilterOrderRegistration. This approach can use spring security built-in components, so to use this approach, you need to modify the FilterOrderRegistration to add custom filters.

2. Modify and override the filter order register

  1. The FilterOrderRegistration class is final and does not provide an open way to register custom filters, so you can only rewrite the class and add a custom filter order.
package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .x509.X509AuthenticationFilter;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;

final class FilterOrderRegistration {


    private static final int INITIAL_ORDER = 100;

    private static final int ORDER_STEP = 100;

    private final Map<String, Integer> filterToOrder = new HashMap<>();

    FilterOrderRegistration() {
        Step order = new Step(INITIAL_ORDER, ORDER_STEP);
        put(, ());
        put(, ());
        put(, ());
        (); // gh-8105
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        (
                "..OAuth2AuthorizationRequestRedirectFilter",
                ());
        (
                "..Saml2WebSsoAuthenticationRequestFilter",
                ());
        put(, ());
        put(, ());
        ("", ());
        ("..OAuth2LoginAuthenticationFilter",
                ());
        (
                "..Saml2WebSsoAuthenticationFilter",
                ());
        //Adding custom filters
        put(, ());
        put(, ());
        (); // gh-8105
        ("", ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        (
                ".",
                ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        ("..OAuth2AuthorizationCodeGrantFilter",
                ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
        put(, ());
    }

    /**
     * Register a {@link Filter} with its specific position. If the {@link Filter} was
     * already registered before, the position previously defined is not going to be
     * overriden
     *
     * @param filter the {@link Filter} to register
     * @param position the position to associate with the {@link Filter}
     */
    void put(Class<? extends Filter> filter, int position) {
        String className = ();
        if ((className)) {
            return;
        }
        (className, position);
    }

    /**
     * Gets the order of a particular {@link Filter} class taking into consideration
     * superclasses.
     *
     * @param clazz the {@link Filter} class to determine the sort order
     * @return the sort order or null if not defined
     */
    Integer getOrder(Class<?> clazz) {
        while (clazz != null) {
            Integer result = (());
            if (result != null) {
                return result;
            }
            clazz = ();
        }
        return null;
    }

    private static class Step {

        private final int stepSize;
        private int value;

        Step(int initialValue, int stepSize) {
             = initialValue;
             = stepSize;
        }

        int next() {
            int value = ;
             += ;
            return value;
        }

    }

}

3, create RestfulUsernamePasswordAuthenticationFilter

  1. References UsernamePasswordAuthenticationFilter
  2. Change the parameter fetch method from to from body body
  3. Create UsernamePasswordAuthenticationToken
  4. Setup Details
  5. Call the authenticate method of getAuthenticationManager() to get authentication information
package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;
import ;

/**
 * Customized front-end and back-end separation/restfulUser name and password authentication filters for the method
 * consultationUsernamePasswordAuthenticationFilter
 */
public class RestfulUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    //Does it only supportpostmethodologies
    private final boolean postOnly;
    private final String username;
    private final String password;

    public RestfulUsernamePasswordAuthenticationFilter(String username, String password, String loginUrl, String httpMethod) {
        super(new AntPathRequestMatcher(loginUrl, httpMethod));
        postOnly = ().equals(httpMethod);
         = username;
         = password;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        if ( && !().equals(())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + ());
        } else {
            Map<String, String> body = (request);
            String name = (username);
            String pswd = (password);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(name, pswd);
            setDetails(request, authRequest);
            return getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        ((request));
    }

}

4, create a custom user name password authentication filter configuration class RestfulLoginConfigurer

  1. References FormLoginConfigurer
  2. Register custom username password authentication filter RestfulUsernamePasswordAuthenticationFilter
  3. Setting the login address and request method
package ;

import ;
import ;
import ;
import ;

/**
 * Customized front-end and back-end separation/restfulConfigurator for the username-password authentication filter for the mode,For registering authentication filters
 * consultationFormLoginConfigurer
 */
public class RestfulLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, RestfulLoginConfigurer<H>, RestfulUsernamePasswordAuthenticationFilter> {
    private final String loginMethod;

    public RestfulLoginConfigurer(RestfulUsernamePasswordAuthenticationFilter authenticationFilter, String defaultLoginProcessingUrl, String loginMethod) {
        super(authenticationFilter, defaultLoginProcessingUrl);
         = loginMethod;
    }

    @Override
    public RestfulLoginConfigurer<H> loginPage(String loginPage) {
        return (loginPage);
    }

    @Override
    public void init(H http) throws Exception {
        (http);
    }

    @Override
    protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
        return new AntPathRequestMatcher(loginProcessingUrl, loginMethod);
    }
}

Third, custom security context repository SecurityContextRepositoryImpl

  1. Implementing a Secure Contextual Warehouse Based on Distributed Caching
  2. Get the context from the request header to get the token, get the context from the cache through the token, and return the null security context if it does not exist
  3. When saving the context get the token from the request header or logged in user information and save the token and context to the cache

1、Distributed cache interface and implementation

package ;

import ;

public interface CacheManager {

    /**
     * pass (a bill or inspection etc)tokenGetting Authentication Information
     *
     * @param token token
     * @return Certification Information
     */
    SecurityContext getSecurityContext(String token);

    /**
     * Does it containtoken
     *
     * @param token token
     * @return Does it containtoken
     */
    boolean contains(String token);

    /**
     * pass (a bill or inspection etc)token添加Certification Information
     *
     * @param token token
     * @param securityContext Certification Information
     */
    void addSecurityContext(String token, SecurityContext securityContext);

    /**
     * pass (a bill or inspection etc)token删除Certification Information
     *
     * @param token token
     */
    void deleteSecurityContext(String token);

}

For the convenience of demonstration, here the expired Map, the actual use of the map will be changed to redis or other distributed cache can be

package ;

import ;
import ;
import ;
import ;
import ;

import ;
import ;

@Component
public class CacheManagerImpl implements CacheManager {

    private static ExpiringMap<String, SecurityContext> SECURITY_CONTEXT_CACHE;

    @PostConstruct
    public void init() {
        SECURITY_CONTEXT_CACHE = ().maxSize(200).expiration(30, ).expirationPolicy().variableExpiration().build();
    }

    @Override
    public SecurityContext getSecurityContext(String token) {
        return SECURITY_CONTEXT_CACHE.get(token);
    }

    @Override
    public boolean contains(String token) {
        return SECURITY_CONTEXT_CACHE.containsKey(token);
    }

    @Override
    public void addSecurityContext(String token, SecurityContext securityContext) {
        SECURITY_CONTEXT_CACHE.put(token, securityContext);
    }

    @Override
    public void deleteSecurityContext(String token) {
        SECURITY_CONTEXT_CACHE.remove(token);
    }
}

2, create SecurityContextRepositoryImpl

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;

@Component
public class SecurityContextRepositoryImpl implements SecurityContextRepository {

    private static final String AUTHENTICATION = "Authentication";
    @Autowired
    private CacheManager cacheManager;

    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        //Get thetoken,When not logged in to access the systemTokenempty
        String token = ().getHeader(AUTHENTICATION);
        if ((token)) {
            SecurityContext securityContext = (token);
            //securityContext已expire (as in expiration date)时empty
            if ((securityContext)) {
                return ();
            }
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) ();
            UserDetailsImpl userDetails = (UserDetailsImpl) ();
            if ((())) {
                //Falsified during testingToken(unmodifiedheadercap (a poem)body,modify onlysignaturepartial character)There is a probability of successful parsing,could besecretReasons for being too short,not pursued further,So here's the input under validationTokencap (a poem)缓存中的token
                return securityContext;
            }
        }
        return ();
    }

    @Override
    public void saveContext(SecurityContext securityContext, HttpServletRequest request, HttpServletResponse response) {
        //Get thetoken(Logout with,Not available at login)
        String token = (AUTHENTICATION);
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) ();
        if ((token) && (securityContext)) {
            //not logged in、CAPTCHA, a type of challenge-response test (computing)、User name and password verification failed
            return;
        }
        //At first loginTokenempty
        if ((token)) {
            UserDetailsImpl userDetails = (UserDetailsImpl) ();
            //Login Successful
            ((), securityContext);
            return;
        }
        //Withdrawal ortokenexpire (as in expiration date)(Setting in the cachetokenexpire (as in expiration date)时间)
        if ((securityContext)) {
            (token);
            return;
        }
        //updateToken
        (token, securityContext);
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        //current versionSpring Securityonly ifSessionManagementFilterThe method is called in the
        //disabledSessionManagementFilter,This method will not be called
        String token = (AUTHENTICATION);
        if ((token)) {
            return false;
        }
        if ((token)) {
            return false;
        }
        return (token);
    }

}

Fourth, customize the user details UserDetailsImpl

package ;

import ;
import ;
import ;
import ;
import ;

import ;
import ;

@Setter
@Getter
@ToString
public class UserDetailsImpl implements UserDetails {
    private String password;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;
    /**
     * token
     */
    private String token;

    public UserDetailsImpl(String username, String password, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, boolean enabled, Set<GrantedAuthority> grantedAuthorities) {
         = username;
         = password;
         = enabled;
         = accountNonExpired;
         = credentialsNonExpired;
         = accountNonLocked;
         = grantedAuthorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    /**
     * Whether the account has not expired
     *
     * @return true:be,false:clogged
     */
    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    /**
     * 账号beclogged未锁定
     *
     * @return true:be,false:clogged
     */
    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    /**
     * 密码beclogged未过期
     *
     * @return true:be,false:clogged
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    /**
     * 账号beclogged启用
     *
     * @return true:be,false:clogged
     */
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

V. Customized user details database query UserDetailsServiceImpl

package ;

import ;
import ;
import ;
import ;
import ;

import ;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    //@Autowired
    //private UserService userService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //TODO pass (a bill or inspection etc)usernameGetting users from the database,redirect usersUserDetails
        //User user = (username);
        //return new User(username, (), (), (), (), (), ());
        //{noop}No password encryptor,cryptographic123All of them can be verified successfully
        UserDetailsImpl userDetails = new UserDetailsImpl(username, "{noop}123", true, true, true, true, null);
        //userDetailsset uptoken,ought totokenJust realizing the certification process,unusedjwt
        (().toString());
        return userDetails;
    }

}

VI. Customized Logout Logout Results Processor

package ;


import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;

@Component
public class LoginResultHandler implements AuthenticationSuccessHandler, LogoutSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
        UserDetailsImpl userDetailsImpl = (UserDetailsImpl) ();
        Map<String, Object> resp = new HashMap<>();
        //00000show success
        ("code", "00000");
        ("token", ());
        //generatingtokenBack to front end
        (response, resp);
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Map<String, Object> resp = new HashMap<>();
        //00000show success
        ("code", "00000");
        (response, resp);
    }
}

VII. Personalized configuration of the filter chain

package ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    //Login Parameters User Name
    private static final String LOGIN_ARG_USERNAME = "username";
    //Login Parameters Password
    private static final String LOGIN_ARG_PASSWORD = "password";
    //Login Request Type
    private static final String LOGIN_HTTP_METHOD = ();
    //Login request address
    private static final String LOGIN_URL = "/login";
    //Logout Request Address
    private static final String LOGOUT_URL = "/logout";

    @Autowired
    private LoginResultHandler loginResultHandler;
    @Autowired
    private SecurityContextRepository securityContextRepository;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                //prohibit the use of sth.UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter、DefaultLogoutPageGeneratingFilter
                .formLogin(FormLoginConfigurer::disable)
                //prohibit the use of sth.BasicAuthenticationFilter
                .httpBasic(HttpBasicConfigurer::disable)
                //prohibit the use of sth.CsrfFilter
                .csrf(CsrfConfigurer::disable)
                //prohibit the use of sth.SessionManagementFilter
                .sessionManagement(SessionManagementConfigurer::disable)
                //httpRequest for accreditation
                .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer
                        //Any request
                        .anyRequest()
                        //Authentication required
                        .authenticated())
                //Security Context Configuration
                .securityContext(securityContextCustomizer -> securityContextCustomizer
                        //Setup CustomizationsecurityContextrepository
                        .securityContextRepository(securityContextRepository)
                        //Show SaveSecurityContext,Official Recommendations
                        .requireExplicitSave(true))
                //Logout Configuration
                .logout(logoutCustomizer -> logoutCustomizer
                        //logout address
                        .logoutUrl(LOGOUT_URL)
                        //Logout Success Processor
                        .logoutSuccessHandler(loginResultHandler)
                )
                //Register Configurator for Custom Login Filter:Automatic registration of customized login filters;
                //Needs to be rewrittenFilterOrderRegistrationconstructorFilterOrderRegistration(){},Add the serial number of the custom filter in the constructor method,Otherwise the registration will not be successful
                .apply(new RestfulLoginConfigurer<>(new RestfulUsernamePasswordAuthenticationFilter(LOGIN_ARG_USERNAME, LOGIN_ARG_PASSWORD, LOGIN_URL, LOGIN_HTTP_METHOD), LOGIN_URL, LOGIN_HTTP_METHOD))
                //Setting the login address:The system generates a login page by default when it is not set.,login address/login
                .loginPage(LOGIN_URL)
                //Setting up the processor after a successful login
                .successHandler(loginResultHandler);

        //Creating a Filter Chain Object
        return ();
    }

}

VIII. Other categories

package ;

import ;
import ;
import ;

import ;
import ;

/**
 * JSONtools
 *
 * @author admin
 */
public class JsonUtil {
    private JsonUtil() {
        throw new AssertionError();
    }

    /**
     * object transferjson
     *
     * @param javaObject Objects or collections or arrays
     * @return json
     */
    public static String object2Json(Object javaObject) {
        return (javaObject);
    }

    public static <K, V> Map<K, V> json2Map(String jsonString, Type type) {
        return (jsonString, type);
    }
}

package ;

import ;
import ;

/**
 * SpringFramework Toolkit
 */
public class SecurityUtil {

    private SecurityUtil() {
        throw new AssertionError();
    }

    public static boolean isAuthenticated(SecurityContext securityContext) {
        if (securityContext == null) {
            return false;
        }
        Authentication authentication = ();
        if (authentication == null) {
            return false;
        }
        return ();
    }

    public static boolean isNotAuthenticated(SecurityContext securityContext) {
        return !isAuthenticated(securityContext);
    }

}

package ;

import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;

/**
 * SpringFramework Toolkit
 */
public class SpringUtil {

    private SpringUtil() {
        throw new AssertionError();
    }

    /**
     * requestingbodyParameters are converted tomap
     *
     * @param request requesting
     * @return parametersmap
     * @throws IOException IOstream anomaly (geology)
     */
    public static Map<String, String> rawBodyToMap(HttpServletRequest request) throws IOException {
        BufferedReader streamReader = new BufferedReader(new InputStreamReader((), StandardCharsets.UTF_8));
        StringBuilder responseStrBuilder = new StringBuilder();
        String inputStr;
        while ((inputStr = ()) != null) {
            (inputStr);
        }
        return JsonUtil.json2Map((), );
    }


    public static void respJson(HttpServletResponse response, Map<String, Object> apiResp) throws IOException {
        (MediaType.APPLICATION_JSON_VALUE);
        (StandardCharsets.UTF_8.name());
        ().print(JsonUtil.object2Json(apiResp));
    }

}

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0" xmlns:xsi="http:///2001/XMLSchema-instance"
         xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId></groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId></groupId>
    <artifactId>spring-boot-security2-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-security2-demo</name>
    <description>Spring Bootintegrated (as in integrated circuit)Spring Securityexample</description>

    <properties>
        <>8</>
    </properties>

    <dependencies>
        <dependency>
            <groupId></groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId></groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId></groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>
        <!--expire (as in expiration date)map-->
        <dependency>
            <groupId></groupId>
            <artifactId>expiringmap</artifactId>
            <version>0.5.11</version>
        </dependency>
        <dependency>
            <groupId></groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
    </dependencies>

</project>

IX. Access to case source code

  • download address
  • Private chats, comment sections, +V are all fine!